From f95e7fc4b84821ecb815eeccd8861d542d4f4d8d Mon Sep 17 00:00:00 2001 From: "leire.querejeta" <leire.querejeta@tecnalia.com> Date: Mon, 10 Oct 2022 16:21:36 +0200 Subject: [PATCH] class serial port api --- .gitattributes | 63 + .gitignore | 9 + .gitlab-ci.yml | 102 + README.md | 40 +- serial_port_class_api/CMakeLists.txt | 38 + serial_port_class_api/README.md | 7 + serial_port_class_api/commands.h | 139 + serial_port_class_api/commands.lib | Bin 0 -> 230986 bytes .../docs/CMakeLists.txt | 10 +- serial_port_class_api/docs/Doxyfile.in | 6 + .../include/CLASS_SerialPort.h | 193 ++ .../include/CLASS_structures.hpp | 38 + .../libreria_LQL/CMakeLists.txt | 8 + .../libreria_LQL/commands.cpp | 134 + serial_port_class_api/libreria_LQL/commands.h | 138 + .../src/CLASS_SerialPort.cpp | 1508 ++++++++++ serial_port_class_api/src/CMakeLists.txt | 19 + serial_port_class_api/src/main.cpp | 322 +++ .../wrappers/CLASS_SerialPort.i | 21 + src/.gitkeep | 0 src/CMakeLists.txt | 205 -- src/DownloadProject.CMakeLists.cmake.in | 18 - src/DownloadProject.cmake | 182 -- src/README.md | 49 - src/api/.gitkeep | 0 src/api/CMakeLists.txt | 4 - src/api/class/.gitkeep | 0 src/api/class/CMakeLists.txt | 71 - src/api/class/include/.gitkeep | 0 src/api/class/include/class/.gitkeep | 0 src/api/class/include/class/class.hpp | 539 ---- src/api/class/include/class/class_cb.hpp | 115 - src/api/class/include/class/class_core.hpp | 772 ------ src/api/class/include/class/class_error.hpp | 40 - .../class/include/class/class_structures.hpp | 33 - src/api/class/src/.gitkeep | 0 src/api/class/src/class.cpp | 597 ---- src/api/class/src/class_core.cpp | 2415 ----------------- src/class_server/.gitkeep | 0 src/class_server/CMakeLists.txt | 15 - src/class_server/include/.gitkeep | 0 .../include/class_server/.gitkeep | 0 .../include/class_server/class_server.hpp | 178 -- src/class_server/src/.gitkeep | 0 src/class_server/src/class_client_example.cpp | 70 - src/class_server/src/class_server.cpp | 540 ---- src/class_server/src/class_server_main.cpp | 71 - src/cmspandoc.cmake | 105 - src/commands.h | 103 - src/commands.lib | Bin 190998 -> 0 bytes src/communication/.gitkeep | 0 src/communication/CMakeLists.txt | 43 - src/communication/include/.gitkeep | 0 .../include/communication/.gitkeep | 0 .../include/communication/async_serial.hpp | 267 -- .../include/communication/serial_listener.hpp | 5 - .../include/communication/udp_client.hpp | 121 - .../include/communication/udp_server.hpp | 100 - .../communication/udp_server_listener.hpp | 5 - src/communication/src/.gitkeep | 0 src/communication/src/async_serial.cpp | 395 --- src/communication/src/udp_client.cpp | 163 -- src/communication/src/udp_server.cpp | 111 - src/docs/.gitkeep | 0 src/docs/Doxyfile.in | 9 - src/docs/README.html | 1 - src/docs/README.md | 121 - src/docs/footer.html | 9 - src/docs/images/.gitkeep | 0 src/docs/images/sw_arch.png | Bin 56206 -> 0 bytes src/docs/images/tecnalia.jpg | Bin 28558 -> 0 bytes src/filter/.gitkeep | 0 src/filter/CMakeLists.txt | 37 - src/filter/include/.gitkeep | 0 src/filter/include/filter/.gitkeep | 0 src/filter/include/filter/butterworth.hpp | 42 - src/filter/src/.gitkeep | 0 src/filter/src/butterworth.cpp | 285 -- src/logger/.gitkeep | 0 src/logger/CMakeLists.txt | 38 - src/logger/include/.gitkeep | 0 src/logger/include/logger/.gitkeep | 0 src/logger/include/logger/logger.hpp | 50 - src/logger/src/.gitkeep | 0 src/logger/src/logger.cpp | 274 -- src/pandocology.cmake | 482 ---- src/protobuf/.gitkeep | 0 src/protobuf/class.proto | 22 - src/tests/.gitkeep | 0 src/tests/CMakeLists.txt | 5 - src/tests/test_cpp/.gitkeep | 0 src/tests/test_cpp/CMakeLists.txt | 20 - src/tests/test_cpp/CMakeLists_install.txt | 19 - src/tests/test_cpp/test_cpp_acq.bat | 3 - src/tests/test_cpp/test_cpp_acq.cpp | 100 - src/tests/test_cpp/test_cpp_callback.bat | 3 - src/tests/test_cpp/test_cpp_callback.cpp | 150 - src/tests/test_cpp/test_cpp_stim.bat | 3 - src/tests/test_cpp/test_cpp_stim.cpp | 66 - src/tests/test_wrappers/.gitkeep | 0 src/tests/test_wrappers/CMakeLists.txt | 8 - src/tests/test_wrappers/csharp/.gitkeep | 0 src/tests/test_wrappers/csharp/CMakeLists.txt | 113 - .../csharp/CMakeLists_install.txt | 43 - .../csharp/TestCSharpWrapperAcq.cs | 128 - .../csharp/TestCSharpWrapperCallback.cs | 86 - .../csharp/TestCSharpWrapperStim.cs | 66 - .../csharp/test_csharp_wrapper_acq.bat | 3 - .../csharp/test_csharp_wrapper_callback.bat | 3 - .../csharp/test_csharp_wrapper_stim.bat | 3 - src/tests/test_wrappers/python/.gitkeep | 0 src/tests/test_wrappers/python/CMakeLists.txt | 40 - .../python/test_python_wrapper_acq.bat | 7 - .../python/test_python_wrapper_acq.py | 68 - .../python/test_python_wrapper_acq.sh | 2 - .../python/test_python_wrapper_callback.bat | 7 - .../python/test_python_wrapper_callback.py | 57 - .../python/test_python_wrapper_callback.sh | 2 - .../python/test_python_wrapper_stim.bat | 7 - .../python/test_python_wrapper_stim.py | 54 - .../python/test_python_wrapper_stim.sh | 2 - src/wrappers/.gitkeep | 0 src/wrappers/CMakeLists.txt | 18 - src/wrappers/c_sharp/.gitkeep | 0 src/wrappers/c_sharp/CMakeLists.txt | 15 - src/wrappers/c_sharp/class.i | 27 - src/wrappers/python/.gitkeep | 0 src/wrappers/python/CMakeLists.txt | 21 - src/wrappers/python/class.i | 28 - src/yaml_tools/.gitkeep | 0 src/yaml_tools/CMakeLists.txt | 36 - src/yaml_tools/include/.gitkeep | 0 src/yaml_tools/include/yaml_tools/.gitkeep | 0 .../include/yaml_tools/yaml_tools.hpp | 38 - src/yaml_tools/src/.gitkeep | 0 src/yaml_tools/src/yaml_tools.cpp | 54 - 136 files changed, 2766 insertions(+), 10036 deletions(-) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 serial_port_class_api/CMakeLists.txt create mode 100644 serial_port_class_api/README.md create mode 100644 serial_port_class_api/commands.h create mode 100644 serial_port_class_api/commands.lib rename {src => serial_port_class_api}/docs/CMakeLists.txt (59%) create mode 100644 serial_port_class_api/docs/Doxyfile.in create mode 100644 serial_port_class_api/include/CLASS_SerialPort.h create mode 100644 serial_port_class_api/include/CLASS_structures.hpp create mode 100644 serial_port_class_api/libreria_LQL/CMakeLists.txt create mode 100644 serial_port_class_api/libreria_LQL/commands.cpp create mode 100644 serial_port_class_api/libreria_LQL/commands.h create mode 100644 serial_port_class_api/src/CLASS_SerialPort.cpp create mode 100644 serial_port_class_api/src/CMakeLists.txt create mode 100644 serial_port_class_api/src/main.cpp create mode 100644 serial_port_class_api/wrappers/CLASS_SerialPort.i delete mode 100644 src/.gitkeep delete mode 100644 src/CMakeLists.txt delete mode 100644 src/DownloadProject.CMakeLists.cmake.in delete mode 100644 src/DownloadProject.cmake delete mode 100644 src/README.md delete mode 100644 src/api/.gitkeep delete mode 100644 src/api/CMakeLists.txt delete mode 100644 src/api/class/.gitkeep delete mode 100644 src/api/class/CMakeLists.txt delete mode 100644 src/api/class/include/.gitkeep delete mode 100644 src/api/class/include/class/.gitkeep delete mode 100644 src/api/class/include/class/class.hpp delete mode 100644 src/api/class/include/class/class_cb.hpp delete mode 100644 src/api/class/include/class/class_core.hpp delete mode 100644 src/api/class/include/class/class_error.hpp delete mode 100644 src/api/class/include/class/class_structures.hpp delete mode 100644 src/api/class/src/.gitkeep delete mode 100644 src/api/class/src/class.cpp delete mode 100644 src/api/class/src/class_core.cpp delete mode 100644 src/class_server/.gitkeep delete mode 100644 src/class_server/CMakeLists.txt delete mode 100644 src/class_server/include/.gitkeep delete mode 100644 src/class_server/include/class_server/.gitkeep delete mode 100644 src/class_server/include/class_server/class_server.hpp delete mode 100644 src/class_server/src/.gitkeep delete mode 100644 src/class_server/src/class_client_example.cpp delete mode 100644 src/class_server/src/class_server.cpp delete mode 100644 src/class_server/src/class_server_main.cpp delete mode 100644 src/cmspandoc.cmake delete mode 100644 src/commands.h delete mode 100644 src/commands.lib delete mode 100644 src/communication/.gitkeep delete mode 100644 src/communication/CMakeLists.txt delete mode 100644 src/communication/include/.gitkeep delete mode 100644 src/communication/include/communication/.gitkeep delete mode 100644 src/communication/include/communication/async_serial.hpp delete mode 100644 src/communication/include/communication/serial_listener.hpp delete mode 100644 src/communication/include/communication/udp_client.hpp delete mode 100644 src/communication/include/communication/udp_server.hpp delete mode 100644 src/communication/include/communication/udp_server_listener.hpp delete mode 100644 src/communication/src/.gitkeep delete mode 100644 src/communication/src/async_serial.cpp delete mode 100644 src/communication/src/udp_client.cpp delete mode 100644 src/communication/src/udp_server.cpp delete mode 100644 src/docs/.gitkeep delete mode 100644 src/docs/Doxyfile.in delete mode 100644 src/docs/README.html delete mode 100644 src/docs/README.md delete mode 100644 src/docs/footer.html delete mode 100644 src/docs/images/.gitkeep delete mode 100644 src/docs/images/sw_arch.png delete mode 100644 src/docs/images/tecnalia.jpg delete mode 100644 src/filter/.gitkeep delete mode 100644 src/filter/CMakeLists.txt delete mode 100644 src/filter/include/.gitkeep delete mode 100644 src/filter/include/filter/.gitkeep delete mode 100644 src/filter/include/filter/butterworth.hpp delete mode 100644 src/filter/src/.gitkeep delete mode 100644 src/filter/src/butterworth.cpp delete mode 100644 src/logger/.gitkeep delete mode 100644 src/logger/CMakeLists.txt delete mode 100644 src/logger/include/.gitkeep delete mode 100644 src/logger/include/logger/.gitkeep delete mode 100644 src/logger/include/logger/logger.hpp delete mode 100644 src/logger/src/.gitkeep delete mode 100644 src/logger/src/logger.cpp delete mode 100644 src/pandocology.cmake delete mode 100644 src/protobuf/.gitkeep delete mode 100644 src/protobuf/class.proto delete mode 100644 src/tests/.gitkeep delete mode 100644 src/tests/CMakeLists.txt delete mode 100644 src/tests/test_cpp/.gitkeep delete mode 100644 src/tests/test_cpp/CMakeLists.txt delete mode 100644 src/tests/test_cpp/CMakeLists_install.txt delete mode 100644 src/tests/test_cpp/test_cpp_acq.bat delete mode 100644 src/tests/test_cpp/test_cpp_acq.cpp delete mode 100644 src/tests/test_cpp/test_cpp_callback.bat delete mode 100644 src/tests/test_cpp/test_cpp_callback.cpp delete mode 100644 src/tests/test_cpp/test_cpp_stim.bat delete mode 100644 src/tests/test_cpp/test_cpp_stim.cpp delete mode 100644 src/tests/test_wrappers/.gitkeep delete mode 100644 src/tests/test_wrappers/CMakeLists.txt delete mode 100644 src/tests/test_wrappers/csharp/.gitkeep delete mode 100644 src/tests/test_wrappers/csharp/CMakeLists.txt delete mode 100644 src/tests/test_wrappers/csharp/CMakeLists_install.txt delete mode 100644 src/tests/test_wrappers/csharp/TestCSharpWrapperAcq.cs delete mode 100644 src/tests/test_wrappers/csharp/TestCSharpWrapperCallback.cs delete mode 100644 src/tests/test_wrappers/csharp/TestCSharpWrapperStim.cs delete mode 100644 src/tests/test_wrappers/csharp/test_csharp_wrapper_acq.bat delete mode 100644 src/tests/test_wrappers/csharp/test_csharp_wrapper_callback.bat delete mode 100644 src/tests/test_wrappers/csharp/test_csharp_wrapper_stim.bat delete mode 100644 src/tests/test_wrappers/python/.gitkeep delete mode 100644 src/tests/test_wrappers/python/CMakeLists.txt delete mode 100644 src/tests/test_wrappers/python/test_python_wrapper_acq.bat delete mode 100644 src/tests/test_wrappers/python/test_python_wrapper_acq.py delete mode 100644 src/tests/test_wrappers/python/test_python_wrapper_acq.sh delete mode 100644 src/tests/test_wrappers/python/test_python_wrapper_callback.bat delete mode 100644 src/tests/test_wrappers/python/test_python_wrapper_callback.py delete mode 100644 src/tests/test_wrappers/python/test_python_wrapper_callback.sh delete mode 100644 src/tests/test_wrappers/python/test_python_wrapper_stim.bat delete mode 100644 src/tests/test_wrappers/python/test_python_wrapper_stim.py delete mode 100644 src/tests/test_wrappers/python/test_python_wrapper_stim.sh delete mode 100644 src/wrappers/.gitkeep delete mode 100644 src/wrappers/CMakeLists.txt delete mode 100644 src/wrappers/c_sharp/.gitkeep delete mode 100644 src/wrappers/c_sharp/CMakeLists.txt delete mode 100644 src/wrappers/c_sharp/class.i delete mode 100644 src/wrappers/python/.gitkeep delete mode 100644 src/wrappers/python/CMakeLists.txt delete mode 100644 src/wrappers/python/class.i delete mode 100644 src/yaml_tools/.gitkeep delete mode 100644 src/yaml_tools/CMakeLists.txt delete mode 100644 src/yaml_tools/include/.gitkeep delete mode 100644 src/yaml_tools/include/yaml_tools/.gitkeep delete mode 100644 src/yaml_tools/include/yaml_tools/yaml_tools.hpp delete mode 100644 src/yaml_tools/src/.gitkeep delete mode 100644 src/yaml_tools/src/yaml_tools.cpp diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af6381d --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.vscode/ +build/ + +bin/ +obj/ +Debug/ +Release/ + +*.suo \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..6a9360b --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,102 @@ +stages: + - build + - deploy + +before_script: + - echo "New job started" + +build_linux: + stage: build + script: + - if [ ! -d "build" ]; then + echo "build directory does not exist"; + mkdir build; + cd build; + cmake -DCMAKE_INSTALL_PREFIX=./class_api_install_linux ../src; + cd ..; + fi + - cd build + - make + cache: + paths: + - ./build + tags: + - linux + +build_windows_64: + stage: build + script: + # basic cmd shell is currently used + - echo %PATH% + # display the current directory location for debuggbing purposes + - cd + - dir + - if exist build_64 echo "Build_64 Folder already exists" + - if not exist build_64 ( + echo "Build_64 Folder does not exist" & + mkdir build_64 & + echo "Folder created" & + cd build_64 & + cmake -G "Visual Studio 15 2017 Win64" CMAKE_BUILD_TYPE=Release -DPYTHON_INCLUDE_DIR=C:\Users\class\AppData\Local\Programs\Python\Python37\include -DPYTHON_LIBRARY=C:\Users\class\AppData\Local\Programs\Python\Python37\libs\python37.lib -DCMAKE_INSTALL_PREFIX=./class_api_install_win ../src & + cd .. + ) + - cd build_64 + - cmake --build . --target ALL_BUILD --config Release + cache: + paths: + - ./build_64 + tags: + - windows + +deploy_windows_64: + stage: deploy + tags: + - windows + script: + # basic cmd shell is currently used + - echo %PATH% + # executing the install procedure + - cd build_64 + - cmake --build . --target INSTALL --config Release + # we assume here 7z.exe is in the PATH file + # - 7z a -tzip class_api_install_win.zip class_api_install_win + - dir + # write hash tag inside a file in the install + - echo "%CI_COMMIT_REF_NAME% %CI_COMMIT_SHA%" > ./class_api_install_win/hash.txt + cache: + paths: + - ./build_64 + artifacts: + paths: + - ./build_64/class_api_install_win + name: "class_api_win_%CI_COMMIT_REF_NAME%_%CI_COMMIT_SHA%" + only: + - /^release-.*$/ + - develop + - master + +deploy_linux_64: + stage: deploy + tags: + - linux + script: + # executing the install procedure + - pwd + - cd build + - make install + # write hash tag inside a file in the install + - echo "$CI_COMMIT_REF_NAME $CI_COMMIT_SHA" > ./class_api_install_linux/hash.txt + cache: + paths: + - ./build + artifacts: + paths: + - ./build/class_api_install_linux + name: "class_api_linux_$CI_COMMIT_REF_NAME_$CI_COMMIT_SHA" + only: + - /^release-.*$/ + - develop + - master + +after_script: + - echo "Job finished, well done" diff --git a/README.md b/README.md index 9147474..c64c891 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,45 @@ -This repo contains the source code of the API to communicate with a CLASS device. If you want to build the API you must install the same SW that is specified for the CI/CD. +This repo contains the source code to communicate with CLASS device through serial port. -# CI/CD +For building this project in both Windows and Linux the following tools must be installed: -A continuous integration process has been setup for building this project in both Windows and Linux. - -Requiriments of the machine in which the runner is installed: - Building tools -- cmake: required for building the project -- boost: the API depends on this header based library -- swig: for the generation of the wrappers/libraries for each of the languages programs we want to access the API from -- doxygen: for the generation of the doc for the API -- pandoc: converts README.md file to README.html +- cmake: for building the project +- swig: for the generation of wrappers +- doxygen: for the generation of the documentation - graphviz: required by doxygen ## Windows -- Build tools for Visual Studio (2017): Needed for compiling C++ stuff. There is no official link but [this](https://aka.ms/vs/15/release/vs_buildtools.exe) works. -- [boost](https://sourceforge.net/projects/boost/files/boost-binaries): download the correct binary according to your environment. +- [this](https://aka.ms/vs/15/release/vs_buildtools.exe) Build tools for Visual Studio (2017), needed for compiling C++. - [cmake](https://cmake.org/download/): Download .msi file and execute it. -- [pandoc](https://pandoc.org/installing.html): Download .msi file and execute it. - [swig](http://www.swig.org/survey.html): Download the .zip and unzip it. Include the folder into `path` environemnt variable. - [doxygen](https://www.doxygen.nl/download.html): Download .exe file and execute it. -- [python 3.7.9](https://www.python.org/downloads/release/python-379/): Download .msi file and execute it. ## Linux ``` sudo apt-get install build-essential sudo snap install cmake --classic -sudo apt-get install libboost-all-devlibrary sudo apt-get install swig sudo apt-get install doxygen sudo apt install graphviz -sudo apt-get install pandoc -sudo apt-get install python3.7 ``` -# Build proyect +# Build project +This code has been programmed in C++. Wrappers for C# are generated automatically by swig. +## C++ building with CMake +* Create a build folder and enter the folder +* Execute the following commands to configure the platform -Follow the steps of the official CMake link to build and configure the proyect if you need [cmake](https://cmake.org/cmake/help/v3.6/manual/cmake.1.html#build-tool-mode). If no custom configurations are need -Open Command Prompt (Windows) or Terminal (Linux) in the directory into where CMakeLists.txt is placed [src](./src) and run the following command + -Windows -cmake -B path-to-build + cmake -G "Visual Studio 15 2017 Win64" CMAKE_BUILD_TYPE=Release ../ + cmake --build . --target ALL_BUILD --config Release + + -Linux + + cmake ../ + make diff --git a/serial_port_class_api/CMakeLists.txt b/serial_port_class_api/CMakeLists.txt new file mode 100644 index 0000000..86142f4 --- /dev/null +++ b/serial_port_class_api/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.14) + +project(class_api) + +add_subdirectory(src) +add_subdirectory(libreria_LQL) + +find_package(SWIG REQUIRED) +include(${SWIG_USE_FILE}) + +set(NET_PROJECT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/wrappers/csharpwrapper") + +set(CMAKE_SWIG_FLAGS "") + +set_property(SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/wrappers/CLASS_SerialPort.i PROPERTY CPLUSPLUS ON) + +swig_add_library(CLASS_SerialPort + TYPE SHARED + LANGUAGE CSharp + SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/wrappers/CLASS_SerialPort.i + OUTPUT_DIR ${NET_PROJECT_DIR} + OUTFILE_DIR ${NET_PROJECT_DIR} + ) + swig_link_libraries(CLASS_SerialPort class_serial_port) + +set_target_properties(CLASS_SerialPort + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_RELEASE ${NET_PROJECT_DIR} + RUNTIME_OUTPUT_DIRECTORY_DEBUG ${NET_PROJECT_DIR} + LIBRARY_OUTPUT_DIRECTORY ${NET_PROJECT_DIR} + INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR} + ) + +option(BUILD_DOC "Build documentation" ON) +message(STATUS "Build documentation: ${BUILD_DOC}") +if (BUILD_DOC) + add_subdirectory(docs) +endif() \ No newline at end of file diff --git a/serial_port_class_api/README.md b/serial_port_class_api/README.md new file mode 100644 index 0000000..a3e4b9d --- /dev/null +++ b/serial_port_class_api/README.md @@ -0,0 +1,7 @@ +# Project structure +Project is structured with the following folders: + * docs : documentation is automatically produced from comments by doxygen once a built is done, it will be found in docs/documentation/index.html. + * include : it contains header files for the API. + * src : contains API functions in CLASS_SerialPort.cpp and main.cpp file as example of how to use those funtions. + * wrappers : wrappers are automatically produced by swig. It generates files to be used in C# scripts. + diff --git a/serial_port_class_api/commands.h b/serial_port_class_api/commands.h new file mode 100644 index 0000000..50328fc --- /dev/null +++ b/serial_port_class_api/commands.h @@ -0,0 +1,139 @@ +/** + * @file commands.h + * @author Leire Querejeta Lomas <leire.querejeta@tecnalia.com> + * @brief headers for CLASS commands + * @date 2022-10-10 + * + * @copyright Copyright 2020 + * Tecnalia Research & Innovation. + * Distributed under the GNU GPL v3. + * For full terms see https://www.gnu.org/licenses/gpl.txt + */ +#pragma once +#include <string> + +namespace commands +{ + class ClassCommands + { + public: + + static std::string ClassCommands::CMD_ONSTIM; + static std::string ClassCommands::CMD_OFFSTIM; + static std::string ClassCommands::CMD_VELECSTIM; + + static std::string ClassCommands::CMD_GETTIC; + + static std::string ClassCommands::CMD_CONFIGVELEC; + static std::string ClassCommands::CMD_CONFIGVELEC_NAME; + static std::string ClassCommands::CMD_CONFIGVELEC_PADS; + static std::string ClassCommands::CMD_CONFIGVELEC_SELECTION; + static std::string ClassCommands::CMD_CONFIGVELEC_SYNCHRONOUS; + static std::string ClassCommands::CMD_CONFIGVELEC_CATHODE; + static std::string ClassCommands::CMD_CONFIGVELEC_ANODE; + static std::string ClassCommands::CMD_CONFIGVELEC_AMPLITUDE; + static std::string ClassCommands::CMD_CONFIGVELEC_PULSEWIDTH; + + static std::string ClassCommands::CMD_CONFIGPATTERN; + static std::string ClassCommands::CMD_CONFIGPATTERN_PARAM1; + static std::string ClassCommands::CMD_CONFIGPATTERN_PARAM2; + static std::string ClassCommands::CMD_CONFIGPATTERN_PARAM3; + + static std::string ClassCommands::CMD_GETHV; + static std::string ClassCommands::CMD_ONHV; + static std::string ClassCommands::CMD_OFFHV; + static std::string ClassCommands::CMD_SETHV; + + static std::string ClassCommands::CMD_SD_OPEN_DIRECTORY; + static std::string ClassCommands::CMD_SD_CHANGE_DIRECTORY; + static std::string ClassCommands::CMD_SD_PRINT_DIRECTORY; + static std::string ClassCommands::CMD_SD_DELETE_FILE; + static std::string ClassCommands::CMD_SD_CREATE_FILE; + static std::string ClassCommands::CMD_SD_EDIT_FILE; + static std::string ClassCommands::CMD_SD_CREATE_DIRECTORY; + static std::string ClassCommands::CMD_SD_RENAME_FILE; + static std::string ClassCommands::CMD_SD_SAVE_BOOT; + + static std::string ClassCommands::CMD_GETPROTOTYPE; + static std::string ClassCommands::CMD_SET_FES_PROTOTYPE; + static std::string ClassCommands::CMD_SET_TACTILITY_PROTOTYPE; + + static std::string ClassCommands::CMD_GETDEVICENAME; + static std::string ClassCommands::CMD_SETDEVICENAME; + + static std::string ClassCommands::CMD_GETFW; + + static std::string ClassCommands::CMD_GETHW; + + static std::string ClassCommands::CMD_GETSDFUNCTION; + static std::string ClassCommands::CMD_SETSDFUNCTION; + + static std::string ClassCommands::CMD_GETFRQUENCY; + static std::string ClassCommands::CMD_SETFREQ; + + static std::string ClassCommands::CMD_GETLOGEVENTS; + static std::string ClassCommands::CMD_ONLOGEVENTS; + static std::string ClassCommands::CMD_OFFLOGEVENTS; + + static std::string ClassCommands::CMD_GETSDNAME; + static std::string ClassCommands::CMD_SETSDNAME; + + static std::string ClassCommands::CMD_GETINTERVAL; + static std::string ClassCommands::CMD_SETINTERVAL; + + static std::string ClassCommands::CMD_GETBATTERY; + + static std::string ClassCommands::CMD_GETPULSE_VOLTAGES; + static std::string ClassCommands::CMD_GETPULSE_INTENSITIES; + static std::string ClassCommands::CMD_GETPULSE_NEGATIVEVOLTAGES; + static std::string ClassCommands::CMD_GETPULSE_POSITIVEVOLTAGES; + static std::string ClassCommands::CMD_GETPULSE_AVERAGEINTENSITY; + + static std::string ClassCommands::CMD_ONACQ; + static std::string ClassCommands::CMD_OFFACQ; + static std::string ClassCommands::CMD_NORMALACQ; + static std::string ClassCommands::CMD_TESTACQ; + static std::string ClassCommands::CMD_STREAMONACQ; + static std::string ClassCommands::CMD_STREAMOFFACQ; + static std::string ClassCommands::CMD_ONIMPEDANCEACQ; + static std::string ClassCommands::CMD_OFFIMPEDANCEACQ; + static std::string ClassCommands::CMD_CONFIGACQ; + static std::string ClassCommands::CMD_CONFIGACQ_CHANNELS; + static std::string ClassCommands::CMD_CONFIGACQ_FREQUENCY; + static std::string ClassCommands::CMD_CONFIGACQ_GAIN; + static std::string ClassCommands::CMD_CONFIGACQ_INPUT; + static std::string ClassCommands::CMD_CONFIGACQ_TYPE; + static std::string ClassCommands::CMD_CONFIGACQ_IMPEDANCEPOSITIVE; + static std::string ClassCommands::CMD_CONFIGACQ_IMPEDANCENEGATIVE; + static std::string ClassCommands::CMD_CONFIGACQ_LEADOFF_ON; + static std::string ClassCommands::CMD_CONFIGACQ_LEADOFF_OFF; + static std::string ClassCommands::CMD_CONFIGACQ_SETLEADOFF; + + static std::string ClassCommands::CMD_GETSTIMTIME; + + static std::string ClassCommands::CMD_GETBUZZER; + static std::string ClassCommands::CMD_SETBUZZER; + static std::string ClassCommands::CMD_PLAYBUZZER; + + static std::string ClassCommands::CMD_STARTFES; + static std::string ClassCommands::CMD_STARTTACTILITY; + static std::string ClassCommands::CMD_STARTCOMMUNICATION; + + static std::string ClassCommands::CMD_GETLOGERRORS; + static std::string ClassCommands::CMD_OPENLOGERRORS; + static std::string ClassCommands::CMD_CLOSELOGERRORS; + + static std::string ClassCommands::CMD_SWITCHOFF; + + static std::string ClassCommands::CMD_GETRTC; + static std::string ClassCommands::CMD_SETRTCDATE; + static std::string ClassCommands::CMD_SETRTCTIME; + + static std::string ClassCommands::CMD_PING; + static std::string ClassCommands::CMD_HELP; + + static std::string ClassCommands::CMD_AUX; + static std::string ClassCommands::CMD_AUX2; + + }; +} \ No newline at end of file diff --git a/serial_port_class_api/commands.lib b/serial_port_class_api/commands.lib new file mode 100644 index 0000000000000000000000000000000000000000..a6758c2e9955a79315c30a56aab910468d86289e GIT binary patch literal 230986 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 B#}ohn literal 0 HcmV?d00001 diff --git a/src/docs/CMakeLists.txt b/serial_port_class_api/docs/CMakeLists.txt similarity index 59% rename from src/docs/CMakeLists.txt rename to serial_port_class_api/docs/CMakeLists.txt index b728f44..db1964b 100644 --- a/src/docs/CMakeLists.txt +++ b/serial_port_class_api/docs/CMakeLists.txt @@ -1,19 +1,15 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 3.14) -project (doc) +cmake_minimum_required(VERSION 3.14) +project (documentation) -# check if Doxygen is installed find_package(Doxygen) if (DOXYGEN_FOUND) - # set input and output files set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) - # request to configure the file configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY) message("Doxygen build started") - # note the option ALL which allows to build the docs together with the application add_custom_target( doc_doxygen ALL COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} @@ -23,6 +19,4 @@ else (DOXYGEN_FOUND) message("Doxygen need to be installed to generate the doxygen documentation") endif (DOXYGEN_FOUND) -install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc_doxygen/html/ DESTINATION class_api/docs) -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/README.html DESTINATION class_api) diff --git a/serial_port_class_api/docs/Doxyfile.in b/serial_port_class_api/docs/Doxyfile.in new file mode 100644 index 0000000..c1f33e4 --- /dev/null +++ b/serial_port_class_api/docs/Doxyfile.in @@ -0,0 +1,6 @@ + +OUTPUT_DIRECTORY = @CMAKE_CURRENT_SOURCE_DIR@/documentation/ +INPUT = @CMAKE_CURRENT_SOURCE_DIR@/../include/CLASS_SerialPort.h +INPUT += @CMAKE_CURRENT_SOURCE_DIR@/../src/CLASS_SerialPort.cpp +GENERATE_LATEX = NO +PROJECT_NAME = "CLASS API" diff --git a/serial_port_class_api/include/CLASS_SerialPort.h b/serial_port_class_api/include/CLASS_SerialPort.h new file mode 100644 index 0000000..62425f5 --- /dev/null +++ b/serial_port_class_api/include/CLASS_SerialPort.h @@ -0,0 +1,193 @@ +/** + * @file CLASS_SerialPort.h + * @author Leire Querejeta Lomas <leire.querejeta@tecnalia.com> + * @brief headers for serial connection and CLASS commands + * @date 2022-10-10 + * + * @copyright Copyright 2020 + * Tecnalia Research & Innovation. + * Distributed under the GNU GPL v3. + * For full terms see https://www.gnu.org/licenses/gpl.txt + */ + +#ifndef __CLASS_SerialPort_H__ +#define __CLASS_SerialPort_H__ + +#include <string> +#include <windows.h> +#include <vector> +#include "CLASS_structures.hpp" + +#define BUFFER_SIZE 2000 // read maximum buffer size +#define FLUSH_BUFFSIZE 10 // flush buffer size + +class CLASS_SerialPort // CLASS serial port control +{ + +private: + HANDLE hCom; // handle to the Serial port + bool status; // setopen result + DCB dcb; // control settings for serial port + COMMTIMEOUTS timeouts; // time-out parameters for a communication device + + std::string devicePrototype; // type of prototype : FES, TACTILITY, PAIN ... + + std::string ERROR_NOT_VALID_VALUE = "ERROR out of range input value"; + int MAX_ALLOWED_VIRTUAL_ELECTRODES = 16; // allowed number of virtual electrodes + int MAX_CHAR_VIRTUAL_ELECTRODE_NAME = 16; // maximun number of characters for virtual electrode name + int MAX_VIRTUAL_ELECTRODE_PADS = 48; // allowed maximum number of pads per virtual electrode + double MAX_VIRTUAL_ELECTRODE_AMPLITUDE_FES = 92; // allowed maximum stimulation amplitude value for FES prototypes + double MAX_VIRTUAL_ELECTRODE_AMPLITUDE_TACTILITY = 9; // allowed maximum stimulation amplitude value for TACTILITY/PAIN prototypes + double MIN_VIRTUAL_ELECTRODE_AMPLITUDE = 0.1; // allowed minimum stimulation amplitude value + int MAX_VIRTUAL_ELECTRODE_PULSE_WIDTH = 4000; // allowed maximum stimulation pulse width + int MIN_VIRTUAL_ELECTRODE_PULSE_WIDTH = 30; // allowed minimum stimulation pulse width + int MAX_ALLOWED_PATTERNS = 16; // allowed number of patterns + int MAX_HIGH_VOLTAGE = 200; // allowed maximum high voltage value + int MIN_HIGH_VOLTAGE = 120; // allowed minimum high voltage value + int MAX_CHAR_DEVICENAME = 25; // maximun number of characters for device name + int MAX_CHAR_FUNCTIONNAME = 15; // maximun number of characters for sd pattern folder name + int MAX_FREQUENCY = 2000; // allowed maximum frequency value + int MIN_FREQUENCY = 0; // allowed minimum frequency value + int MAX_PULSE_INTERVAL = 1000; // allowed maximum inter pulses interval + int MIN_PULSE_INTERVAL = 250; // allowed minimum inter pulses interval + int MAX_CHAR_USERNAME = 15; // maximun number of characters for sd user folder + int ACQUISITION_ALLOWED_FREQUENCY1 = 250; // allowed acquisition frequency value + int ACQUISITION_ALLOWED_FREQUENCY2 = 500; // allowed acquisition frequency value + int ACQUISITION_ALLOWED_FREQUENCY3 = 1000; // allowed acquisition frequency value + int ACQUISITION_ALLOWED_FREQUENCY4 = 2000; // allowed acquisition frequency value + int ACQUISITION_ALLOWED_FREQUENCY5 = 4000; // allowed acquisition frequency value + std::string ACQUISITION_ALLOWED_TYPE1 = "bipolar"; // allowed acquisition frequency value + std::string ACQUISITION_ALLOWED_TYPE2 = "unipolar"; // allowed acquisition frequency value + int ACQUISITION_ALLOWED_GAIN1 = 1; // allowed acquisition gain value + int ACQUISITION_ALLOWED_GAIN2 = 2; // allowed acquisition gain value + int ACQUISITION_ALLOWED_GAIN3 = 4; // allowed acquisition gain value + int ACQUISITION_ALLOWED_GAIN4 = 8; // allowed acquisition gain value + int ACQUISITION_ALLOWED_GAIN5 = 12; // allowed acquisition gain value + int ACQUISITION_ALLOWED_GAIN6 = 24; // allowed acquisition gain value + std::string ACQUISITION_ALLOWED_CURRENT1 = "6nA"; // allowed acquisition leadoff current value + std::string ACQUISITION_ALLOWED_CURRENT2 = "24nA"; // allowed acquisition leadoff current value + std::string ACQUISITION_ALLOWED_CURRENT3 = "6uA"; // allowed acquisition leadoff current value + std::string ACQUISITION_ALLOWED_CURRENT4 = "24uA"; // allowed acquisition leadoff current value + int BUZZER_MAX_TEMPO_ms = 500; // allowed maximum number of miliseconds for buzzer duration + int BUZZER_MIN_TEMPO_ms = 25; // allowed minimum number of miliseconds for buzzer duration + +public: + + CLASS_SerialPort(); //constructor + ~CLASS_SerialPort(); //constructor + bool SetOpenPort(std::string &commPortName); //serial port settings + bool write(const char buffer[]); //write + void flush(); //flush serial port read buffer + int flushread(char *buffer, int buffLen); //auxiliar function for flush serial port + std::string read(); //read + std::string communication(const char buffer[]); //write/read + void CloseSerialPort(); //serial port close + + + std::string setStimulationOn(); + std::string setStimulationOff(); + std::string setStimulationVirtualElectrode(std::string &virtualElectrodeName); + + std::string getTic(); + + std::string getVirtualElectrode(std::string &virtualElectrodeNumber); + std::string setVirtualElectrodeOld(std::string &virtualElectrodeNumber, std::string &virtualElectrodeName, std::vector<int> cathodes, std::vector<int> anodes , std::vector<double> amplitudes, std::vector<int> pulsewiths, bool selected , bool sync); + std::string setVirtualElectrode(std::string &virtualElectrodeNumber, std::string virtualElectrodeName = "", std::vector<int> cathodes = {0}, std::vector<int> anodes = {0}, std::vector<CathodeAmplitude> amplitudes = {{"0",0.0}}, std::vector<CathodeWidth> pulsewiths = {{"0",0}}, std::string selected = "", std::string sync = ""); + std::string setVirtualElectrodeSelection(std::string &virtualElectrodeNumber, bool selection); + + std::string getPattern(std::string &patternNumber); + std::string clearPattern(std::string &patternNumber); + std::string setPattern(std::string &patternNumber,std::vector<Pattern> &patternsValues); + + std::string getHighVoltage(); + std::string setHighVoltageOn(); + std::string setHighVoltageOff(); + std::string setHighVoltageValue(std::string &highVoltageValue); + + std::string SDls(); + std::string SDcd(std::string &directoryName); + std::string SDpwd(); + std::string SDrm(std::string &fileName); + std::string SDcat(std::string &fileName); + std::string SDed(std::string &filePathName, std::string &text); + std::string SDmkdir(std::string &directoryName); + std::string SDrename(std::string &oldFileName, std::string &newFileName); + std::string SDsaveboot(); + + std::string getApplication(); + std::string setFESApplication(); + std::string setTACTILITYApplication(); + + std::string getDeviceName(); + std::string setDeviceName(std::string &deviceName); + + std::string getFirmware(); + + std::string getHardware(); + + std::string getSDPartternsFolder(); + std::string setSDPartternsFolder(std::string &sdPattern); + + std::string getFrequency(); + std::string setFrequency(std::string &frequency); + + std::string getPulsesInterval(); + std::string setPulsesInterval(std::string &interval); + + std::string getLogevents(); + std::string setLogeventsOn(); + std::string setLogeventsOff(); + + std::string getSDUserFolder(); + std::string setSDUserFolder(std::string &sdFolder); + + std::string getBattery(); + + std::string getPulseVoltages(); + std::string getPulseIntensities(); + std::string getPulseNegativeVoltages(); + std::string getPulsePositiveVoltages(); + std::string getPulseAverageIntensity(); + + std::string setAcquisitionOn(); + std::string setAcquisitionOff(); + std::string setAcquisitionConfiguratonOld(std::vector<int> &channels, int frequency, std::string type, int gain); + std::string setAcquisitionConfiguraton(std::vector<int> &channels, int frequency = 0, std::string type = "", int gain = 0); + std::string setAcquisitionTest(); + std::string setAcquisitionNormal(); + std::string setAcquisitionStreamOn(); + std::string setAcquisitionStreamOff(); + std::string setAcquisitionImpedanceOn(); + std::string setAcquisitionImpedanceOff(); + std::string setAcquisitionImpedanceChannelsPositive(); + std::string setAcquisitionImpedanceChannelsNegative(); + std::string setAcquisitionLeadoffOn(); + std::string setAcquisitionLeadoffOff(); + std::string setAcquisitionLeadoffConfiguration(std::string ¤t); + + std::string getStimulationTime(); + + std::string getBuzzerTempo(); + std::string setBuzzerTempo(std::string &buzzerTempo); + std::string playBuzzer(); + + std::string startFESCommunication(); + std::string startTACTILITYCommunication(); + std::string startCommunication(std::string &prototipeName); + + std::string showLogErrors(); + std::string createLogFile(); + std::string closeLogFile(); + + std::string shutDown(); + + std::string getRTC(); + std::string setRTCDate(std::string &rtcDate); + std::string setRTCTime(std::string &rtcTime); + + std::string ping(); + + std::string getHelp(); +}; + +#endif \ No newline at end of file diff --git a/serial_port_class_api/include/CLASS_structures.hpp b/serial_port_class_api/include/CLASS_structures.hpp new file mode 100644 index 0000000..fd15c7d --- /dev/null +++ b/serial_port_class_api/include/CLASS_structures.hpp @@ -0,0 +1,38 @@ +/** + * @file CLASS_structures.h + * @author Leire Querejeta Lomas <leire.querejeta@tecnalia.com> + * @brief structures needed for API + * @date 2022-10-10 + * + * @copyright Copyright 2020 + * Tecnalia Research & Innovation. + * Distributed under the GNU GPL v3. + * For full terms see https://www.gnu.org/licenses/gpl.txt + */ +#pragma once +#include <vector> + +//! Structure for pattern +struct Pattern +{ + std::string amp; + std::string pw; + std::string r; + int ampval; + int pwval; + int time; +}; + +//! Structure for virtual eletrode cathode amplitude configuration +struct CathodeAmplitude +{ + std::string cathodenumber; + double amplitude; +}; + +//! Structure for virtual eletrode cathode width configuration +struct CathodeWidth +{ + std::string cathodenumber; + int width; +}; \ No newline at end of file diff --git a/serial_port_class_api/libreria_LQL/CMakeLists.txt b/serial_port_class_api/libreria_LQL/CMakeLists.txt new file mode 100644 index 0000000..c003702 --- /dev/null +++ b/serial_port_class_api/libreria_LQL/CMakeLists.txt @@ -0,0 +1,8 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +project (api) + +#set(commands_files +# ${CMAKE_CURRENT_SOURCE_DIR}/commands.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/commands.h +#) +#add_library(commands STATIC ${commands_files}) \ No newline at end of file diff --git a/serial_port_class_api/libreria_LQL/commands.cpp b/serial_port_class_api/libreria_LQL/commands.cpp new file mode 100644 index 0000000..3864457 --- /dev/null +++ b/serial_port_class_api/libreria_LQL/commands.cpp @@ -0,0 +1,134 @@ +/** + * @file commands.cpp + * @author Leire Querejeta Lomas <leire.querejeta@tecnalia.com> + * @brief CLASS commands DANGER this file should be hide for Open API + * @date 2022-10-10 + * + * @copyright Copyright 2020 + * Tecnalia Research & Innovation. + * Distributed under the GNU GPL v3. + * For full terms see https://www.gnu.org/licenses/gpl.txt + */ +#include "commands.h" +#define getName(var) #var + +namespace commands +{ + std::string ClassCommands::CMD_ONSTIM = "stim on\r\n"; + std::string ClassCommands::CMD_OFFSTIM = "stim off\r\n"; + std::string ClassCommands::CMD_VELECSTIM = "stim "; + + std::string ClassCommands::CMD_GETTIC = "tic\r\n"; + + std::string ClassCommands::CMD_CONFIGVELEC = "velec "; + std::string ClassCommands::CMD_CONFIGVELEC_NAME = " *name "; + std::string ClassCommands::CMD_CONFIGVELEC_PADS = " *pads "; + std::string ClassCommands::CMD_CONFIGVELEC_SELECTION = " *selected "; + std::string ClassCommands::CMD_CONFIGVELEC_SYNCHRONOUS = " *sync "; + std::string ClassCommands::CMD_CONFIGVELEC_CATHODE = "=C,"; + std::string ClassCommands::CMD_CONFIGVELEC_ANODE = "=A,"; + std::string ClassCommands::CMD_CONFIGVELEC_AMPLITUDE = " *amp "; + std::string ClassCommands::CMD_CONFIGVELEC_PULSEWIDTH = " *width "; + + std::string ClassCommands::CMD_CONFIGPATTERN = "pattern "; + std::string ClassCommands::CMD_CONFIGPATTERN_PARAM1 = " clear"; + std::string ClassCommands::CMD_CONFIGPATTERN_PARAM2 = " *newline "; + std::string ClassCommands::CMD_CONFIGPATTERN_PARAM3 = " register"; + + std::string ClassCommands::CMD_GETHV = "hv ?\r\n"; + std::string ClassCommands::CMD_ONHV = "hv on\r\n"; + std::string ClassCommands::CMD_OFFHV = "hv off\r\n"; + std::string ClassCommands::CMD_SETHV = "hv "; + + std::string ClassCommands::CMD_SD_OPEN_DIRECTORY = "sdcard ls\r\n"; + std::string ClassCommands::CMD_SD_CHANGE_DIRECTORY = "sdcard cd "; + std::string ClassCommands::CMD_SD_PRINT_DIRECTORY = "sdcard pwd\r\n"; + std::string ClassCommands::CMD_SD_DELETE_FILE = "sdcard rm "; + std::string ClassCommands::CMD_SD_CREATE_FILE = "sdcard cat > "; + std::string ClassCommands::CMD_SD_EDIT_FILE = "sdcard ed "; + std::string ClassCommands::CMD_SD_CREATE_DIRECTORY = "sdcard mkdir "; + std::string ClassCommands::CMD_SD_RENAME_FILE = "sdcard rename "; + std::string ClassCommands::CMD_SD_SAVE_BOOT = "sdcard save boot\r\n"; + + std::string ClassCommands::CMD_GETPROTOTYPE = "application ?\r\n"; + std::string ClassCommands::CMD_SET_FES_PROTOTYPE = "application FES\r\n"; + std::string ClassCommands::CMD_SET_TACTILITY_PROTOTYPE = "application TACTILITY\r\n"; + + std::string ClassCommands::CMD_GETDEVICENAME = "device ?\r\n"; + std::string ClassCommands::CMD_SETDEVICENAME = "device "; + + std::string ClassCommands::CMD_GETFW = "firmware ?\r\n"; + + std::string ClassCommands::CMD_GETHW = "hardware ?\r\n"; + + std::string ClassCommands::CMD_GETSDFUNCTION = "function ?\r\n"; + std::string ClassCommands::CMD_SETSDFUNCTION = "function "; + + + std::string ClassCommands::CMD_GETFRQUENCY = "frequency ?\r\n"; + std::string ClassCommands::CMD_SETFREQ = "frequency "; + + std::string ClassCommands::CMD_GETLOGEVENTS = "logevents ?\r\n"; + std::string ClassCommands::CMD_ONLOGEVENTS = "logevents on\r\n"; + std::string ClassCommands::CMD_OFFLOGEVENTS = "logevents off\r\n"; + + std::string ClassCommands::CMD_GETSDNAME = "uname ?\r\n"; + std::string ClassCommands::CMD_SETSDNAME = "uname "; + + std::string ClassCommands::CMD_GETINTERVAL = "interval ?\r\n"; + std::string ClassCommands::CMD_SETINTERVAL = "interval "; + + std::string ClassCommands::CMD_GETBATTERY = "battery ?\r\n"; + + std::string ClassCommands::CMD_GETPULSE_VOLTAGES = "pulse *vpulse ?\r\n"; + std::string ClassCommands::CMD_GETPULSE_INTENSITIES = "pulse *ipulse ?\r\n"; + std::string ClassCommands::CMD_GETPULSE_NEGATIVEVOLTAGES = "pulse *lowside ?\r\n"; + std::string ClassCommands::CMD_GETPULSE_POSITIVEVOLTAGES = "pulse *highside ?\r\n"; + std::string ClassCommands::CMD_GETPULSE_AVERAGEINTENSITY = "pulse *iavg ?\r\n"; + + std::string ClassCommands::CMD_ONACQ = "acq on\r\n"; + std::string ClassCommands::CMD_OFFACQ = "acq off\r\n"; + std::string ClassCommands::CMD_NORMALACQ = "acq config *input normal\r\n"; + std::string ClassCommands::CMD_TESTACQ = "acq config *input test\r\n"; + std::string ClassCommands::CMD_STREAMONACQ = "acq stream on\r\n"; + std::string ClassCommands::CMD_STREAMOFFACQ = "acq stream off\r\n"; + std::string ClassCommands::CMD_ONIMPEDANCEACQ = "acq impedance on\r\n"; + std::string ClassCommands::CMD_OFFIMPEDANCEACQ = "acq impedance off\r\n"; + std::string ClassCommands::CMD_CONFIGACQ = "acq config"; + std::string ClassCommands::CMD_CONFIGACQ_CHANNELS = " *channels "; + std::string ClassCommands::CMD_CONFIGACQ_FREQUENCY = " *freq "; + std::string ClassCommands::CMD_CONFIGACQ_GAIN = " *gain "; + std::string ClassCommands::CMD_CONFIGACQ_INPUT = " *input "; + std::string ClassCommands::CMD_CONFIGACQ_TYPE = " *type "; + std::string ClassCommands::CMD_CONFIGACQ_IMPEDANCEPOSITIVE = "acq impedance_config *channels positives\r\n"; + std::string ClassCommands::CMD_CONFIGACQ_IMPEDANCENEGATIVE = "acq impedance_config *channels negatives\r\n"; + std::string ClassCommands::CMD_CONFIGACQ_LEADOFF_ON = "acq leadoff on\r\n"; + std::string ClassCommands::CMD_CONFIGACQ_LEADOFF_OFF = "acq leadoff off\r\n"; + std::string ClassCommands::CMD_CONFIGACQ_SETLEADOFF = "acq leadoff_config *current "; + + std::string ClassCommands::CMD_GETSTIMTIME = "time ?\r\n"; + + std::string ClassCommands::CMD_GETBUZZER = "buzzer *tempo ?\r\n"; + std::string ClassCommands::CMD_SETBUZZER = "buzzer *tempo "; + std::string ClassCommands::CMD_PLAYBUZZER = "buzzer *play \r\n"; + + std::string ClassCommands::CMD_STARTFES = "iam DESKTOP\r\n"; + std::string ClassCommands::CMD_STARTTACTILITY = "iam TACTILITY\r\n"; + std::string ClassCommands::CMD_STARTCOMMUNICATION = "iam "; + + std::string ClassCommands::CMD_GETLOGERRORS = "log errors ?\r\n"; + std::string ClassCommands::CMD_OPENLOGERRORS = "log open\r\n"; + std::string ClassCommands::CMD_CLOSELOGERRORS = "log close\r\n"; + + std::string ClassCommands::CMD_SWITCHOFF = "shutdown\r\n"; + + std::string ClassCommands::CMD_GETRTC = "rtc ?\r\n"; + std::string ClassCommands::CMD_SETRTCDATE = "rtc *date "; + std::string ClassCommands::CMD_SETRTCTIME = "rtc *time "; + + std::string ClassCommands::CMD_PING = "ping\r\n"; + std::string ClassCommands::CMD_HELP = "help\r\n"; + + std::string ClassCommands::CMD_AUX = " \r\n"; + std::string ClassCommands::CMD_AUX2 = " ?"; +} \ No newline at end of file diff --git a/serial_port_class_api/libreria_LQL/commands.h b/serial_port_class_api/libreria_LQL/commands.h new file mode 100644 index 0000000..0bdaaf5 --- /dev/null +++ b/serial_port_class_api/libreria_LQL/commands.h @@ -0,0 +1,138 @@ +/** + * @file commands.h + * @author Leire Querejeta Lomas <leire.querejeta@tecnalia.com> + * @brief headers for CLASS commands + * @date 2022-10-10 + * + * @copyright Copyright 2020 + * Tecnalia Research & Innovation. + * Distributed under the GNU GPL v3. + * For full terms see https://www.gnu.org/licenses/gpl.txt + */ +#pragma once +#include <string> + +namespace commands +{ + class ClassCommands + { + public: + + static std::string ClassCommands::CMD_ONSTIM; + static std::string ClassCommands::CMD_OFFSTIM; + static std::string ClassCommands::CMD_VELECSTIM; + + static std::string ClassCommands::CMD_GETTIC; + + static std::string ClassCommands::CMD_CONFIGVELEC; + static std::string ClassCommands::CMD_CONFIGVELEC_NAME; + static std::string ClassCommands::CMD_CONFIGVELEC_PADS; + static std::string ClassCommands::CMD_CONFIGVELEC_SELECTION; + static std::string ClassCommands::CMD_CONFIGVELEC_SYNCHRONOUS; + static std::string ClassCommands::CMD_CONFIGVELEC_CATHODE; + static std::string ClassCommands::CMD_CONFIGVELEC_ANODE; + static std::string ClassCommands::CMD_CONFIGVELEC_AMPLITUDE; + static std::string ClassCommands::CMD_CONFIGVELEC_PULSEWIDTH; + + static std::string ClassCommands::CMD_CONFIGPATTERN; + static std::string ClassCommands::CMD_CONFIGPATTERN_PARAM1; + static std::string ClassCommands::CMD_CONFIGPATTERN_PARAM2; + static std::string ClassCommands::CMD_CONFIGPATTERN_PARAM3; + + static std::string ClassCommands::CMD_GETHV; + static std::string ClassCommands::CMD_ONHV; + static std::string ClassCommands::CMD_OFFHV; + static std::string ClassCommands::CMD_SETHV; + + static std::string ClassCommands::CMD_SD_OPEN_DIRECTORY; + static std::string ClassCommands::CMD_SD_CHANGE_DIRECTORY; + static std::string ClassCommands::CMD_SD_PRINT_DIRECTORY; + static std::string ClassCommands::CMD_SD_DELETE_FILE; + static std::string ClassCommands::CMD_SD_CREATE_FILE; + static std::string ClassCommands::CMD_SD_EDIT_FILE; + static std::string ClassCommands::CMD_SD_CREATE_DIRECTORY; + static std::string ClassCommands::CMD_SD_RENAME_FILE; + static std::string ClassCommands::CMD_SD_SAVE_BOOT; + + static std::string ClassCommands::CMD_GETPROTOTYPE; + static std::string ClassCommands::CMD_SET_FES_PROTOTYPE; + static std::string ClassCommands::CMD_SET_TACTILITY_PROTOTYPE; + + static std::string ClassCommands::CMD_GETDEVICENAME; + static std::string ClassCommands::CMD_SETDEVICENAME; + + static std::string ClassCommands::CMD_GETFW; + + static std::string ClassCommands::CMD_GETHW; + + static std::string ClassCommands::CMD_GETSDFUNCTION; + static std::string ClassCommands::CMD_SETSDFUNCTION; + + static std::string ClassCommands::CMD_GETFRQUENCY; + static std::string ClassCommands::CMD_SETFREQ; + + static std::string ClassCommands::CMD_GETLOGEVENTS; + static std::string ClassCommands::CMD_ONLOGEVENTS; + static std::string ClassCommands::CMD_OFFLOGEVENTS; + + static std::string ClassCommands::CMD_GETSDNAME; + static std::string ClassCommands::CMD_SETSDNAME; + + static std::string ClassCommands::CMD_GETINTERVAL; + static std::string ClassCommands::CMD_SETINTERVAL; + + static std::string ClassCommands::CMD_GETBATTERY; + + static std::string ClassCommands::CMD_GETPULSE_VOLTAGES; + static std::string ClassCommands::CMD_GETPULSE_INTENSITIES; + static std::string ClassCommands::CMD_GETPULSE_NEGATIVEVOLTAGES; + static std::string ClassCommands::CMD_GETPULSE_POSITIVEVOLTAGES; + static std::string ClassCommands::CMD_GETPULSE_AVERAGEINTENSITY; + + static std::string ClassCommands::CMD_ONACQ; + static std::string ClassCommands::CMD_OFFACQ; + static std::string ClassCommands::CMD_NORMALACQ; + static std::string ClassCommands::CMD_TESTACQ; + static std::string ClassCommands::CMD_STREAMONACQ; + static std::string ClassCommands::CMD_STREAMOFFACQ; + static std::string ClassCommands::CMD_ONIMPEDANCEACQ; + static std::string ClassCommands::CMD_OFFIMPEDANCEACQ; + static std::string ClassCommands::CMD_CONFIGACQ; + static std::string ClassCommands::CMD_CONFIGACQ_CHANNELS; + static std::string ClassCommands::CMD_CONFIGACQ_FREQUENCY; + static std::string ClassCommands::CMD_CONFIGACQ_GAIN; + static std::string ClassCommands::CMD_CONFIGACQ_INPUT; + static std::string ClassCommands::CMD_CONFIGACQ_TYPE; + static std::string ClassCommands::CMD_CONFIGACQ_IMPEDANCEPOSITIVE; + static std::string ClassCommands::CMD_CONFIGACQ_IMPEDANCENEGATIVE; + static std::string ClassCommands::CMD_CONFIGACQ_LEADOFF_ON; + static std::string ClassCommands::CMD_CONFIGACQ_LEADOFF_OFF; + static std::string ClassCommands::CMD_CONFIGACQ_SETLEADOFF; + + static std::string ClassCommands::CMD_GETSTIMTIME; + + static std::string ClassCommands::CMD_GETBUZZER; + static std::string ClassCommands::CMD_SETBUZZER; + static std::string ClassCommands::CMD_PLAYBUZZER; + + static std::string ClassCommands::CMD_STARTFES; + static std::string ClassCommands::CMD_STARTTACTILITY; + static std::string ClassCommands::CMD_STARTCOMMUNICATION; + + static std::string ClassCommands::CMD_GETLOGERRORS; + static std::string ClassCommands::CMD_OPENLOGERRORS; + static std::string ClassCommands::CMD_CLOSELOGERRORS; + + static std::string ClassCommands::CMD_SWITCHOFF; + + static std::string ClassCommands::CMD_GETRTC; + static std::string ClassCommands::CMD_SETRTCDATE; + static std::string ClassCommands::CMD_SETRTCTIME; + + static std::string ClassCommands::CMD_PING; + static std::string ClassCommands::CMD_HELP; + + static std::string ClassCommands::CMD_AUX; + static std::string ClassCommands::CMD_AUX2; + }; +} \ No newline at end of file diff --git a/serial_port_class_api/src/CLASS_SerialPort.cpp b/serial_port_class_api/src/CLASS_SerialPort.cpp new file mode 100644 index 0000000..8f8c8a2 --- /dev/null +++ b/serial_port_class_api/src/CLASS_SerialPort.cpp @@ -0,0 +1,1508 @@ +/** + * @file CLASS_SerialPort.cpp + * @author Leire Querejeta Lomas <leire.querejeta@tecnalia.com> + * @brief API -> serial connection and CLASS commands + * @date 2022-10-10 + * + * @copyright Copyright 2020 + * Tecnalia Research & Innovation. + * Distributed under the GNU GPL v3. + * For full terms see https://www.gnu.org/licenses/gpl.txt + */ +#include <iostream> +#include "../include/CLASS_SerialPort.h" +#include <algorithm> +#include <sstream> +#include <iomanip> +#include "../commands.h" +#include <vector> + +/** + * @brief construct a new class serialport::class serialport object + * + */ +CLASS_SerialPort::CLASS_SerialPort(){} +/** + * @brief destroy the class serialport::class serialport object and close seial port + * + */ +CLASS_SerialPort::~CLASS_SerialPort() +{ + CloseHandle(hCom); +} +/** + * @brief configure parameters and open serial port for the proper communication of the CLASS device + * + * @param commPortName name of port (ex: COM1) + * @return true serial port correct set and open + * @return false error during serial port open precess + */ +bool CLASS_SerialPort::SetOpenPort(std::string &commPortName) +{ + hCom = CreateFile(commPortName.c_str(), // port + GENERIC_READ|GENERIC_WRITE, // read/write access + 0, // must be opened with exclusive-access + NULL, // default security attributes + OPEN_EXISTING, // must use OPEN_EXISTING + 0, // not overlapped I/O + NULL); // must be NULL for comm devices + + if(hCom == INVALID_HANDLE_VALUE) + { + status = 0; + return status; + } + + //Set paremeters for the serial commutication device + memset(&dcb,0,sizeof(dcb)); + dcb.DCBlength = sizeof(dcb); //length + dcb.BaudRate = CBR_115200; //baud rate + dcb.ByteSize = 8; //data size + dcb.StopBits = ONESTOPBIT; //stop bit + dcb.Parity = NOPARITY; //parity bit + + if(!SetCommState(hCom,&dcb)) + { + CLASS_SerialPort::~CLASS_SerialPort(); + status = 0; + return status; + } + + //Set time-out parameter for a communication device (ms) + timeouts.ReadIntervalTimeout = MAXDWORD; //time allowed to elapse before the arrival of the next byte + timeouts.ReadTotalTimeoutMultiplier = 0; //multiplier used to calculate the total time-out period for read operations + timeouts.ReadTotalTimeoutConstant = 0; //a constant used to calculate the total time-out period for read operations + timeouts.WriteTotalTimeoutConstant = 0; //multiplier used to calculate the total time-out period for write operations + timeouts.WriteTotalTimeoutMultiplier = 0; //a constant used to calculate the total time-out period for write operations + + if(!SetCommTimeouts(hCom,&timeouts)) + { + CLASS_SerialPort::~CLASS_SerialPort(); + status = 0; + return status; + } + + status = 1; + return status; +} + +/** + * @brief writers command to CLASS device through serial port + * + * @param buffer command to send + * @return true write to serial port done + * @return false error during writting process + */ +bool CLASS_SerialPort::write(const char *buffer) +{ + DWORD numWritten; + WriteFile(hCom, buffer, strlen(buffer), &numWritten, NULL); + if(numWritten < strlen(buffer)) + return 0; + else + return 1; +} + +/** + * @brief auxiliar function to flush read serial port + * + * @param buffer buffer to read + * @param buffLen number of bytes to read FLUSH_BUFFSIZE + * @return int bytes readed + */ +int CLASS_SerialPort::flushread(char *buffer, int buffLen) +{ + DWORD numRead; + BOOL ret = ReadFile(hCom, buffer, buffLen, &numRead, NULL); + if(!ret) + return 0; + return numRead; +} + +/** + * @brief flush serial port read buffer + * + */ +void CLASS_SerialPort::flush() +{ + char buffer[FLUSH_BUFFSIZE]; + int numBytes = flushread(buffer, FLUSH_BUFFSIZE); + while(numBytes != 0) + numBytes = flushread(buffer, FLUSH_BUFFSIZE); +} + +/** + * @brief read information from CLASS device through serial port + * + * @return std::string received information + */ +std::string CLASS_SerialPort::read() +{ + char buffer[BUFFER_SIZE]; + int buffLen = BUFFER_SIZE; + DWORD numRead; + + --buffLen; + + bool ret = ReadFile(hCom, buffer, buffLen, &numRead, NULL); + + if(!ret) + return 0; + + buffer[numRead] = '\0'; + std::string r; + r.assign(buffer); + return r; +} + +/** + * @brief writers command to CLASS device and reads the answer through serial port + * + * @param buffer command to send + * @return std::string received information + */ +std::string CLASS_SerialPort::communication(const char *buffer) +{ + flush(); + + std::string result; + bool ret = write(buffer); + if(!ret) + { + result = "ERROR"; + } + else + { + result = ""; + for(int i = 0; i < 10; i++)//while(charsRead!=0)// //LQL DUDAS + { + result = result.append(read()); + Sleep(100); + } + } + /*std::string re = "Re:[] "; + std::string::size_type i = result.find(re); + + if (i != std::string::npos) + result.erase(i, re.length());*/ + + return result; +} + +/** + * @brief closes serial port + * + */ +void CLASS_SerialPort::CloseSerialPort() +{ + CloseHandle(hCom); +} + + +/** + * @brief turns stimulation on + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setStimulationOn() +{ + std::string msg = commands::ClassCommands::CMD_ONSTIM; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief turns stimulation off + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setStimulationOff() +{ + std::string msg = commands::ClassCommands::CMD_OFFSTIM; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief executes stimulation on a virutal electrode + * + * @param virtualElectrodeName selected virtual electrode to be stimulated + * @return std::string answer + */ +std::string CLASS_SerialPort::setStimulationVirtualElectrode(std::string &virtualElectrodeName) +{ + std::string msg = commands::ClassCommands::CMD_VELECSTIM + virtualElectrodeName + commands::ClassCommands::CMD_AUX; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief gives the battery level + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getTic() +{ + std::string msg = commands::ClassCommands::CMD_GETTIC; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief gives information about a virtual electrode + * + * @param virtualElectrodeNumber selected virtual electrode + * @return std::string answer + */ +std::string CLASS_SerialPort::getVirtualElectrode(std::string &virtualElectrodeNumber) +{ + std::string msg = commands::ClassCommands::CMD_CONFIGVELEC + virtualElectrodeNumber + commands::ClassCommands::CMD_AUX2 + commands::ClassCommands::CMD_AUX; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief cofigures selected virtual electrode (all parameters must be included) + * + * @param virtualElectrodeNumber number of virtual electrode, maximum allowed value MAX_ALLOWED_VIRTUAL_ELECTRODES + * @param virtualElectrodeName name of virtual electrode, maximum number of characters MAX_CHAR_VIRTUAL_ELECTRODE_NAME + * @param cathodes to be active for the virtual electrode, allowed maximum number of pads per virtual electrode MAX_VIRTUAL_ELECTRODE_PADS + * @param anodes to be active for the virtual electrode + * @param amplitudes associated to each cathode, allowed minimum value MIN_VIRTUAL_ELECTRODE_AMPLITUDE, allowed maximum value for FES prototypes MAX_VIRTUAL_ELECTRODE_AMPLITUDE_FES, allowed maximum value for TACTILITY prototypes MAX_VIRTUAL_ELECTRODE_AMPLITUDE_TACTILITY + * @param pulsewiths associated to each cathode, allowed maximum value MAX_VIRTUAL_ELECTRODE_PULSE_WIDTH, allowd minumum value MIN_VIRTUAL_ELECTRODE_PULSE_WIDTH + * @param selected virtual electrode seleccted '1' or deselected '0' + * @param sync virtual electrode synchronous '1' or asynchronous '0' + * @return std::string answer + */ +std::string CLASS_SerialPort::setVirtualElectrodeOld(std::string &virtualElectrodeNumber, std::string &virtualElectrodeName, std::vector<int> cathodes, std::vector<int> anodes , std::vector<double> amplitudes, std::vector<int> pulsewiths, bool selected , bool sync) +{ + std::string result = ERROR_NOT_VALID_VALUE; + double aux_min_amplitude = *min_element(amplitudes.begin(),amplitudes.end()); + double aux_max_amplitude = *max_element(amplitudes.begin(),amplitudes.end()); + double allowed_max_amplitude = MAX_VIRTUAL_ELECTRODE_AMPLITUDE_FES; + + if(devicePrototype == "TACTILITY" || devicePrototype == "PAIN") + allowed_max_amplitude = MAX_VIRTUAL_ELECTRODE_AMPLITUDE_TACTILITY; + + int aux_widths = *max_element(pulsewiths.begin(), pulsewiths.end()); + + if(virtualElectrodeName.length() <= MAX_ALLOWED_VIRTUAL_ELECTRODES && virtualElectrodeName.length() <= MAX_CHAR_VIRTUAL_ELECTRODE_NAME && cathodes.size() <= MAX_VIRTUAL_ELECTRODE_PADS && aux_max_amplitude <= allowed_max_amplitude && aux_min_amplitude >= MIN_VIRTUAL_ELECTRODE_AMPLITUDE && aux_widths <= MAX_VIRTUAL_ELECTRODE_PULSE_WIDTH && aux_widths >= MIN_VIRTUAL_ELECTRODE_PULSE_WIDTH) + { + std::string msg = commands::ClassCommands::CMD_CONFIGVELEC + virtualElectrodeNumber + commands::ClassCommands::CMD_CONFIGVELEC_NAME + virtualElectrodeName + commands::ClassCommands::CMD_CONFIGVELEC_PADS; + + for (int i = 0; i < cathodes.size(); i++) + { + msg += std::to_string(cathodes[i]) + commands::ClassCommands::CMD_CONFIGVELEC_CATHODE; + } + for (int i = 0; i < anodes.size(); i++) + { + msg += std::to_string(anodes[i]) + commands::ClassCommands::CMD_CONFIGVELEC_ANODE; + } + msg += commands::ClassCommands::CMD_CONFIGVELEC_AMPLITUDE; + for (int i = 0; i < cathodes.size(); i++) + { + msg += std::to_string(cathodes[i]) + "=" + std::to_string(amplitudes[i]) + ","; + } + msg += commands::ClassCommands::CMD_CONFIGVELEC_PULSEWIDTH; + for (int i = 0; i < cathodes.size(); i++) + { + msg += std::to_string(cathodes[i]) + "=" + std::to_string(pulsewiths[i]) + ","; + } + msg += commands::ClassCommands::CMD_CONFIGVELEC_SELECTION + std::to_string(selected) + commands::ClassCommands::CMD_CONFIGVELEC_SYNCHRONOUS + std::to_string(sync) + commands::ClassCommands::CMD_AUX; + result = communication(msg.c_str()); + } + return result; +} +/** + * @brief cofigures selected virtual electrode, only virtualElectrodeNumber is compulsory. Each parameter can be included independetly allowing function to use defult values + * + * @param virtualElectrodeNumber number of virtual electrode, maximum allowed value MAX_ALLOWED_VIRTUAL_ELECTRODES + * @param virtualElectrodeName name of virtual electrode, maximum number of characters MAX_CHAR_VIRTUAL_ELECTRODE_NAME + * @param cathodes to be active for the virtual electrode, allowed maximum number of pads per virtual electrode MAX_VIRTUAL_ELECTRODE_PADS + * @param anodes to be active for the virtual electrode + * @param amplitudes associated to each cathode, allowed minimum value MIN_VIRTUAL_ELECTRODE_AMPLITUDE, allowed maximum value for FES prototypes MAX_VIRTUAL_ELECTRODE_AMPLITUDE_FES, allowed maximum value for TACTILITY prototypes MAX_VIRTUAL_ELECTRODE_AMPLITUDE_TACTILITY + * @param pulsewiths associated to each cathode, allowed maximum value MAX_VIRTUAL_ELECTRODE_PULSE_WIDTH, allowd minumum value MIN_VIRTUAL_ELECTRODE_PULSE_WIDTH + * @param selected virtual electrode seleccted '1' or deselected '0' + * @param sync virtual electrode synchronous '1' or asynchronous '0' + * @return std::string answer + */ +std::string CLASS_SerialPort::setVirtualElectrode(std::string &virtualElectrodeNumber, std::string virtualElectrodeName, std::vector<int> cathodes, std::vector<int> anodes, std::vector<CathodeAmplitude> amplitudes, std::vector<CathodeWidth> pulsewidths, std::string selected, std::string sync) +{ + std::string result = ERROR_NOT_VALID_VALUE; + std::string msg; + + if(std::stoi(virtualElectrodeNumber) <= MAX_ALLOWED_VIRTUAL_ELECTRODES) + { + msg += commands::ClassCommands::CMD_CONFIGVELEC + virtualElectrodeNumber; + } + else + { + return result; + } + + if(virtualElectrodeName.length() <= MAX_CHAR_VIRTUAL_ELECTRODE_NAME && !virtualElectrodeName.empty()) + { + msg += commands::ClassCommands::CMD_CONFIGVELEC_NAME + virtualElectrodeName; + } + else + { + return result; + } + if(cathodes.size() <= MAX_VIRTUAL_ELECTRODE_PADS && anodes.size() <= MAX_VIRTUAL_ELECTRODE_PADS) + { + if(cathodes[0] != 0 || anodes[0] != 0) + { + msg += commands::ClassCommands::CMD_CONFIGVELEC_PADS; + } + if(cathodes[0] != 0) + { + for (int i = 0; i < cathodes.size(); i++) + { + msg += std::to_string(cathodes[i]) + commands::ClassCommands::CMD_CONFIGVELEC_CATHODE; + } + } + if(anodes[0] != 0) + { + for (int i = 0; i < anodes.size(); i++) + { + msg += std::to_string(anodes[i]) + commands::ClassCommands::CMD_CONFIGVELEC_ANODE; + } + } + } + else + { + return result; + } + if(amplitudes[0].cathodenumber != "0") + { + std::vector<double> aux_amplitudes; + for(int i = 0; i < amplitudes.size(); i++) + { + aux_amplitudes.push_back(amplitudes[i].amplitude); + } + double aux_min_amplitude = *min_element(aux_amplitudes.begin(),aux_amplitudes.end()); + double aux_max_amplitude = *max_element(aux_amplitudes.begin(),aux_amplitudes.end()); + double allowed_max_amplitude = MAX_VIRTUAL_ELECTRODE_AMPLITUDE_FES; + + if(devicePrototype == "TACTILITY" || devicePrototype == "PAIN") + allowed_max_amplitude = MAX_VIRTUAL_ELECTRODE_AMPLITUDE_TACTILITY; + + if(aux_max_amplitude <= allowed_max_amplitude && aux_min_amplitude >= MIN_VIRTUAL_ELECTRODE_AMPLITUDE) + { + msg += commands::ClassCommands::CMD_CONFIGVELEC_AMPLITUDE; + for (int i = 0; i < amplitudes.size(); i++) + { + msg += amplitudes[i].cathodenumber + "=" + std::to_string(amplitudes[i].amplitude) + ","; + } + } + else + { + return result; + } + } + if(pulsewidths[0].cathodenumber != "0") + { + std::vector<int> aux_widths; + for(int i = 0; i < pulsewidths.size(); i++) + { + aux_widths.push_back(pulsewidths[i].width); + } + int aux_max_widths = *max_element(aux_widths.begin(), aux_widths.end()); + int aux_min_widths = *min_element(aux_widths.begin(), aux_widths.end()); + + if(aux_max_widths <= MAX_VIRTUAL_ELECTRODE_PULSE_WIDTH && aux_min_widths >= MIN_VIRTUAL_ELECTRODE_PULSE_WIDTH) + { + msg += commands::ClassCommands::CMD_CONFIGVELEC_PULSEWIDTH; + for (int i = 0; i < pulsewidths.size(); i++) + { + msg += pulsewidths[i].cathodenumber + "=" + std::to_string(pulsewidths[i].width ) + ","; + } + } + else + { + return result; + } + } + if(selected != "") + { + if(selected == "1" || selected == "0") + { + msg += commands::ClassCommands::CMD_CONFIGVELEC_SELECTION + selected; + } + else + { + return result; + } + } + if(sync !="") + { + if(sync == "1" || sync == "0") + { + msg += commands::ClassCommands::CMD_CONFIGVELEC_SYNCHRONOUS + sync; + } + else + { + return result; + } + } + msg += commands::ClassCommands::CMD_AUX; + result = communication(msg.c_str()); + return result; +} + +/** + * @brief cofigures virtual electrode to selected or not selected + * + * @param virtualElectrodeNumber number of virtual electrode, maximum allowed value MAX_ALLOWED_VIRTUAL_ELECTRODES + * @param selection virtual electrode seleccted '1' or deselected '0' + * @return std::string answer + */ +std::string CLASS_SerialPort::setVirtualElectrodeSelection(std::string &virtualElectrodeNumber, bool selection) +{ + std::string result = ERROR_NOT_VALID_VALUE; + std::string msg; + + if(std::stoi(virtualElectrodeNumber) <= MAX_ALLOWED_VIRTUAL_ELECTRODES) + { + msg += commands::ClassCommands::CMD_CONFIGVELEC + virtualElectrodeNumber; + std::string selected; + if(selection == true) + { + selected = "1"; + } + else + { + selected = "0"; + } + msg += commands::ClassCommands::CMD_CONFIGVELEC_SELECTION + selected; + } + else + { + return result; + } + + msg += commands::ClassCommands::CMD_AUX; + result = communication(msg.c_str()); + return result; +} + +/** + * @brief gives information about a pattern + * + * @param patternNumber selected pattern number, allowed number of patterns MAX_ALLOWED_PATTERNS + * @return std::string answer + */ +std::string CLASS_SerialPort::getPattern(std::string &patternNumber) +{ + std:: string result = ERROR_NOT_VALID_VALUE; + if(std::stoi(patternNumber) <= MAX_ALLOWED_PATTERNS) + { + std::string msg = commands::ClassCommands::CMD_CONFIGPATTERN + patternNumber + commands::ClassCommands::CMD_AUX2 + commands::ClassCommands::CMD_AUX; + result = communication(msg.c_str()); + } + + return result; +} + +/** + * @brief delete a pattern + * + * @param patternNumber selected pattern number, allowed number of patterns MAX_ALLOWED_PATTERNS + * @return std::string answer + */ +std::string CLASS_SerialPort::clearPattern(std::string &patternNumber) +{ + std::string result = ERROR_NOT_VALID_VALUE; + if(std::stoi(patternNumber) <= MAX_ALLOWED_PATTERNS) + { + std::string msg = commands::ClassCommands::CMD_CONFIGPATTERN + patternNumber + commands::ClassCommands::CMD_CONFIGPATTERN_PARAM1 + commands::ClassCommands::CMD_AUX; + result = communication(msg.c_str()); + } + + return result; +} + +/** + * @brief cofigures selected pattern + * + * @param patternNumber selected pattern, allowed number of patterns MAX_ALLOWED_PATTERNS + * @param patternsValues vector of Pattern structure + * @return std::string answer + */ +std::string CLASS_SerialPort::setPattern(std::string &patternNumber, std::vector<Pattern> &patternsValues) +{ + std::string result = ERROR_NOT_VALID_VALUE; + if(std::stoi(patternNumber) <= MAX_ALLOWED_PATTERNS) + { + result = clearPattern(patternNumber); + std::string msg; + for(int i = 0; i < patternsValues.size() ; i++) + { + msg = commands::ClassCommands::CMD_CONFIGPATTERN + patternNumber + commands::ClassCommands::CMD_CONFIGPATTERN_PARAM2 + patternsValues[i].amp + " " + patternsValues[i].pw + " " + patternsValues[i].r + " " + std::to_string(patternsValues[i].ampval) + " " + std::to_string(patternsValues[i].pwval) + " " + std::to_string(patternsValues[i].time) + commands::ClassCommands::CMD_AUX; + result = communication(msg.c_str()); + } + msg = commands::ClassCommands::CMD_CONFIGPATTERN + patternNumber + commands::ClassCommands::CMD_CONFIGPATTERN_PARAM3 + commands::ClassCommands::CMD_AUX; + result = communication(msg.c_str()); + } + return result; +} + +/** + * @brief gives information about high voltage value + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getHighVoltage() +{ + std::string msg = commands::ClassCommands::CMD_GETHV; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief sets hihg voltage value to on + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setHighVoltageOn() +{ + std::string msg = commands::ClassCommands::CMD_ONHV; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief sets high voltage value to off + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setHighVoltageOff() +{ + std::string msg = commands::ClassCommands::CMD_OFFHV; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief sets high voltage value + * + * @param highVoltageValue allowed maximum high voltage value MAX_HIGH_VOLTAGE, allowed minimum high voltage value MIN_HIGH_VOLTAGE + * @return std::string answer + */ +std::string CLASS_SerialPort::setHighVoltageValue(std::string &highVoltageValue) +{ + std::string result = ERROR_NOT_VALID_VALUE; + if(MIN_HIGH_VOLTAGE <= std::stoi(highVoltageValue) && std::stoi(highVoltageValue) <= MAX_HIGH_VOLTAGE) + { + std::string msg = commands::ClassCommands::CMD_SETHV + highVoltageValue + commands::ClassCommands::CMD_AUX; + result = communication(msg.c_str()); + } + return result; +} + +/** + * @brief opens SD current directory + * + * @return std::string answer + */ +std::string CLASS_SerialPort::SDls() +{ + std::string msg = commands::ClassCommands::CMD_SD_OPEN_DIRECTORY; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief changes SD directory + * + * @param directoryName fully specified path + * @return std::string answer + */ +std::string CLASS_SerialPort::SDcd(std::string &directoryName) +{ + std::string msg = commands::ClassCommands::CMD_SD_CHANGE_DIRECTORY + directoryName + commands::ClassCommands::CMD_AUX; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief prints SD current directory + * + * @return std::string answer + */ +std::string CLASS_SerialPort::SDpwd() +{ + std::string msg = commands::ClassCommands::CMD_SD_PRINT_DIRECTORY; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief removes SD directory + * + * @param fileName fully specified path + * @return std::string answer + */ +std::string CLASS_SerialPort::SDrm(std::string &fileName) +{ + std::string msg = commands::ClassCommands::CMD_SD_DELETE_FILE + fileName + commands::ClassCommands::CMD_AUX; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief create new file into SD + * + * @param fileName fully specified path + * @return std::string answer + */ +std::string CLASS_SerialPort::SDcat(std::string &fileName) +{ + std::string msg = commands::ClassCommands::CMD_SD_CREATE_FILE + fileName + commands::ClassCommands::CMD_AUX; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief edit file into SD + * + * @param filePathName fully specified path + * @param text text to add + * @return std::string answer + */ +std::string CLASS_SerialPort::SDed(std::string &filePathName, std::string &text) +{ + std::string msg = commands::ClassCommands::CMD_SD_EDIT_FILE + filePathName + " " + text +commands::ClassCommands::CMD_AUX; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief creates new directory into SD + * + * @param directoryName fully specified path + * @return std::string answer + */ +std::string CLASS_SerialPort::SDmkdir(std::string &directoryName) +{ + std::string msg = commands::ClassCommands::CMD_SD_CREATE_DIRECTORY + directoryName + commands::ClassCommands::CMD_AUX; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief renames SD file + * + * @param oldFileName fully specified path + * @param newFileName fully specified new path + * @return std::string answer + */ +std::string CLASS_SerialPort::SDrename(std::string &oldFileName, std::string &newFileName) +{ + std::string msg = commands::ClassCommands::CMD_SD_RENAME_FILE + oldFileName + " " + newFileName + commands::ClassCommands::CMD_AUX; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief create and save in SD boot information file + * + * @return std::string answer + */ +std::string CLASS_SerialPort::SDsaveboot() +{ + std::string msg = commands::ClassCommands::CMD_SD_SAVE_BOOT; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief get prototype type : FES, TACTILITY + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getApplication() +{ + std::string msg = commands::ClassCommands::CMD_GETPROTOTYPE; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief set prototype type to FES + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setFESApplication() +{ + std::string msg = commands::ClassCommands::CMD_SET_FES_PROTOTYPE; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief set prototype type to TACTILITY + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setTACTILITYApplication() +{ + std::string msg = commands::ClassCommands::CMD_SET_TACTILITY_PROTOTYPE; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief gives device name + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getDeviceName() +{ + std::string msg = commands::ClassCommands::CMD_GETDEVICENAME; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief sets device name + * + * @param deviceName allowed maximun number of characters MAX_CHAR_DEVICENAME + * @return std::string answer + */ +std::string CLASS_SerialPort::setDeviceName(std::string &deviceName) +{ + std::string result = ERROR_NOT_VALID_VALUE; + if(deviceName.length() <= MAX_CHAR_DEVICENAME) + { + std::string msg = commands::ClassCommands::CMD_SETDEVICENAME + deviceName + commands::ClassCommands::CMD_AUX; + result = communication(msg.c_str()); + } + return result; +} + +/** + * @brief gives information about firmware + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getFirmware() +{ + std::string msg = commands::ClassCommands::CMD_GETFW; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief gives information about hardware + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getHardware() +{ + std::string msg = commands::ClassCommands::CMD_GETHW; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief gives the name of the SD card folder where patterns are saved + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getSDPartternsFolder() +{ + std::string msg = commands::ClassCommands::CMD_GETSDFUNCTION; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief sets the name of SD card folder where patterns will be saved + * + * @param sdPattern name of folder, allowed maximum charaters MAX_CHAR_FUNCTIONNAME + * @return std::string answer + */ +std::string CLASS_SerialPort::setSDPartternsFolder(std::string &sdPattern) +{ + std::string result = ERROR_NOT_VALID_VALUE; + if(sdPattern.length() <= MAX_CHAR_FUNCTIONNAME) + { + std::string msg = commands::ClassCommands::CMD_SETSDFUNCTION + sdPattern + commands::ClassCommands::CMD_AUX; + result = communication(msg.c_str()); + } + return result; +} + +/** + * @brief gives information about stimulation frequency value in hertz + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getFrequency() +{ + std::string msg = commands::ClassCommands::CMD_GETFRQUENCY; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief set the stimulation frequency value in hertz + * + * @param frequency allowed maximum value MAX_FREQUENCY, allowed minimum value MIN_FREQUENCY + * @return std::string answer + */ +std::string CLASS_SerialPort::setFrequency(std::string &frequency) +{ + std::string result = ERROR_NOT_VALID_VALUE; + if(MIN_FREQUENCY <= std::stoi(frequency) && std::stoi(frequency) <= MAX_FREQUENCY) + { + std::string msg = commands::ClassCommands::CMD_SETFREQ + frequency + commands::ClassCommands::CMD_AUX; + result = communication(msg.c_str()); + } + return result; +} + +/** + * @brief gives information about stimulation interpulses interval + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getPulsesInterval() +{ + std::string msg = commands::ClassCommands::CMD_GETINTERVAL; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief set the stimulation interpulses interval + * + * @param interval allowed maximum value MAX_PULSE_INTERVAL, allowed minimum value MIN_PULSE_INTERVAL + * @return std::string answer + */ +std::string CLASS_SerialPort::setPulsesInterval(std::string &interval) +{ + std::string result = ERROR_NOT_VALID_VALUE; + if(MIN_PULSE_INTERVAL <= std::stoi(interval) && std::stoi(interval) <= MAX_PULSE_INTERVAL) + { + std::string msg = commands::ClassCommands::CMD_SETINTERVAL + interval + commands::ClassCommands::CMD_AUX; + result = communication(msg.c_str()); + } + return result; +} + +/** + * @brief gives information about events log status + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getLogevents() +{ + std::string msg = commands::ClassCommands::CMD_GETLOGEVENTS; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief start loggind events + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setLogeventsOn() +{ + std::string msg = commands::ClassCommands::CMD_ONLOGEVENTS; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief stop logging events + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setLogeventsOff() +{ + std::string msg = commands::ClassCommands::CMD_OFFLOGEVENTS; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief gives the name of SD folder where the folder containing the pattern is saved + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getSDUserFolder() +{ + std::string msg = commands::ClassCommands::CMD_GETSDNAME; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief sets the name of SD folder where the folder containing the pattern is saved + * + * @param sdFolder allowed maximum namber of characters MAX_CHAR_USERNAME + * @return std::string answer + */ +std::string CLASS_SerialPort::setSDUserFolder(std::string &sdFolder) +{ + std::string result = ERROR_NOT_VALID_VALUE; + if(sdFolder.length() <= MAX_CHAR_USERNAME) + { + std::string msg = commands::ClassCommands::CMD_SETSDNAME + sdFolder + commands::ClassCommands::CMD_AUX; + result = communication(msg.c_str()); + } + return result; +} + +/** + * @brief gives information about battery status + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getBattery() +{ + std::string msg = commands::ClassCommands::CMD_GETBATTERY; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief gives information about last measured pulse voltages + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getPulseVoltages() +{ + std::string msg = commands::ClassCommands::CMD_GETPULSE_VOLTAGES; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief gives information about last measured pulse intensities (mA) + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getPulseIntensities() +{ + std::string msg = commands::ClassCommands::CMD_GETPULSE_INTENSITIES; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief gives information about last measured voltages of the negative pulse cycle + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getPulseNegativeVoltages() +{ + std::string msg = commands::ClassCommands::CMD_GETPULSE_NEGATIVEVOLTAGES; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief gives information about last measured voltages of the positive pulse cycle + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getPulsePositiveVoltages() +{ + std::string msg = commands::ClassCommands::CMD_GETPULSE_POSITIVEVOLTAGES; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief gives information about the average intensity (mA) for the positive cycle + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getPulseAverageIntensity() +{ + std::string msg = commands::ClassCommands::CMD_GETPULSE_AVERAGEINTENSITY; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief start acquisition + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setAcquisitionOn() +{ + std::string msg = commands::ClassCommands::CMD_ONACQ; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief stop acquisition + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setAcquisitionOff() +{ + std::string msg = commands::ClassCommands::CMD_OFFACQ; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief sets configuration for acquisition (all parameters must be included) + * + * @param channels acquisition channels to be active + * @param frequency acquisition sampling rate, allowed values ACQUISITION_ALLOWED_FREQUENCY1, ACQUISITION_ALLOWED_FREQUENCY2, ACQUISITION_ALLOWED_FREQUENCY3, ACQUISITION_ALLOWED_FREQUENCY4, ACQUISITION_ALLOWED_FREQUENCY5 + * @param type unipolar or bipolar + * @param gain parameter that defines gain for the recording, allowed values ACQUISITION_ALLOWED_GAIN1, ACQUISITION_ALLOWED_GAIN2, ACQUISITION_ALLOWED_GAIN3, ACQUISITION_ALLOWED_GAIN4, ACQUISITION_ALLOWED_GAIN5, ACQUISITION_ALLOWED_GAIN6 + * @return std::string answer + */ +std::string CLASS_SerialPort::setAcquisitionConfiguratonOld( std::vector<int> &channels, int frequency, std::string type, int gain) +{ + std::string result = ERROR_NOT_VALID_VALUE; +std::string msg; + int aux_max_channel = *max_element(channels.begin(),channels.end()); + int aux_min_channel = *min_element(channels.begin(),channels.end()); + + if((aux_min_channel>=1 && aux_max_channel<16) + && (frequency == ACQUISITION_ALLOWED_FREQUENCY1 || frequency == ACQUISITION_ALLOWED_FREQUENCY2|| frequency == ACQUISITION_ALLOWED_FREQUENCY3 ||frequency == ACQUISITION_ALLOWED_FREQUENCY4 ||frequency == ACQUISITION_ALLOWED_FREQUENCY5) + && (type == ACQUISITION_ALLOWED_TYPE1 || type == ACQUISITION_ALLOWED_TYPE2) + && (gain == ACQUISITION_ALLOWED_GAIN1 || gain == ACQUISITION_ALLOWED_GAIN2 || gain == ACQUISITION_ALLOWED_GAIN3 || gain == ACQUISITION_ALLOWED_GAIN4 || gain == ACQUISITION_ALLOWED_GAIN5 || gain == ACQUISITION_ALLOWED_GAIN6)) + { + int channels_int = 0; + for (int i = 0; i < channels.size(); i++) + { + channels_int += (int)(pow(2, channels[i] - 1)); + } + std::stringstream stream; + stream << "0x" + << std::setfill('0') << std::setw(2) + << std::hex << channels_int; + std::string channels_hex_str(stream.str()); + msg = commands::ClassCommands::CMD_CONFIGACQ + commands::ClassCommands::CMD_CONFIGACQ_FREQUENCY + std::to_string(frequency) + commands::ClassCommands::CMD_CONFIGACQ_CHANNELS + channels_hex_str + commands::ClassCommands::CMD_CONFIGACQ_TYPE + type + commands::ClassCommands::CMD_CONFIGACQ_GAIN + std::to_string(gain) + commands::ClassCommands::CMD_AUX; + result = communication(msg.c_str()); + } + return result; +} + +/** + * @brief sets configuration for acquisition , only virtualElectrodeNumber is compulsory. Each parameter can be included independetly allowing function to use defult values + * + * @param channels acquisition channels to be active + * @param frequency acquisition sampling rate, allowed values ACQUISITION_ALLOWED_FREQUENCY1, ACQUISITION_ALLOWED_FREQUENCY2, ACQUISITION_ALLOWED_FREQUENCY3, ACQUISITION_ALLOWED_FREQUENCY4, ACQUISITION_ALLOWED_FREQUENCY5 + * @param type unipolar or bipolar + * @param gain parameter that defines gain for the recording, allowed values ACQUISITION_ALLOWED_GAIN1, ACQUISITION_ALLOWED_GAIN2, ACQUISITION_ALLOWED_GAIN3, ACQUISITION_ALLOWED_GAIN4, ACQUISITION_ALLOWED_GAIN5, ACQUISITION_ALLOWED_GAIN6 + * @return std::string answer + */ +std::string CLASS_SerialPort::setAcquisitionConfiguraton(std::vector<int> &channels, int frequency, std::string type, int gain) +{ + std::string result = ERROR_NOT_VALID_VALUE; + std::string msg; + + int aux_max_channel = *max_element(channels.begin(),channels.end()); + int aux_min_channel = *min_element(channels.begin(),channels.end()); + + if(aux_min_channel>=1 && aux_max_channel<16) + { + int channels_int = 0; + for (int i = 0; i < channels.size(); i++) + { + channels_int += (int)(pow(2, channels[i] - 1)); + } + std::stringstream stream; + stream << "0x" + << std::setfill('0') << std::setw(2) + << std::hex << channels_int; + std::string channels_hex_str(stream.str()); + msg = commands::ClassCommands::CMD_CONFIGACQ + commands::ClassCommands::CMD_CONFIGACQ_CHANNELS + channels_hex_str; + + if(frequency != 0) + { + if(frequency == ACQUISITION_ALLOWED_FREQUENCY1 || frequency == ACQUISITION_ALLOWED_FREQUENCY2|| frequency == ACQUISITION_ALLOWED_FREQUENCY3 ||frequency == ACQUISITION_ALLOWED_FREQUENCY4 ||frequency == ACQUISITION_ALLOWED_FREQUENCY5) + { + msg += commands::ClassCommands::CMD_CONFIGACQ_FREQUENCY + std::to_string(frequency); + } + else + { + return result; + } + } + if(type != "") + { + if(type == ACQUISITION_ALLOWED_TYPE1 || type == ACQUISITION_ALLOWED_TYPE2) + { + msg += commands::ClassCommands::CMD_CONFIGACQ_TYPE + type; + } + else + { + return result; + } + } + if(gain != 0) + { + if(gain == ACQUISITION_ALLOWED_GAIN1 || gain == ACQUISITION_ALLOWED_GAIN2 || gain == ACQUISITION_ALLOWED_GAIN3 || gain == ACQUISITION_ALLOWED_GAIN4 || gain == ACQUISITION_ALLOWED_GAIN5 || gain == ACQUISITION_ALLOWED_GAIN6) + { + msg += commands::ClassCommands::CMD_CONFIGACQ_GAIN + std::to_string(gain); + } + else + { + return result; + } + } + } + else + { + return result; + } + + msg += commands::ClassCommands::CMD_AUX; + result = communication(msg.c_str()); + return result; + +} + +/** + * @brief to generate a quadratic signal for input tests + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setAcquisitionTest() +{ + std::string msg = commands::ClassCommands::CMD_TESTACQ; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief set normal input signal, biosignal recoding + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setAcquisitionNormal() +{ + std::string msg = commands::ClassCommands::CMD_NORMALACQ; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief enable for streaming acquisition data by bluetooth + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setAcquisitionStreamOn() +{ + std::string msg = commands::ClassCommands::CMD_STREAMONACQ; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief disable for streaming acquisition data by bluetooth + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setAcquisitionStreamOff() +{ + std::string msg = commands::ClassCommands::CMD_STREAMOFFACQ; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief enable acquisition electrode impedance measurement mode + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setAcquisitionImpedanceOn() +{ + std::string msg = commands::ClassCommands::CMD_ONIMPEDANCEACQ; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief desable acquisition electrode impedance measurement mode + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setAcquisitionImpedanceOff() +{ + std::string msg = commands::ClassCommands::CMD_OFFIMPEDANCEACQ; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief enable acquisition impedance measurement for positive electrodes + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setAcquisitionImpedanceChannelsPositive() +{ + std::string msg = commands::ClassCommands::CMD_CONFIGACQ_IMPEDANCEPOSITIVE; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief enable acquisition impedance measurement for negative electrodes + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setAcquisitionImpedanceChannelsNegative() +{ + std::string msg = commands::ClassCommands::CMD_CONFIGACQ_IMPEDANCENEGATIVE; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief enable acquisition electrode disconnection alarm + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setAcquisitionLeadoffOn() +{ + std::string msg = commands::ClassCommands::CMD_CONFIGACQ_LEADOFF_ON; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief disable acquisition electrode disconnection alarm + * + * @return std::string answer + */ +std::string CLASS_SerialPort::setAcquisitionLeadoffOff() +{ + std::string msg = commands::ClassCommands::CMD_CONFIGACQ_LEADOFF_OFF; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief configure leadoff signal current, depending on the electrode type higher or lower leadoff current is required + * + * @param current allowed values ACQUISITION_ALLOWED_CURRENT1, ACQUISITION_ALLOWED_CURRENT2, ACQUISITION_ALLOWED_CURRENT3, ACQUISITION_ALLOWED_CURRENT4 + * @return std::string answer + */ +std::string CLASS_SerialPort::setAcquisitionLeadoffConfiguration(std::string ¤t) +{ + std::string result = ERROR_NOT_VALID_VALUE; + if(current == ACQUISITION_ALLOWED_CURRENT1 || current == ACQUISITION_ALLOWED_CURRENT2 || current == ACQUISITION_ALLOWED_CURRENT3 || current == ACQUISITION_ALLOWED_CURRENT4) + { + std::string msg = commands::ClassCommands::CMD_CONFIGACQ_SETLEADOFF + current + commands::ClassCommands::CMD_AUX; + result = communication(msg.c_str()); + } + return result; +} + +/** + * @brief get the elapsed time since the stimulation began in in microsencond + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getStimulationTime() +{ + std::string msg = commands::ClassCommands::CMD_GETSTIMTIME; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief get the time buzzer will sound in milliseconds + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getBuzzerTempo() +{ + std::string msg = commands::ClassCommands::CMD_SETBUZZER + commands::ClassCommands::ClassCommands::CMD_AUX2 + commands::ClassCommands::ClassCommands::CMD_AUX; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief set the time buzzer will sound in milliseconds + * + * @param buzzerTempo allowed maximum value BUZZER_MAX_TEMPO_ms, allowed minimum value BUZZER_MIN_TEMPO_ms + * @return std::string answer + */ +std::string CLASS_SerialPort::setBuzzerTempo(std::string &buzzerTempo) +{ + std::string result = ERROR_NOT_VALID_VALUE; + if(BUZZER_MIN_TEMPO_ms <= std::stoi(buzzerTempo) && std::stoi(buzzerTempo) <= BUZZER_MAX_TEMPO_ms) + { + std::string msg = commands::ClassCommands::CMD_SETBUZZER + buzzerTempo + commands::ClassCommands::CMD_AUX; + result = communication(msg.c_str()); + } + + return result; +} + +/** + * @brief play buzzer + * + * @return std::string answer + */ +std::string CLASS_SerialPort::playBuzzer() +{ + std::string msg = commands::ClassCommands::CMD_PLAYBUZZER; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief start communication with FES prototype + * + * @return std::string answer + */ +std::string CLASS_SerialPort::startFESCommunication() +{ + std::string msg = commands::ClassCommands::CMD_STARTFES; + std::string result = communication(msg.c_str()); + devicePrototype = "FES"; + return result; +} + +/** + * @brief start communication with TACTILITY prototype + * + * @return std::string answer + */ +std::string CLASS_SerialPort::startTACTILITYCommunication() +{ + std::string msg = commands::ClassCommands::CMD_STARTTACTILITY; + std::string result = communication(msg.c_str()); + devicePrototype = "TACTILTIY"; + return result; +} + +/** + * @brief start communication with prototype + * + * @param prototipeName name of prototype + * @return std::string answer + */ +std::string CLASS_SerialPort::startCommunication(std::string &prototipeName) +{ + std::string msg = commands::ClassCommands::CMD_STARTCOMMUNICATION + prototipeName + commands::ClassCommands::CMD_AUX; + std::string result = communication(msg.c_str()); + devicePrototype = prototipeName; + return result; +} + +/** + * @brief gives information about the number of writing errors + * + * @return std::string answer + */ +std::string CLASS_SerialPort::showLogErrors() +{ + std::string msg = commands::ClassCommands::CMD_GETLOGERRORS; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief create a new file to log errors in SD card + * + * @return std::string answer + */ +std::string CLASS_SerialPort::createLogFile() +{ + std::string msg = commands::ClassCommands::CMD_OPENLOGERRORS; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief flush remaining data form the log file to SD card + * + * @return std::string answer + */ +std::string CLASS_SerialPort::closeLogFile() +{ + std::string msg = commands::ClassCommands::CMD_CLOSELOGERRORS; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief turn off device + * + * @return std::string answer + */ +std::string CLASS_SerialPort::shutDown() +{ + std::string msg = commands::ClassCommands::CMD_SWITCHOFF; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief gives information about RTC clock + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getRTC() +{ + std::string msg = commands::ClassCommands::CMD_GETRTC; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief sets RTC date + * + * @param rtcDate format mm-dd-yyyy + * @return std::string answer + */ +std::string CLASS_SerialPort::setRTCDate(std::string &rtcDate) +{ + std::string msg = commands::ClassCommands::CMD_SETRTCDATE + rtcDate + commands::ClassCommands::CMD_AUX; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief sets RTC time + * + * @param rtcTime format hh:mm:ss + * @return std::string answer + */ +std::string CLASS_SerialPort::setRTCTime(std::string &rtcTime) +{ + std::string msg = commands::ClassCommands::CMD_SETRTCTIME + rtcTime + commands::ClassCommands::CMD_AUX; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief check connection + * + * @return std::string answer + */ +std::string CLASS_SerialPort::ping() +{ + std::string msg = commands::ClassCommands::CMD_PING; + std::string result = communication(msg.c_str()); + return result; +} + +/** + * @brief displays a list of commands + * + * @return std::string answer + */ +std::string CLASS_SerialPort::getHelp() +{ + std::string msg = commands::ClassCommands::CMD_HELP; + std::string result = communication(msg.c_str()); + return result; +} \ No newline at end of file diff --git a/serial_port_class_api/src/CMakeLists.txt b/serial_port_class_api/src/CMakeLists.txt new file mode 100644 index 0000000..4e2d415 --- /dev/null +++ b/serial_port_class_api/src/CMakeLists.txt @@ -0,0 +1,19 @@ + +cmake_minimum_required(VERSION 3.14) +project (class_serial_port VERSION 0.1.0) + +add_executable(class_api_exe + CLASS_SerialPort.cpp + main.cpp) + +target_link_libraries(class_api_exe ${CMAKE_CURRENT_SOURCE_DIR}/../commands.lib) + + +set(class_files_local ${CMAKE_CURRENT_SOURCE_DIR}/CLASS_SerialPort.cpp) + +set(class_files ${class_files_local} PARENT_SCOPE) + +add_library(class_serial_port ${class_files_local}) + +target_link_libraries(class_serial_port ${CMAKE_CURRENT_SOURCE_DIR}/../commands.lib) +set_target_properties(class_serial_port PROPERTIES FOLDER class) diff --git a/serial_port_class_api/src/main.cpp b/serial_port_class_api/src/main.cpp new file mode 100644 index 0000000..83ae980 --- /dev/null +++ b/serial_port_class_api/src/main.cpp @@ -0,0 +1,322 @@ +/** + * @file main.cpp + * @author Leire Querejeta Lomas <leire.querejeta@tecnalia.com> + * @brief example of the use of API + * @date 2022-10-10 + * + * @copyright Copyright 2020 + * Tecnalia Research & Innovation. + * Distributed under the GNU GPL v3. + * For full terms see https://www.gnu.org/licenses/gpl.txt + */ +#include <iostream> +#include <string> +#include "../include/CLASS_SerialPort.h" +#include <vector> +#include "../commands.h" + +using namespace std; + +int main(void) +{ + std::string port; + port = "COM3"; // name of port assigned to class bluetooth device + CLASS_SerialPort serial; + bool port_status = serial.SetOpenPort(port); + + cout << "Port Status :"; + cout << port_status <<endl; + + + + + + + + if(port_status == 1) + { + //start communication command depending on device --> startFESCommunication() ; startTACTILITYCommunication() ; startCommunication(std::string &prototipeName) + std::string start_communication = serial.startFESCommunication(); + cout << "start_communication : " + start_communication << endl; + + std::string setStimulationOn = serial.setStimulationOn(); + cout << "setStimulationOn : " << setStimulationOn << endl; + + std::string setStimulationOff = serial.setStimulationOff(); + cout << "setStimulationOff : " << setStimulationOff << endl; + + + //the virtual electrode need to be configured previous to it stimulation using setVirtualElectrodeOld or setVirtualElectrode functions + std::string virtual_electrode_number = "10"; + std::string virtual_electrode_name = "test"; + std::vector<int> virtual_electrode_cathodes; + virtual_electrode_cathodes.push_back(1); + std::vector<int> virtual_electrode_anodes; + virtual_electrode_anodes.push_back(2); + std::vector<double> virtual_electrode_amplitudes; + virtual_electrode_amplitudes.push_back(2.3); + std::vector<int> virtual_electrode_widths; + virtual_electrode_widths.push_back(300); + boolean selection = true; + boolean synchronous = false; + + std::string setVirtualElectrodeOld = serial.setVirtualElectrodeOld(virtual_electrode_number, virtual_electrode_name, virtual_electrode_cathodes, virtual_electrode_anodes, virtual_electrode_amplitudes, virtual_electrode_widths, selection, synchronous); + cout << "setVirtualElectrodeOld : " + setVirtualElectrodeOld << endl; + + + std::string namevelec = "test"; + std::string setStimulationVirtualElectrode = serial.setStimulationVirtualElectrode(namevelec); + cout << "setStimulationVirtualElectrode : " + setStimulationVirtualElectrode << endl; + + std::string getTic = serial.getTic(); + cout << "getTic: " << getTic << endl; + + std::string getvirtual_electrode_number = "10"; + std::string getVirtualElectrode = serial.getVirtualElectrode(getvirtual_electrode_number); + cout << "getVirtualElectrode :" + getVirtualElectrode << endl; + + std::string setvirtual_electrode_number = "11"; + std::string setvirtual_electrode_name = "test2"; + std::vector<int> setvirtual_electrode_cathodes = {4,5}; + std::vector<int> setvirtual_electrode_anodes = {8}; + std::vector<CathodeAmplitude> setvirtual_electrode_amplitudes; + CathodeAmplitude cathodeamplitude1 = {"4",2.3}; + CathodeAmplitude cathodeamplitude2 = {"5",2.4}; + setvirtual_electrode_amplitudes.push_back(cathodeamplitude1); + setvirtual_electrode_amplitudes.push_back(cathodeamplitude2); + std::vector<CathodeWidth> setvirtual_electrode_widths; + CathodeWidth cathodewidth1 = {"4",300}; + CathodeWidth cathodewidth2 = {"5",200}; + setvirtual_electrode_widths.push_back(cathodewidth1); + setvirtual_electrode_widths.push_back(cathodewidth2); + std::string setselection = "1"; + std::string setsynchronous = "0"; + + std::string setVirtualElectrode = serial.setVirtualElectrode(setvirtual_electrode_number, setvirtual_electrode_name, setvirtual_electrode_cathodes, setvirtual_electrode_anodes, setvirtual_electrode_amplitudes, setvirtual_electrode_widths, setselection, setsynchronous); + cout << "setVirtualElectrode :" + setVirtualElectrode << endl; + + std::string selectionvirtual_electrode_number = "11"; + bool selection_setselection = 0; + std::string setVirtualElectrodeSelection = serial.setVirtualElectrodeSelection(selectionvirtual_electrode_number, selection_setselection); + cout << "setVirtualElectrodeSelection :" + setVirtualElectrodeSelection << endl; + + std::string pattern_number = "11"; + std::string getPattern = serial.getPattern(pattern_number); + cout << "getPattern : " << getPattern << endl; + + std::string clearPattern = serial.clearPattern(pattern_number); + cout << "clearPattern : " << clearPattern << endl; + + vector<Pattern> patterns; + Pattern pattern1; + pattern1.amp = "CONST"; + pattern1.pw = "CONST"; + pattern1.r = "R"; + pattern1.ampval = 100; + pattern1.pwval = 100; + pattern1.time = 100; + Pattern pattern2; + pattern2.amp = "CONST"; + pattern2.pw = "CONST"; + pattern2.r = "R"; + pattern2.ampval = 100; + pattern2.pwval = 100; + pattern2.time = 100; + + patterns.push_back(pattern1); + patterns.push_back(pattern2); + + std::string setPattern = serial.setPattern(pattern_number,patterns); + cout << "setPattern : " << setPattern << endl; + + + std::string getHighVoltage = serial.getHighVoltage(); + cout << "getHighVoltage : " << getHighVoltage << endl; + + std::string setHighVoltageOn = serial.setHighVoltageOn(); + cout << "setHighVoltageOn : " << setHighVoltageOn << endl; + + std::string setHighVoltageOff = serial.setHighVoltageOff(); + cout << "setHighVoltageOff : " << setHighVoltageOff << endl; + + std::string hv = "150"; + std::string setHighVoltageValue = serial.setHighVoltageValue(hv); + cout << "setHighVoltageValue : " << setHighVoltageValue << endl; + + std::string getApplication = serial.getApplication(); + cout << "getApplication :" + getApplication << endl; + + //set application depending on device --> setFESApplication() ; setTACTILITYApplication() + std::string setFESApplication = serial.setFESApplication(); + cout << "setFESApplication :" + setFESApplication << endl; + + std::string getDeviceName = serial.getDeviceName(); + cout << "getDeviceName : " << getDeviceName << endl; + + std::string device = "new_name"; + std::string setDeviceName = serial.setDeviceName(device); + cout << "setDeviceName : " << setDeviceName << endl; + + std::string getFirmware = serial.getFirmware(); + cout << "getFirmware : " << getFirmware << endl; + + std::string getHardware = serial.getHardware(); + cout << "getHardware : " << getHardware << endl; + + std::string getSDPartternsFolder = serial.getSDPartternsFolder(); + cout << "getSDPartternsFolder : " << getSDPartternsFolder << endl; + + std::string sd_patternfolder = "new_folder"; + std::string setSDPartternsFolder = serial.setSDPartternsFolder(sd_patternfolder); + cout << "setSDPartternsFolder : " << setSDPartternsFolder << endl; + + std::string getFrequency = serial.getFrequency(); + cout << "getFrequency : " << getFrequency << endl; + + std::string set_frequency = "1000"; + std::string setFrequency = serial.setFrequency(set_frequency); + cout << "setFrequency : " << setFrequency << endl; + + std::string getPulsesInterval = serial.getPulsesInterval(); + cout << "getPulsesInterval : " << getPulsesInterval << endl; + + std::string set_interval = "300"; + std::string setPulsesInterval = serial.setPulsesInterval(set_interval); + cout << "setPulsesInterval : " << setPulsesInterval << endl; + + std::string getLogevents = serial.getLogevents(); + cout << "getLogevents : " << getLogevents << endl; + + std::string setLogeventsOn = serial.setLogeventsOn(); + cout << "setLogeventsOn : " << setLogeventsOn << endl; + + std::string setLogeventsOff = serial.setLogeventsOff(); + cout << "setLogeventsOff : " << setLogeventsOff << endl; + + std::string getSDUserFolder = serial.getSDUserFolder(); + cout << "getSDUserFolder : " << getSDUserFolder << endl; + + std::string sd_userfolder = "user_folder"; + std::string setSDUserFolder = serial.setSDUserFolder(sd_userfolder); + cout << "setSDUserFolder : " << setSDUserFolder << endl; + + std::string getBattery = serial.getBattery(); + cout << "getBattery : " << getBattery << endl; + + std::string getPulseVoltages = serial.getPulseVoltages(); + cout << "getPulseVoltages :" + getPulseVoltages << endl; + + std::string getPulseIntensities = serial.getPulseIntensities(); + cout << "getPulseIntensities :" + getPulseIntensities << endl; + + std::string getPulseNegativeVoltages = serial.getPulseNegativeVoltages(); + cout << "getPulseNegativeVoltages :" + getPulseNegativeVoltages << endl; + + std::string getPulsePositiveVoltages = serial.getPulsePositiveVoltages(); + cout << "getPulsePositiveVoltages :" + getPulsePositiveVoltages << endl; + + std::string getPulseAverageIntensity = serial.getPulseAverageIntensity(); + cout << "getPulseAverageIntensity :" + getPulseAverageIntensity << endl; + + std::string setAcquisitionOn = serial.setAcquisitionOn(); + cout << "setAcquisitionOn :" + setAcquisitionOn << endl; + + std::string setAcquisitionOff = serial.setAcquisitionOff(); + cout << "setAcquisitionOff :" + setAcquisitionOff << endl; + + + std::vector<int> acquisition_channels; + acquisition_channels.push_back(3); + acquisition_channels.push_back(12); + acquisition_channels.push_back(1); + acquisition_channels.push_back(7); + int frequencyconfig= 500; + std::string type = "bipolar"; + int gain= 2; + + std::string setAcquisitionConfiguraton = serial.setAcquisitionConfiguraton(acquisition_channels, frequencyconfig, type, gain); + cout << "setAcquisitionConfiguraton :" + setAcquisitionConfiguraton << endl; + + std::string setAcquisitionTest = serial.setAcquisitionTest(); + cout << "setAcquisitionTest :" + setAcquisitionTest << endl; + + std::string setAcquisitionNormal = serial.setAcquisitionNormal(); + cout << "setAcquisitionNormal :" + setAcquisitionNormal << endl; + + std::string setAcquisitionStreamOn = serial.setAcquisitionStreamOn(); + cout << "setAcquisitionStreamOn :" + setAcquisitionStreamOn << endl; + + std::string setAcquisitionStreamOff = serial.setAcquisitionStreamOff(); + cout << "setAcquisitionStreamOff :" + setAcquisitionStreamOff << endl; + + std::string setAcquisitionImpedanceOn = serial.setAcquisitionImpedanceOn(); + cout << "setAcquisitionImpedanceOn :" + setAcquisitionImpedanceOn << endl; + + std::string setAcquisitionImpedanceOff = serial.setAcquisitionImpedanceOff(); + cout << "setAcquisitionImpedanceOff :" + setAcquisitionImpedanceOff << endl; + + std::string setAcquisitionImpedanceChannelsPositive = serial.setAcquisitionImpedanceChannelsPositive(); + cout << "setAcquisitionImpedanceChannelsPositive :" + setAcquisitionImpedanceChannelsPositive << endl; + + std::string setAcquisitionImpedanceChannelsNegative = serial.setAcquisitionImpedanceChannelsNegative(); + cout << "setAcquisitionImpedanceChannelsNegative :" + setAcquisitionImpedanceChannelsNegative << endl; + + std::string setAcquisitionLeadoffOn = serial.setAcquisitionLeadoffOn(); + cout << "setAcquisitionLeadoffOn :" + setAcquisitionLeadoffOn << endl; + + std::string setAcquisitionLeadoffOff = serial.setAcquisitionLeadoffOff(); + cout << "setAcquisitionLeadoffOff :" + setAcquisitionLeadoffOff << endl; + + std::string current_acqleadoff = "6uA"; + std::string setAcquisitionLeadoffConfiguration = serial.setAcquisitionLeadoffConfiguration(current_acqleadoff); + cout << "setAcquisitionLeadoffConfiguration :" + setAcquisitionLeadoffConfiguration << endl; + + std::string getStimulationTime = serial.getStimulationTime(); + cout << "getStimulationTime : " << getStimulationTime << endl; + + std::string getBuzzerTempo = serial.getBuzzerTempo(); + cout << "getBuzzerTempo : " << getBuzzerTempo << endl; + + std::string buzzertempo = "25"; + std::string setBuzzerTempo = serial.setBuzzerTempo(buzzertempo); + cout << "setBuzzerTempo: " << setBuzzerTempo << endl; + + //std::string playBuzzer = serial.playBuzzer(); + //cout << "playBuzzer : " << playBuzzer << endl; + + std::string showLogErrors = serial.showLogErrors(); + cout << "showLogErrors : " << showLogErrors << endl; + + std::string createLogFile = serial.createLogFile(); + cout << "createLogFile : " << createLogFile << endl; + + std::string closeLogFile = serial.closeLogFile(); + cout << "closeLogFile : " << closeLogFile << endl; + + std::string getRTC = serial.getRTC(); + cout << "getRTC : " << getRTC << endl; + + //std::string date = "02-25-2022"; + //std::string setRTCDate = serial.setRTCDate(date); + //cout << "setRTCDate : " << setRTCDate << endl; + + //std::string time = "10:00:00"; + //std::string setRTCTime = serial.setRTCTime(time); + //cout << "setRTCTime : " << setRTCTime << endl; + + + std::string ping = serial.ping(); + cout << "ping : " + ping << endl; + + std::string help = serial.getHelp(); + cout << "Help : " + help << endl; + + std::string shutDown = serial.shutDown(); + cout << "shutDown : " << shutDown << endl; + + serial.CloseSerialPort(); + } + + return 0; + +} \ No newline at end of file diff --git a/serial_port_class_api/wrappers/CLASS_SerialPort.i b/serial_port_class_api/wrappers/CLASS_SerialPort.i new file mode 100644 index 0000000..a94feda --- /dev/null +++ b/serial_port_class_api/wrappers/CLASS_SerialPort.i @@ -0,0 +1,21 @@ +%module CLASS_SerialPortModule + +%include <std_string.i> +%include <std_vector.i> +%include <typemaps.i> +%apply const std::string & {std::string &}; + +%template(VectorOfDoubles) std::vector<double>; +%template(VectorOfInts) std::vector<int>; + +%{ + #include "../../include/CLASS_SerialPort.h" + #include "../../include/CLASS_structures.hpp" +%} + +%include "../include/CLASS_SerialPort.h" +%include "../include/CLASS_structures.hpp" + +%template(VectorOfPatterns) std::vector<Pattern>; +%template(VectorOfCathodeAmplitudes) std::vector<CathodeAmplitude>; +%template(VectorOfCathodeWidths) std::vector<CathodeWidth>; \ No newline at end of file diff --git a/src/.gitkeep b/src/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt deleted file mode 100644 index 2d4f8df..0000000 --- a/src/CMakeLists.txt +++ /dev/null @@ -1,205 +0,0 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 3.14) -project (class_api_wrappers) -message("cmake ${PROJECT_NAME}") - -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) -project (class_api) -message("cmake ${PROJECT_NAME}") -message("Cmake version: ${CMAKE_VERSION}") - -if (WIN32) - set ( CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/class_api_install_win" CACHE PATH "Install dir" FORCE) -else() - set ( CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/class_api_install_linux" CACHE PATH "Install dir" FORCE) -endif() - -############################################# -# handlng compilation fields -if (CMAKE_VERSION VERSION_LESS "3.1") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") -else () - set (CMAKE_CXX_STANDARD 11) -endif () - -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fPIC") -if(UNIX) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") -endif() - -# see https://svn.boost.org/trac10/ticket/12974?replyto=description -if (WIN32) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_WIN32_WINNT=0x0A00") -endif() -message("CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}") - - -if (CMAKE_VERSION VERSION_LESS 3.2) - set(UPDATE_DISCONNECTED_IF_AVAILABLE "") -else() - set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") -endif() - - -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread") -############################################# -# get the name of the host machine -site_name(hostname) -message ("Compiling on machine ${hostname}") - -############################################# -# get the 32 / 64 flag of the compiler used -MESSAGE( STATUS "CMAKE_GENERATOR: ${CMAKE_GENERATOR}") -MESSAGE( STATUS "CMAKE_GENERATOR_PLATFORM: ${CMAKE_GENERATOR_PLATFORM}") -MESSAGE( STATUS "CMAKE_SIZEOF_VOID_P: ${CMAKE_SIZEOF_VOID_P}") - -if (WIN32) - #some versions of cmake do not fill CMAKE_GENERATOR_PLATFORM - if (CMAKE_GENERATOR_PLATFORM STREQUAL "") - STRING( FIND ${CMAKE_GENERATOR} "64" APOSITION ) - else (CMAKE_GENERATOR_PLATFORM STREQUAL "") - STRING( FIND ${CMAKE_GENERATOR_PLATFORM} "64" APOSITION ) - endif (CMAKE_GENERATOR_PLATFORM STREQUAL "") - - #MESSAGE( STATUS "Position of character '64' ${CMAKE_GENERATOR_PLATFORM} in is ${APOSITION}" ) - if(${APOSITION} EQUAL -1) - MESSAGE( "32 bits compiler detected using CMAKE_GENERATOR_PLATFORM" ) - SET( EX_PLATFORM 32 ) - else() - MESSAGE( "64 bits compiler detected using CMAKE_GENERATOR_PLATFORM" ) - SET( EX_PLATFORM 64 ) - endif() -else (WIN32) - if( CMAKE_SIZEOF_VOID_P EQUAL 8 ) - MESSAGE( "64 bits compiler detected using CMAKE_SIZEOF_VOID_P" ) - SET( EX_PLATFORM 64 ) - else( CMAKE_SIZEOF_VOID_P EQUAL 8 ) - MESSAGE( "32 bits compiler detected using CMAKE_SIZEOF_VOID_P" ) - SET( EX_PLATFORM 32 ) - endif( CMAKE_SIZEOF_VOID_P EQUAL 8 ) -endif (WIN32) - -if (WIN32) - set_property(GLOBAL PROPERTY USE_FOLDERS ON) - - if( ${hostname} MATCHES "WKM0255A") - set(BOOST_ROOT "C:/local/boost_1_63_0") - endif( ${hostname} MATCHES "WKM0255A") - - # based on the defined variables, we extend def for 32/64 version - # Boost - if ( EX_PLATFORM EQUAL 32) - set(BOOST_LIBRARYDIR ${BOOST_ROOT}/lib32-msvc-14.0/) - else ( EX_PLATFORM EQUAL 32) - set(BOOST_LIBRARYDIR ${BOOST_ROOT}/lib64-msvc-14.0/) - endif ( EX_PLATFORM EQUAL 32) - -endif (WIN32) - - -############################################# -# Boost management -if (UNIX) - # Dynamic libraries are used. - set(BOOST_INCLUDEDIR ${BOOST_ROOT}) - set(Boost_USE_STATIC_LIBS OFF) - set(Boost_USE_MULTITHREADED ON) - set(Boost_USE_STATIC_RUNTIME OFF) - add_definitions(-DBOOST_ALL_DYN_LINK ) - add_definitions(-DBOOST_LOG_DYNLINK) -else (UNIX) - # Static libraries are used. - set(BOOST_INCLUDEDIR ${BOOST_ROOT}) - set(Boost_USE_STATIC_LIBS ON) - set(Boost_MULTITHREADED ON) -endif (UNIX) - -message("Boost reference repository set to: ${BOOST_ROOT}") -message("Boost Library dir: ${BOOST_LIBRARYDIR}") -find_package(Boost 1.63.0 REQUIRED COMPONENTS log log_setup thread program_options system date_time chrono regex atomic unit_test_framework filesystem) - -############################################# -# yaml-cpp management - -include(DownloadProject.cmake) - -if (UNIX) - download_project(PROJ yaml-cpp - GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git - GIT_TAG yaml-cpp-0.6.3 - ${UPDATE_DISCONNECTED_IF_AVAILABLE} - ) -endif (UNIX) - -if (WIN32) - if(MSVC_VERSION LESS_EQUAL 1900) - download_project(PROJ yaml-cpp - GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git - GIT_TAG yaml-cpp-0.6.3 - ${UPDATE_DISCONNECTED_IF_AVAILABLE} - ) - else (MSVC_VERSION LESS_EQUAL 1900) - #2017 requires master branch - download_project(PROJ yaml-cpp - GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git - GIT_TAG yaml-cpp-0.7.0 - ${UPDATE_DISCONNECTED_IF_AVAILABLE} - ) - endif (MSVC_VERSION LESS_EQUAL 1900) -endif (WIN32) - -message("Check: ${yaml-cpp_SOURCE_DIR} ${yaml-cpp_BINARY_DIR}") -set(YAML_CPP_BUILD_TESTS OFF) -add_subdirectory(${yaml-cpp_SOURCE_DIR} ${yaml-cpp_BINARY_DIR}) - - -message("Check") -message ("is it found?") -if (yaml-cpp_FOUND) - message("yaml-cpp is found!!!") - message("YAML_CPP_INCLUDE_DIR= ${YAML_CPP_INCLUDE_DIR}") - message("YAML_CPP_LIBRARIES= ${YAML_CPP_LIBRARIES}") - message("YAML_CPP_LIBRARY_DIRS= ${YAML_CPP_LIBRARY_DIRS}") - -else(yaml-cpp_FOUND) - message("yaml-cpp not found") -endif(yaml-cpp_FOUND) - -set(YAML_INCLUDE_DIR ${yaml-cpp_SOURCE_DIR}/include) -message ("YAML_INCLUDE_DIR= ${YAML_INCLUDE_DIR}") - -message("yaml static: ${YAML_CPP_LIBRARIES_STATIC}") -message("yaml shared: ${YAML_CPP_LIBRARIES_SHARED}") - -SET(LINKLIBS ${Boost_LIBRARIES} ${YAML_CPP_LIBRARIES_SHARED}) - -#option(BUILD_PYTHON "Build Python Library" ON) -if (WIN32) - option(BUILD_DOTNET "Build .NET Library" ON) -endif (WIN32) - -add_subdirectory(yaml_tools) -add_subdirectory(logger) -add_subdirectory(communication) -add_subdirectory(filter) -add_subdirectory(class_server) -add_subdirectory(api) -add_subdirectory(wrappers) -add_subdirectory(tests) - -option(BUILD_DOC "Build documentation" ON) -message(STATUS "Build documentation: ${BUILD_DOC}") -if (BUILD_DOC) - add_subdirectory(docs) -endif() - -INCLUDE(pandocology.cmake) - -add_document( - TARGET documentation - OUTPUT_FILE README.html - SOURCES README.md -) - -install(FILES ${CMAKE_BINARY_DIR}/README.html DESTINATION ./) -file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/docs/images/sw_arch.png DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/docs/images) -file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/docs/images/tecnalia.jpg DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/docs/images) \ No newline at end of file diff --git a/src/DownloadProject.CMakeLists.cmake.in b/src/DownloadProject.CMakeLists.cmake.in deleted file mode 100644 index d573929..0000000 --- a/src/DownloadProject.CMakeLists.cmake.in +++ /dev/null @@ -1,18 +0,0 @@ -# Distributed under the OSI-approved MIT License. See accompanying -# file LICENSE or https://github.com/Crascit/DownloadProject for details. - -cmake_minimum_required(VERSION 2.8.2) - -project(${DL_ARGS_PROJ}-download NONE) -message("Check cmake in ${DL_ARGS_UNPARSED_ARGUMENTS}") -include(ExternalProject) -ExternalProject_Add(${DL_ARGS_PROJ}-download - ${DL_ARGS_UNPARSED_ARGUMENTS} - SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" - BINARY_DIR "${DL_ARGS_BINARY_DIR}" - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - TEST_COMMAND "" - CMAKE_ARGS -DYAML_CPP_BUILD_TESTS=OFF -) \ No newline at end of file diff --git a/src/DownloadProject.cmake b/src/DownloadProject.cmake deleted file mode 100644 index 1709e09..0000000 --- a/src/DownloadProject.cmake +++ /dev/null @@ -1,182 +0,0 @@ -# Distributed under the OSI-approved MIT License. See accompanying -# file LICENSE or https://github.com/Crascit/DownloadProject for details. -# -# MODULE: DownloadProject -# -# PROVIDES: -# download_project( PROJ projectName -# [PREFIX prefixDir] -# [DOWNLOAD_DIR downloadDir] -# [SOURCE_DIR srcDir] -# [BINARY_DIR binDir] -# [QUIET] -# ... -# ) -# -# Provides the ability to download and unpack a tarball, zip file, git repository, -# etc. at configure time (i.e. when the cmake command is run). How the downloaded -# and unpacked contents are used is up to the caller, but the motivating case is -# to download source code which can then be included directly in the build with -# add_subdirectory() after the call to download_project(). Source and build -# directories are set up with this in mind. -# -# The PROJ argument is required. The projectName value will be used to construct -# the following variables upon exit (obviously replace projectName with its actual -# value): -# -# projectName_SOURCE_DIR -# projectName_BINARY_DIR -# -# The SOURCE_DIR and BINARY_DIR arguments are optional and would not typically -# need to be provided. They can be specified if you want the downloaded source -# and build directories to be located in a specific place. The contents of -# projectName_SOURCE_DIR and projectName_BINARY_DIR will be populated with the -# locations used whether you provide SOURCE_DIR/BINARY_DIR or not. -# -# The DOWNLOAD_DIR argument does not normally need to be set. It controls the -# location of the temporary CMake build used to perform the download. -# -# The PREFIX argument can be provided to change the base location of the default -# values of DOWNLOAD_DIR, SOURCE_DIR and BINARY_DIR. If all of those three arguments -# are provided, then PREFIX will have no effect. The default value for PREFIX is -# CMAKE_BINARY_DIR. -# -# The QUIET option can be given if you do not want to show the output associated -# with downloading the specified project. -# -# In addition to the above, any other options are passed through unmodified to -# ExternalProject_Add() to perform the actual download, patch and update steps. -# The following ExternalProject_Add() options are explicitly prohibited (they -# are reserved for use by the download_project() command): -# -# CONFIGURE_COMMAND -# BUILD_COMMAND -# INSTALL_COMMAND -# TEST_COMMAND -# -# Only those ExternalProject_Add() arguments which relate to downloading, patching -# and updating of the project sources are intended to be used. Also note that at -# least one set of download-related arguments are required. -# -# If using CMake 3.2 or later, the UPDATE_DISCONNECTED option can be used to -# prevent a check at the remote end for changes every time CMake is run -# after the first successful download. See the documentation of the ExternalProject -# module for more information. It is likely you will want to use this option if it -# is available to you. Note, however, that the ExternalProject implementation contains -# bugs which result in incorrect handling of the UPDATE_DISCONNECTED option when -# using the URL download method or when specifying a SOURCE_DIR with no download -# method. Fixes for these have been created, the last of which is scheduled for -# inclusion in CMake 3.8.0. Details can be found here: -# -# https://gitlab.kitware.com/cmake/cmake/commit/bdca68388bd57f8302d3c1d83d691034b7ffa70c -# https://gitlab.kitware.com/cmake/cmake/issues/16428 -# -# If you experience build errors related to the update step, consider avoiding -# the use of UPDATE_DISCONNECTED. -# -# EXAMPLE USAGE: -# -# include(DownloadProject) -# download_project(PROJ googletest -# GIT_REPOSITORY https://github.com/google/googletest.git -# GIT_TAG master -# UPDATE_DISCONNECTED 1 -# QUIET -# ) -# -# add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) -# -#======================================================================================== - - -set(_DownloadProjectDir "${CMAKE_CURRENT_LIST_DIR}") - -include(CMakeParseArguments) - -function(download_project) - - set(options QUIET) - set(oneValueArgs - PROJ - PREFIX - DOWNLOAD_DIR - SOURCE_DIR - BINARY_DIR - # Prevent the following from being passed through - CONFIGURE_COMMAND - BUILD_COMMAND - INSTALL_COMMAND - TEST_COMMAND - ) - set(multiValueArgs "") - - cmake_parse_arguments(DL_ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - # Hide output if requested - if (DL_ARGS_QUIET) - set(OUTPUT_QUIET "OUTPUT_QUIET") - else() - unset(OUTPUT_QUIET) - message(STATUS "Downloading/updating ${DL_ARGS_PROJ}") - endif() - - # Set up where we will put our temporary CMakeLists.txt file and also - # the base point below which the default source and binary dirs will be. - # The prefix must always be an absolute path. - if (NOT DL_ARGS_PREFIX) - set(DL_ARGS_PREFIX "${CMAKE_BINARY_DIR}") - else() - get_filename_component(DL_ARGS_PREFIX "${DL_ARGS_PREFIX}" ABSOLUTE - BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") - endif() - if (NOT DL_ARGS_DOWNLOAD_DIR) - set(DL_ARGS_DOWNLOAD_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-download") - endif() - - # Ensure the caller can know where to find the source and build directories - if (NOT DL_ARGS_SOURCE_DIR) - set(DL_ARGS_SOURCE_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-src") - endif() - if (NOT DL_ARGS_BINARY_DIR) - set(DL_ARGS_BINARY_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-build") - endif() - set(${DL_ARGS_PROJ}_SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" PARENT_SCOPE) - set(${DL_ARGS_PROJ}_BINARY_DIR "${DL_ARGS_BINARY_DIR}" PARENT_SCOPE) - - # The way that CLion manages multiple configurations, it causes a copy of - # the CMakeCache.txt to be copied across due to it not expecting there to - # be a project within a project. This causes the hard-coded paths in the - # cache to be copied and builds to fail. To mitigate this, we simply - # remove the cache if it exists before we configure the new project. It - # is safe to do so because it will be re-generated. Since this is only - # executed at the configure step, it should not cause additional builds or - # downloads. - file(REMOVE "${DL_ARGS_DOWNLOAD_DIR}/CMakeCache.txt") - - # Create and build a separate CMake project to carry out the download. - # If we've already previously done these steps, they will not cause - # anything to be updated, so extra rebuilds of the project won't occur. - # Make sure to pass through CMAKE_MAKE_PROGRAM in case the main project - # has this set to something not findable on the PATH. - configure_file("${_DownloadProjectDir}/DownloadProject.CMakeLists.cmake.in" - "${DL_ARGS_DOWNLOAD_DIR}/CMakeLists.txt") - execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" - -D "CMAKE_MAKE_PROGRAM:FILE=${CMAKE_MAKE_PROGRAM}" - . - RESULT_VARIABLE result - ${OUTPUT_QUIET} - WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" - ) - if(result) - message(FATAL_ERROR "CMake step for ${DL_ARGS_PROJ} failed: ${result}") - endif() - execute_process(COMMAND ${CMAKE_COMMAND} --build . - RESULT_VARIABLE result - ${OUTPUT_QUIET} - WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" - ) - if(result) - message(FATAL_ERROR "Build step for ${DL_ARGS_PROJ} failed: ${result}") - endif() - -endfunction() \ No newline at end of file diff --git a/src/README.md b/src/README.md deleted file mode 100644 index 777eea6..0000000 --- a/src/README.md +++ /dev/null @@ -1,49 +0,0 @@ -This folder contains all the needed components to write and run applications which are able to interchange messages with a CLASS_DEVICE. It is structured as follows: - -* [class_server](./class_server): this folder contains the executable of the CLASS_SERVER. -* [class_api](./api): it contains the files needed for programming clients which communicates with a CLASS_DEVICE. - -# CLASS_SW ARCHITECTURE -The CLASS_SW is divided in two parts: CLASS_SERVER and CLASS_CLIENT. The CLASS_SERVER connects to the CLASS_DEVICE via Bluetooth (BT) and allows a CLASS_CLIENT for interacting with it. The CLASS_SERVER and the CLASS_CLIENT could be in the same or different host and the communication between them is through UDP. The CLASS_CLIENT applications will make use of the provided CLASS_API in order to connect to the CLASS_SERVER and be able to send commands to CLASS_DEVICE and receive messages from it. - -<p align="center"> - <img width="600" height="300" src="./docs/images/SW_arch.png" alt="SW Architecture"> -</p> - ->NOTE: CLASS_SW components have been programmed to be cross-platform (Windows and Linux). However, they have been only tested on Windows for the moment. - -## CLASS_SERVER -It is an standalone application. It acepts two arguments: -* the COM port of the CLASS device to connect with -* the port where it will listen for client connections. - -It must be run before any client application. The CLASS_DEVICE should have been paired beforehand. - -The CLASS_SERVER is currently available for Windows and Linux. The executable for launching it is located in [class_server](./class_server/Release) folder. The following commands illustrate how to run it for both Windows and Linux: - -``` -# The CLASS_DEVICE is assigned to COM14 and the CLASS_SERVER will listen to CLASS_CLIENTs on port 50000 - -# windows -class_server.exe COM14 50000 - -# linux -./class_server COM14 50000 -``` - -## CLASS_CLIENT -A CLASS_CLIENT is an appplication which wants to send/receive messages to/from a CLASS_DEVICE. For that it must make use of the CLASS_API. There are some client examples in [examples](../src/class_server/src/) folder. - -# CLASS_API -The API has been developed to be used from different languages. More information can be found in this [link](./docs/doc_doxygen/html/index.html). - -<hr> -<p align="center"> - <a href="https://www.tecnalia.com"> - <img width="200" height="60" src="./docs/images/tecnalia.jpg" alt="Tecnalia"> - </a> -</p> -<p align="center"> -©Copyright 2021 Tecnalia -</p> - diff --git a/src/api/.gitkeep b/src/api/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt deleted file mode 100644 index d3b7f0d..0000000 --- a/src/api/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) -project (api) - -add_subdirectory(class) diff --git a/src/api/class/.gitkeep b/src/api/class/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/api/class/CMakeLists.txt b/src/api/class/CMakeLists.txt deleted file mode 100644 index 93fd829..0000000 --- a/src/api/class/CMakeLists.txt +++ /dev/null @@ -1,71 +0,0 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) -project(class) -message("cmake: ${PROJECT_NAME}") - -if (DEBUG_CMAKE) - set(CMAKE_VERBOSE_MAKEFILE yes) -endif (DEBUG_CMAKE) - -find_package(PythonLibs REQUIRED) - -include_directories(include ${Boost_INCLUDE_DIRS} ${YAML_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) -link_directories(${Boost_LIBRARY_DIRS} ${YAML_CPP_LIBRARY_DIRS}) - -set(class_api_files - ${CMAKE_CURRENT_SOURCE_DIR}/src/class.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/class_core.cpp - #${CMAKE_CURRENT_SOURCE_DIR}/src/class_core.hpp -) - -include (GenerateExportHeader) - -add_library(class SHARED - ${class_api_files} - ${logger_files} - ${communication_files} - ${yaml_tools_files} - ${filter_files} -) - -GENERATE_EXPORT_HEADER (class - # BASE_NAME class - # EXPORT_MACRO_NAME class_EXPORT - EXPORT_FILE_NAME class/class_Export.h - # STATIC_DEFINE class_BUILT_AS_STATIC -) - -set_target_properties(class PROPERTIES FOLDER class) - -target_include_directories(class PUBLIC - ${logger_SOURCE_DIR}/include - ${communication_SOURCE_DIR}/include - ${yaml_tools_SOURCE_DIR}/include - ${filter_SOURCE_DIR}/include - ${PROJECT_SOURCE_DIR} -) - -add_dependencies(class yaml-cpp) -target_link_libraries(class yaml-cpp ${Boost_LIBRARIES} communication logger filter ${PYTHON_LIBRARIES}) - -target_link_libraries(class ${CMAKE_CURRENT_SOURCE_DIR}/../../commands.lib) - -target_include_directories(class PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) - -# 'make install' to the correct location -install(TARGETS class - ARCHIVE DESTINATION class_api/lib/cpp - LIBRARY DESTINATION class_api/lib/cpp - RUNTIME DESTINATION class_api/lib/cpp) # This is for Windows -install(DIRECTORY include/ DESTINATION class_api/include) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/class/class_Export.h DESTINATION class_api/include/class) -if (WIN32) - install(FILES ${PYTHON_LIBRARIES}/../../python37.dll DESTINATION class_api/lib/cpp) -endif (WIN32) - -export(TARGETS class - logger - yaml_tools - yaml-cpp - communication - filter - FILE class_Config.cmake) \ No newline at end of file diff --git a/src/api/class/include/.gitkeep b/src/api/class/include/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/api/class/include/class/.gitkeep b/src/api/class/include/class/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/api/class/include/class/class.hpp b/src/api/class/include/class/class.hpp deleted file mode 100644 index 7aeeea9..0000000 --- a/src/api/class/include/class/class.hpp +++ /dev/null @@ -1,539 +0,0 @@ -/** - * @file class.hpp - * @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> and Leire Querejeta Lomas <leire.querejeta@tecnalia.com> - * @date 2020 - * - * Copyright 2020 Tecnalia Research & Innovation. - * Distributed under the GNU GPL v3. - * For full terms see https://www.gnu.org/licenses/gpl.txt - * - * @brief Header with the API methods - */ - -#pragma once - -#include <string> -#include <vector> - -#include "class_structures.hpp" -#include "class_error.hpp" -#include "class_cb.hpp" - -#include <class/class_Export.h> - -// forward declaration -class ClassCore; - -//! default server port to connect to -const int DEFAULT_SERVER_PORT = 50000; -//! default server ip to connect to -const std::string DEFAULT_SERVER_IP = "127.0.0.1"; -//! default port on which class_server messages are listened to -const int DEFAULT_LOCAL_PORT = 50001; -//! default ip on which class_server messages are listened to -const std::string DEFAULT_LOCAL_IP = "127.0.0.1"; - -//! API main class -class CLASS_EXPORT Class -{ -public: - - //! Allowed acquisition rates - enum AcqFreq { - FREQ_250, ///< 250Hz - FREQ_500, ///< 500Hz - FREQ_1000, ///< 1000Hz - FREQ_2000, ///< 2000Hz - FREQ_4000, ///< 4000Hz - FREQ_8000 ///< 8000Hz - }; - - //! Mode for initialization - enum InitMode { - TACTILITY, ///< Tactility: low amplitude stimulators, and includes a special fw version, such as the command of "selected 0/1" - PAIN, ///< Pain: low amplitude stimulators, including small steps (0.1 steps) - DESKTOP, ///< Desktop: high amplitudes, with bigger steps (0.1 are not allowed, only 1 steps) - NEURORESEARCH, - ICARE, - TIB_ACQ, - TIB_STIM - }; - - //! Allowed channel numbers for acquisition - enum AcqCh { - CH_1, ///< 1 channel - CH_2, ///< 2 channels - CH_3, ///< 3 channels - CH_4, ///< 4 channels - CH_5, ///< 5 channels - CH_6, ///< 6 channels - CH_7, ///< 7 channels - CH_8, ///< 8 channels - CH_9, ///< 9 channels - CH_10, ///< 10 channels - CH_11, ///< 11 channels - CH_12, ///< 12 channels - CH_13, ///< 13 channels - CH_14, ///< 14 channels - CH_15, ///< 15 channels - CH_16 ///< 16 channels - }; - - //! Acquisition types - enum AcqImpedancePolarity { - TYPE_UNIPOLAR, ///< Unipolar - TYPE_BIPOLAR ///< Bipolar - }; - - //! Impedance acquisition sign - enum AcqImpedanceSign { - TYPE_POSITIVE, ///< Positives - TYPE_NEGATIVE ///< Negatives - }; - - //! Acquisition gain - enum AcqGain { - GAIN_1, ///< 1.0 - GAIN_2, ///< 2.0 - GAIN_4, ///< 4.0 - GAIN_8, ///< 8.0 - GAIN_12, ///< 12.0 - GAIN_24 ///< 24.0 - }; - - //! Acquisition Input - enum AcqInput { - NORMAL, ///< Normal - TEST ///< Test - }; - - //! Basic constructor - Class(); - //! Basic destructor - ~Class(); - - /*! - \brief Connect to the CLASS server - \return Integer indicating the success of the connection (0-success, <0 error) - */ - ClassError::Error connect(); - - /*! - \brief Connect to the CLASS server - \param server_ip IP to connect with. - \param server_port Port to connect with. - \return Integer indicating the success of the connection (0-success, <0 error) - */ - ClassError::Error connect(const std::string & server_ip, const int & server_port); - - /*! - \brief Connect to the CLASS server - \param server_ip IP to connect with. - \param server_port Port to connect with. - \param local_port Port which listens for msgs coming from CLASS server. - \return Integer indicating the success of the connection (0-success, <0 error) - */ - ClassError::Error connect(const std::string & server_ip, const int & server_port, const int & local_port); - - /*! - \brief Connect to the CLASS server - \param server_ip IP to connect with. - \param server_port Port to connect with. - \param local_ip IP which listens for msgs coming from CLASS server. - \param local_port Port which listens for msgs coming from CLASS server. - \return Integer indicating the success of the connection (0-success, <0 error) - */ - ClassError::Error connect(const std::string & server_ip, const int & server_port, const std::string & local_ip, const int & local_port); - - /*! - \brief Disconnect from CLASS server - \return Integer indicating the success of the disconnection (0-success, <0 error) - */ - ClassError::Error disconnect(); - - /*! - \brief get acquisition frames - \return Vector with acquisition frames - */ - std::vector<AcqFrame> getAcqFrames(); - - /*! - \brief get the level of the battery - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error getBattery(); - - /*! - \brief get buzzer - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error getBuzzer(); - - /*! - \brief get device name - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error getDeviceName(); - - /*! - \brief get the firmware version - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error getFirmware(); - - /*! - \brief get frequency - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error getFrequency(); - - /*! - \brief get the hardware version - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error getHardware(); - - /*! - \brief get high voltage - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error getHv(); - - /*! - \brief get interval - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error getInterval(); - - /*! - \brief get logevents - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error getLogevents(); - - /*! - \brief get pattern - \param number number of pattern - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error getPatternStatus(const int& number); - - - /*! - \brief get rtc - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error getRTC(); - - /*! - \brief get sd function - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error getSDFunction(); - - /*! - \brief get sd user name - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error getSDUName(); - - /*! - \brief get the tic - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error getTic(); - - /*! - \brief get virtual electrode - \param number of virtual electrode - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error getVelecStatus(const int& number); - - /*! - \brief Init communication - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error initCommunication(); - - /*! - \brief Init communication sending iam mode - \param mode Initialization mode. - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error initCommunication(const InitMode& mode); - - /*! - \brief Clear pattern - \param id pattern to delete - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error patternDelete(const int& id); - - /*! - \brief Sends custom command to CLASS device - \param cmd Command. - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error sendCustomCmd(const std::string& cmd); - - /*! - \brief Sets class acq configurations - \param frequency Frequency of acquisition. - \param channel_numbers Channel numbers. - \param gain Gain for the acquisition. - \param input Acq input type, normal or test. - \return Integer indicating the success of the setting process (0-success, <0 error) - */ - ClassError::Error setAcqConfig(const AcqFreq& frequency, const std::vector<int>& channel_numbers, const AcqGain& gain, const AcqInput& input); - - /*! - \brief Sets the config for impedance acquisition - \param type Impedance acquisition type. - \return Integer indicating the success of the setting process (0-success, <0 error) - */ - ClassError::Error setAcqImpedanceConfig(const AcqImpedanceSign& type); - - /*! - \brief Sets the polarity for impedance acquisition - \param type Polarity acquisition type. - \return Integer indicating the success of the setting process (0-success, <0 error) - */ - ClassError::Error setAcqImpedancePolarity(const AcqImpedancePolarity& polarity); - - /*! - \brief Sets class acq input to normal (bio signal captured from channels) - \return Integer indicating the success of the setting process (0-success, <0 error) - */ - ClassError::Error setAcqInputNormal(); - - /*! - \brief Sets class acq input to test. The class will provide a quadratic signal. It is useful for testing and not getting saturated signal all the time. - \return Integer indicating the success of the setting process (0-success, <0 error) - */ - ClassError::Error setAcqInputTest(); - - /*! - \brief Sets the order of the filter used for impedance calculation - \param order Filter order (>0). - \return Integer indicating the success of the setting process (0-success, <0 error) - */ - ClassError::Error setImpedanceFilterOrder(const int& order); - - /*! - \brief Sets the duration of the buffer (ms*frequency//1000.0) - \param ms Time in ms. - \return Integer indicating the success of the setting process (0-success, <0 error) - */ - ClassError::Error setBufferDuration(const double& ms); - - /*! - \brief Sets buzzer on - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error setbuzzerPlay(); - - /*! - \brief Sets ms of buzzer song - \param tempo time in ms - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error setbuzzerTempo(const std::string& tempo); - - /*! - \brief set the callback - \param callback Callback - \param language Language which has defined the callback - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error setCallback(ClassCallback * callback, std::string language); - - /*! - \brief Sets name of device - \param name name of the device. - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error setDevName(const std::string& devicename); - - /*! - \brief Sets electrode pads number - \param id Electrode number/ID. - \param pads_number Number of pads of the electrode. - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error setElecPads(const int& id, const int& pads_number); - - /*! - \brief Sets high voltage to off - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error sethvOff(); - - /*! - \brief Sets name of device - \param hvvalue name of the device. - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error sethvValue(const std::string& hvvalue); - - /*! - \brief Sets high voltage to on - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error sethvOn(); - - /*! - \brief Sets interval value - \param interval value of stimulation interl pulses interval - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error setintervalValue(const std::string& interval); - /*! - \brief Sets logevents to off - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error setlogeventsOff(); - /*! - \brief Sets logevents to on - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error setlogeventsOn(); - - /*! - \brief set pattern - \param id pattern number - \param pat vector of patterns - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error setPattern(const int& id, const std::vector<Pattern>& pat); - - /*! - \brief set name of sd card folder where the pattern are saved - \param sdfunctionname name of the folder - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error setSDFunctionName(const std::string& sdfunctionname); - - /*! - \brief set name of sdcard folder where the folder containing the patterns is saved - \param sduname name of the folder - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error setSDUName(const std::string& sduname); - - /*! - \brief Shutdown - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error setShutdown(); - /*! - \brief Sets electrode pads number - \param frequency Frequency of the stimulation. - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error setStimFreq(const double& frequency); - - /*! - \brief Sets rtc date - \param date mm/dd/yyyy format - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error setrtcDate(const std::string& rtcDate); - - /*! - \brief Sets rtc time - \param date hh:mm:ss format - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error setrtcTime(const std::string& rtcTime); - - /*! - \brief Sets virtual electrode - \param id Virtual electrode number/ID. - \param name Name of the virtual electrode. - \param electrode_id Electrode ID/number. - \param cathodes List of integers with the IDs of the pads acting as cathodes - \param anodes List of integers with the IDs of the pads acting as cathodes - \param amp List of doubles with the amplitude (in mA) associated to each of the cathodes. Must have the same length as cathodes - \param width List of integers with the pulse width (in uS) associated to each of the cathodes. Must have the same length as cathodes - \param selected Defines if the velec is selected (‘true’ value) or deselected (‘false’ value). Only selected velecs will be executed with a “stim on” command - \param sync Defines if the velec is synchronous (‘rtue’ value) or asynchronous (‘false’ value). - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error setVelec(const int& id, const std::string& name, const int& electrode_id, const std::vector<int>& cathodes, const std::vector<int>& anodes, const std::vector<double>& amp, const std::vector<int>& width, const bool& selected, const bool& sync); - - /*! - \brief Sets whether the virtual electrode is selected or not - \param id Virtual electrode number/ID. - \param selected Defines if the velec is selected (‘true’ value) or deselected (‘false’ value). Only selected velecs will be executed with a “stim on” command. - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error setVelecSelected(const int& id, const bool& selected); - - /*! - \brief Sets whether the virtual electrodes are selected or not - \param id Vector with virtual electrode numbers/IDs. - \param selected Vector with bool indicating whether the velecs are selected (‘true’ value) or deselected (‘false’ value). Only selected velecs will be executed with a “stim on” command. - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error setVelecsSelected(const std::vector<int>& id, const std::vector<bool>& selected); - - /*! - \brief Starts acquisition - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error startAcq(); - - /*! - \brief Starts acquisition stream - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error startAcqStream(); - - /*! - \brief Starts impedance acquisition - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error startImpedanceAcq(); - - /*! - \brief Starts stimulation - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error startStim(); - - /*! - \brief Start stimulation of the virtual electrode which name is passed as parameter - \param name Name of the virtual electrode. - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error stimVelec(const std::string& name); - - /*! - \brief Stops acquisition - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error stopAcq(); - - /*! - \brief Stops acquisition stream - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error stopAcqStream(); - - /*! - \brief Stops impedance acquisition - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error stopImpedanceAcq(); - - /*! - \brief Stops stimulation - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error stopStim(); - -private: - //! Core internal component - ClassCore * core_; -}; diff --git a/src/api/class/include/class/class_cb.hpp b/src/api/class/include/class/class_cb.hpp deleted file mode 100644 index c7e7f79..0000000 --- a/src/api/class/include/class/class_cb.hpp +++ /dev/null @@ -1,115 +0,0 @@ -/** - * @file class_cb.hpp - * @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> and Leire Querejeta Lomas <leire.querejeta@tecnalia.com> - * @date 2020 - * - * Copyright 2020 Tecnalia Research & Innovation. - * Distributed under the GNU GPL v3. - * For full terms see https://www.gnu.org/licenses/gpl.txt - * - * @brief Callbacks for Class - */ - -#pragma once -#include <string> - -//! Abstract class (interface) for receiving callbacks from device. -class ClassCallback -{ -public: - - /*! - \brief Handle for battery status callbacks - \param battery Received battery percentage - */ - virtual void batteryHandle(double battery) = 0; - - /*! - \brief Handle for buzzer status callbacks - \param buzzerstatus Received buzzer status message - */ - virtual void buzzerHandle(std::string buzzerstatus) = 0; - - /*! - \brief Handle for device name callbacks - \param device Received device name - */ - virtual void deviceNameHandle(std::string device) = 0; - - /*! - \brief Handle for frequncy callbacks - \param frequencystatus Received frequency - */ - virtual void frequencyHandle(std::string frequencystatus) = 0; - - /*! - \brief Handle for firmware callbacks - \param firmware Received firmware version - */ - virtual void firmwareHandle(std::string firmware) = 0; - - /*! - \brief Handle for hardware callbacks - \param hardware Received hardware version - */ - virtual void hardwareHandle(std::string hardware) = 0; - - /*! - \brief Handle for high voltage callbacks - \param hvstatus Received high voltage status - */ - virtual void hvHandle(std::string hvstatus) = 0; - - /*! - \brief Handle for interval callbacks - \param intervalstatus Received stimulation inter pulses interval - */ - virtual void intervalHandle(std::string intervalstatus) = 0; - - /*! - \brief Handle for logevents callbacks - \param logevents Received logevents message - */ - virtual void logeventsHandle(std::string logevents) = 0; - - /*! - \brief Handle for pattern callbacks - \param patternstatus Received pattern message - */ - virtual void patternHandle(std::string patternstatus) = 0; - - /*! - \brief Handle for rtc callbacks - \param rtcstatus Received rtc values - */ - virtual void rtcHandle(std::string rtcstatus) = 0; - - /*! - \brief Handle for sd function callbacks - \param sdfunctionstatus Received name of the sd card folder where patterns are saved - */ - virtual void sdfunctionHandle(std::string sdfunctionstatus) = 0; - - /*! - \brief Handle for sd user name callbacks - \param sdunamestatus Received name of the sd card folder where the folder containing patterns is saved - */ - virtual void sdunameHandle(std::string sdunamestatus) = 0; - - /*! - \brief Handle for tic callbacks - \param tic Received tic message - */ - virtual void ticHandle(std::string tic) = 0; - - /*! - \brief Handle for turning off callbacks - */ - virtual void turnOffHandle() = 0; - - /*! - \brief Handle for virtual electrodes callbacks - \param velecstatus Received virtual electrode message - */ - virtual void velecstatusHandle(std::string velecstatus) = 0; -}; \ No newline at end of file diff --git a/src/api/class/include/class/class_core.hpp b/src/api/class/include/class/class_core.hpp deleted file mode 100644 index 75d6b0f..0000000 --- a/src/api/class/include/class/class_core.hpp +++ /dev/null @@ -1,772 +0,0 @@ -/** - * @file class_core.hpp - * @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> and Leire Querejeta Lomas <leire.querejeta@tecnalia.com> - * @date 2020 - * - * Copyright 2020 Tecnalia Research & Innovation. - * Distributed under the GNU GPL v3. - * For full terms see https://www.gnu.org/licenses/gpl.txt - * - * @brief Interface to the funccionalities of Class - */ - -#ifndef CLASS_CORE_HPP -#define CLASS_CORE_HPP - -#include <vector> -#include <string> -#include <memory> -#include <thread> -#include <mutex> -#include <cmath> -#include <iostream> -#include <fstream> - -#include <boost/circular_buffer.hpp> - -#include <communication/udp_server.hpp> -#include <communication/udp_client.hpp> -#include <logger/logger.hpp> -#include <class/class_structures.hpp> -#include <class/class_error.hpp> -#include <class/class_cb.hpp> -#include <filter/butterworth.hpp> - -#include <Python.h> - -/** - * @brief Core class - */ -class ClassCore:public UdpServerListener -{ -public: - //! Allowed values - - int BUZZER_MAX_VALUE = 500; - int BUZZER_MIN_VALUE = 25; - int FREQ_MAX_VALUE = 2000; - int FREQ_MIN_VALUE = 0; - int HV_MAX_VALUE = 200; - int HV_MIN_VALUE = 120; - int INTERVAL_MAX_VALUE = 1000; - int INTERVAL_MIN_VALUE = 250; - - //! Structure for electrode - struct Elec - { - int id; - int pads_number; - }; - - //! Structure for virtual electrode - struct Velec - { - int id; - std::string name; - bool selected; - }; - - //! basic constructor - ClassCore(); - //! basic destructor - ~ClassCore(); - - /*! - \brief Stops acquisition - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error acqOff(); - - /*! - \brief Starts acquisition - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error acqOn(); - - /*! - \brief Stops impedance acquisition - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error acqImpedanceOff(); - - /*! - \brief Starts impedance acquisition - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error acqImpedanceOn(); - - /*! - \brief Stops acquisition stream - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error acqStreamOff(); - - /*! - \brief Starts acquisition stream - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error acqStreamOn(); - - /*! - \brief Sets the config for impedance acquisition - \param positive Indicates whther is positive (true) or negative (false) - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error acqImpedanceConfig(const bool& positive); - - /*! - \brief Sets the polarity for impedance acquisition - \param unipolar Indicates whther is unipolar (true) or bipolar (false). - \return Integer indicating the success of the setting process (0-success, <0 error) - */ - ClassError::Error acqImpedancePolarity(const bool& unipolar); - - /*! - \brief Sets class acq input to normal (bio signal captured from channels) - \return Integer indicating the success of the setting process (0-success, <0 error) - */ - ClassError::Error acqInputNormal(); - - /*! - \brief Sets class acq input to test. The class will provide a quadratic signal. It is useful for testing and not getting saturated signal all the time. - \return Integer indicating the success of the setting process (0-success, <0 error) - */ - ClassError::Error acqInputTest(); - - /*! - \brief Sends the request for battery level - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error batteryLevel(); - - /*! - \brief Sets the duration of the buffer (ms*frequency//1000.0) - \param ms Time in ms - \return Integer indicating the success of the setting process (0-success, <0 error) - */ - ClassError::Error bufferDuration(const double& ms); - - /*! - \brief Sets buzzer on - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error buzzerPlay(); - - /*! - \brief Sends the request for buzzer - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error buzzerStatus(); - - /*! - \brief Sets ms of buzzer song - \param tempo time in ms - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error buzzerTempo(const std::string& tempo); - - /*! - \brief Connect to the CLASS server - \param server_ip IP of the CLASS server - \param server_port of the CLASS server - \param local_ip IP in which the instance is listening for messages coming from CLASS server - \param local_port Port in which the instance is listening for messages coming from CLASS server - \return Integer indicating the success of the connection (0-success, <0 error) - */ - ClassError::Error connect(const std::string & server_ip, const int & server_port, const std::string & local_ip, const int & local_port); - - /*! - \brief Sends the request for device name - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error deviceName(); - - /*! - \brief Sets name of device - \param devicename Name of the device - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error devName(const std::string& devicename); - - /*! - \brief Disconnect from the CLASS server - \return Integer indicating the success of the disconnection (0-success, <0 error) - */ - ClassError::Error disconnect(); - - /*! - \brief Sets electrode pads number - \param id Electrode number/ID - \param pads_number Number of pads of the electrode - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error elecPads(const int& id, const int& pads_number); - - /*! - \brief Sends the request for firmware version - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error firmwareVersion(); - - /*! - \brief Sends the request for frequency - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error frequencyStatus(); - - /*! - \brief Sets the config for impedance acquisition - \param acq_frames Vector to fill with acqusition frames - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error getAcqFrames(std::vector<AcqFrame>& acq_frames); - - /*! - \brief Sends the request for hardware version - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error hardwareVersion(); - - /*! - \brief Sets high voltage to off - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error hvOff(); - - /*! - \brief Sets high voltage to on - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error hvOn(); - - /*! - \brief Sends the request for high voltage status - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error hvStatus(); - - /*! - \brief Sets high voltage value - \param hvvalue value of high voltage - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error hvValue(const std::string& hvvalue); - - /*! - \brief Sets the order of the filter used for impedance calculation - \param order Filter order (>0) - \return Integer indicating the success of the setting process (0-success, <0 error) - */ - ClassError::Error impedanceFilterOrder(const int& order); - - /*! - \brief Init communication - \param init_mode Initialization mode - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error initCommunication(const std::string& init_mode); - - /*! - \brief Sends the request for interval status - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error intervalStatus(); - - /*! - \brief Sets stimulation inter pulses interval - \param interval value of stimulation inter pulses - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error intervalValue(const std::string& interval); - - /*! - \brief Sets logevents to off - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error logeventsOff(); - - /*! - \brief Sets logevents to on - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error logeventsOn(); - - /*! - \brief Sends the request for logevents - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error logeventsStatus(); - - /*! - \brief Sets pattern - \param number pattern number - \param pat vector of patterns - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error pattern(const int& number, const std::vector<Pattern>& pat); - - /*! - \brief Clear pattern - \param id pattern to delete - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error patternClear(const int& id); - - /*! - \brief Sends the request for pattern - \param number pattern - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error patternStatus(const int& number); - - /*! - \brief Sends the request for pattern - \param number pattern - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error velecStatus(const int& number); - - /*! - \brief Sets rtc date - \param rtcdate mm/dd/yyyy format - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error rtcDate(const std::string& rtcdate); - - /*! - \brief Sends the request for rtc - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error rtcStatus(); - - /*! - \brief Sets rtc time - \param rtctime hh:mm:ss format - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error rtcTime(const std::string& rtctime); - - /*! - \brief Sends the request for sd function - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error sdfunctionStatus(); - - /*! - \brief Sets name of sdcard folder where the pattern are saved - \param sdfunctionname name of the folder - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error sdFunctionName(const std::string& sdfunctionname); - - /*! - \brief Sets name of sdcard folder where the folder containing the patterns is saved - \param sduname name of the folder - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error sdUName(const std::string& sduname); - - /*! - \brief Sends the request for sd user name - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error sdunameStatus(); - - /*! - \brief Sends custom command to CLASS device - \param cmd Command - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error sendCustomCmd(const std::string& cmd); - - /*! - \brief Sets class acq configuration - \param frequency Frequency of acquisition - \param channel_numbers Channel numbers - \param gain Gain of the acquisition - \param input Acq input type, normal or test - \return Integer indicating the success of the setting process (0-success, <0 error) - */ - ClassError::Error setAcqConfig(const double& frequency, const std::vector<int>& channel_numbers, const double& gain, const std::string& input); - - /*! - \brief Sets the callback - \param callback Callback - \param language Language which has defined the callback - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error setCallback(ClassCallback* callback, std::string language); - - /*! - \brief Turn off - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error shutdown(); - - /*! - \brief Sets frequency of the stimulation - \param frequency Frequency of the stimulation - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error stimFreq(const double& frequency); - - /*! - \brief Stops stimulation - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error stimOff(); - - /*! - \brief Starts stimulation - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error stimOn(); - - /*! - \brief Starts stimulation of the virtual electrode which name is passed as parameter - \param name Name of the virtual electrode - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error stimVelec(const std::string& name); - - /*! - \brief Sends the request for tic - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error tic(); - - /*! - \brief Handles the reception of an UDP msg from the CLASS server - \param msg Msg - \param ip IP the message comes from - */ - void udpMsgReceived(std::string msg, std::string client_ip); - - /*! - \brief Sets virtual electrode - \param number Virtual electrode number/ID - \param name Name of the virtual electrode - \param electrode_id Electrode ID/number - \param cathodes List of integers with the IDs of the pads acting as cathodes - \param anodes List of integers with the IDs of the pads acting as cathodes - \param amp List of doubles with the amplitude (in mA) associated to each of the cathodes. Must have the same length as cathodes - \param width List of integers with the pulse width (in uS) associated to each of the cathodes. Must have the same length as cathodes - \param selected Defines if the velec is selected (‘true’ value) or deselected (‘false’ value). Only selected velecs will be executed with a “stim on” command - \param sync Defines if the velec is synchronous (‘rtue’ value) or asynchronous (‘false’ value). - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error velec(const int& id, const std::string& name, const int& electrode_id, const std::vector<int>& cathodes, const std::vector<int>& anodes, const std::vector<double>& amp, const std::vector<int>& width, const bool& selected, const bool& sync); - - /*! - \brief Sets whether the virtual electrode is selected or not - \param id Virtual electrode number/ID - \param selected Defines if the velec is selected (‘true’ value) or deselected (‘false’ value). Only selected velecs will be executed with a “stim on” command. - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error velecSelected(const int& id, const bool& selected); - - /*! - \brief Sets whether the virtual electrodes are selected or not - \param id Vector with virtual electrode number/ID. - \param selected Vector of bool values indicating whether the virtual electrode is selected - \return Integer indicating the success of the process (0-success, <0 error) - */ - ClassError::Error velecsSelected(const std::vector<int>& id, const std::vector<bool>& selected); - -private: - /*! - \brief Sends msg through serial - \param msg Msg to send. - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error sendMsg(std::string msg); - - /*! - \brief Sends msgs through serial - \param msgs Vector with msgs to send. - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error sendMsgs(std::vector<std::string> msgs); - - /*! - \brief Updates buffer capacity based on acq rate and seconds - \param seconds Buffer duration. - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error setBufferCapacity(const double& seconds); - - /*! - \brief Configures the filter for impedance - \param order Order of the filter - \param low_freq Lower frequency of the band - \param up_freq Upper freqeuncy of the band - \param acq_freq Sampling rate - \return Integer indicating the success of the starting process (0-success, <0 error) - */ - ClassError::Error configureFilter(int order, double low_freq, double up_freq, double acq_freq); - - /*! - \brief Sets the number of digits after decimal - \param value Value - \param digits_after_decimal Number of desired digits after decimal - \return Pair with the new_value and a bool indicating whether the operation has enough precision - */ - std::pair< double, bool > trunc_n( double value, std::size_t digits_after_decimal); - - /*! - \brief Increases lock error number - */ - void increaseLockError(); - - //! UDP server where it listens for command coming form the CLASS server - UDPServer *udp_server_; - //! UDP client which sends messages to CLASS server - UDPClient *udp_client_; - - //! indicates the IP where the CLASS server is listening - std::string server_ip_; - //! indicates the port where the CLASS server is listening - int server_port_; - //! indicates the local IP where the instance is listening for messages coming from CLASS server - std::string local_ip_; - //! indicates the local port where the instance is listening for messages coming from CLASS server - int local_port_; - - //! defined electrodes - std::vector<Elec> electrodes_; - - //! defined virual electrodes - std::vector<Velec> virtual_electrodes_; - - //! indicates whether it is acquiring frames - bool is_acquiring_frames_; - //! is_acquiring_frames mutex - std::mutex is_acquiring_frames_mutex_; - //! number of channels - int channels_number_; - //! frequency of the acquisition - double acq_frequency_; - //! acquisition time (determinates buffer maximum size) - double acq_seconds_; - //! acquisition time - int acq_buffer_capacity_; - //! acquisition buffer - boost::circular_buffer<AcqFrame> acq_buffer_; - //! acquisition buffer mutex - std::mutex acq_buffer_mutex_; - - //! indicates whether it is acquiring impedances - bool is_acquiring_impedances_; - //! is_acquiring_frames mutex - std::mutex is_acquiring_impedances_mutex_; - //! acquisition time - int acq_impedances_buffer_capacity_; - //! acquisition buffer - boost::circular_buffer<AcqFrame> acq_impedances_buffer_; - //! acquisition buffer mutex - std::mutex acq_impedances_buffer_mutex_; - - //! butterworth filters, one per channel - Butterworth **butterworth_; - //! order of the filter - int filter_order_; - - //! thread for waiting messages - std::thread *waiting_thread_; - //! function for waiting messages - void waitingThreadFunction_(); - //! Indicates whether to wait for special messages - bool waiting_; - //! Mutex for waiting - std::mutex waiting_mutex_; - - //! indicates whether it is waiting for tic - bool tic_received_; - //! indicates whether tic has been received - bool is_waiting_tic_; - //! mutex for tic - std::mutex tic_mutex_; - - //! indicates whether it is waiting for firmware - bool firmware_received_; - //! indicates whether firmware has been received - bool is_waiting_firmware_; - //! mutex for firmware - std::mutex firmware_mutex_; - - //! indicates whether it is waiting for device name - bool device_received_; - //! indicates whether device name has been received - bool is_waiting_device_; - //! mutex for firmware - std::mutex device_mutex_; - - //! indicates whether it is waiting for hardware - bool hardware_received_; - //! indicates whether hardware has been received - bool is_waiting_hardware_; - //! mutex for hardware - std::mutex hardware_mutex_; - - //! indicates whether it is waiting for battery - bool battery_received_; - //! indicates whether battery has been received - bool is_waiting_battery_; - //! mutex for battery - std::mutex battery_mutex_; - - //! indicates whether it is waiting for logevents - bool logevents_received_; - //! indicates whether logevents has been received - bool is_waiting_logevents_; - //! mutex for logevents - std::mutex logevents_mutex_; - - //! indicates whether it is waiting for high voltage status - bool hvstatus_received_; - //! indicates whether high voltage status has been received - bool is_waiting_hvstatus_; - //! mutex for high voltage status - std::mutex hvstatus_mutex_; - - //! indicates whether it is waiting for interval status - bool intervalstatus_received_; - //! indicates whether interval status has been received - bool is_waiting_intervalstatus_; - //! mutex for interval status - std::mutex intervalstatus_mutex_; - - //! indicates whether it is waiting for rtc status - bool rtcstatus_received_; - //! indicates whether rtc status has been received - bool is_waiting_rtcstatus_; - //! mutex for rtc status - std::mutex rtcstatus_mutex_; - - //! indicates whether it is waiting for buzzer status - bool buzzerstatus_received_; - //! indicates whether buzzer status has been received - bool is_waiting_buzzerstatus_; - //! mutex for buzzer status - std::mutex buzzerstatus_mutex_; - - //! indicates whether it is waiting for frequency status - bool frequencystatus_received_; - //! indicates whether frequency status has been received - bool is_waiting_frequencystatus_; - //! mutex for frequency status - std::mutex frequencystatus_mutex_; - - //! indicates whether it is waiting for sdfunction - bool sdfunction_received_; - //! indicates whether sdfunction has been received - bool is_waiting_sdfunction_; - //! mutex for sdfunction - std::mutex sdfunction_mutex_; - - //! indicates whether it is waiting for sduname - bool sduname_received_; - //! indicates whether sduname has been received - bool is_waiting_sduname_; - //! mutex for sduname - std::mutex sduname_mutex_; - - //! indicates whether it is waiting for pattern - bool patternstatus_received_; - //! indicates whether pattern has been received - bool is_waiting_patternstatus_; - //! mutex for logevents - std::mutex patternstatus_mutex_; - - //! indicates whether it is waiting for velecstatus - bool velecstatus_received_; - //! indicates whether velecstatus has been received - bool is_waiting_velecstatus_; - //! mutex for velecstatus - std::mutex velecstatus_mutex_; - - //! battery level - double battery_; - // tic text - std::string tic_; - // firmware text - std::string firmware_; - // hardware text - std::string hardware_; - // device text - std::string device_; - // logevents text - std::string logevents_; - // high voltage status text - std::string hvstatus_; - // interval status text - std::string intervalstatus_; - // rtc status text - std::string rtcstatus_; - // buzzer status text - std::string buzzerstatus_; - // frequency status text - std::string frequencystatus_; - // sdfunction status text - std::string sdfunctionstatus_; - // sduname status text - std::string sdunamestatus_; - // pattern status text - std::string patternstatus_; - // velecstatus text - std::string velecstatus_; - - //! mutex for lock errors - std::mutex lock_error_mutex_; - //! number of lock errors - int lock_error_number_; - //! max number of lock errors before launching error - int lock_error_number_max_; - //! thread for lock errors - std::thread *lock_error_thread_; - //! function for lock error - void lockErrorThreadFunction_(); - //! Indicates whether to wait for special messages - bool error_waiting_; - - //! callback for battery, turn_off, etc. - ClassCallback* class_cb_; - //! Language which has defined the callback - std::string language_; - - //! acq gainb - double gain_; - - //! log function - #define log_stream_(x) log_(std::stringstream() << x); - #define log_warn_stream_(x) log_warn_(std::stringstream() << x); - #define log_error_stream_(x) log_err_(std::stringstream() << x); - void log_(const std::string &text); - void log_(const std::stringstream &text); - void log_(std::ostream & text); - void log_err_(const std::string &text); - void log_err_(std::ostream & text); - void log_warn_(const std::string &text); - void log_warn_(std::ostream & text); -}; - -/** - * @brief Class for locking when accesing callback in Python - */ -class PyThreadStateLock -{ -public: - PyThreadStateLock(void) - { - state = PyGILState_Ensure( ); - } - - ~PyThreadStateLock(void) - { - PyGILState_Release( state ); - } -private: - PyGILState_STATE state; -}; - -#endif // CLASS_CORE_HPP diff --git a/src/api/class/include/class/class_error.hpp b/src/api/class/include/class/class_error.hpp deleted file mode 100644 index 3626520..0000000 --- a/src/api/class/include/class/class_error.hpp +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @file class_error.hpp - * @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> and Leire Querejeta Lomas <leire.querejeta@tecnalia.com> - * @date 2020 - * - * Copyright 2020 Tecnalia Research & Innovation. - * Distributed under the GNU GPL v3. - * For full terms see https://www.gnu.org/licenses/gpl.txt - * - * @brief Errors thrown by the API methods - */ - -#pragma once - -//! Errors thrown by the API methods. -class ClassError -{ - public: - //! Enum for errors - enum Error - { - CLASS_NO_ERROR = 0, ///< No error - ERROR_CORE_NULL = -1, ///< Core object is null - ERROR_SENDING_MSG = -2, ///< Error sending msg to server - ERROR_NO_ELEC_WITH_ID = -3, ///< There is no electrode with the specified ID - ERROR_CATHODE_AMP_DIFFERENT_SIZE = -4, ///< Cathode and Amp vectors have different size - ERROR_NO_VELEC_WITH_ID = -5, ///< There is no virtual electrode with the specified ID - ERROR_NO_VELEC_WITH_NAME = -6, ///< There is no virtual electrode with the specified name - ERROR_DEVICE_NOT_ACQUIRING = -7, ///< The device is not acquiring frames/impedances. Have you configured for it? - ERROR_UDP_CLIENT_NULL = -8, ///< The sender object is null - ERROR_NOT_VALID_ORDER = -9, ///< Filter order is negative. It must be a integer > 0 - ERROR_NOT_VALID_MS = -10, ///< Duration for buffer is negative. It must be a double > 0 - ERROR_CB_NULL = -11, ///< Class callback is NULL. A callback must be set before getting any value - ERROR_DIF_VECTOR_SIZE = -12, ///< Vector sizes are different - ERROR_EMPTY_CATHODE = -13, ///< Cathode vector is empty - ERROR_EMPTY_ANODE = -14, ///< Cathode vector is empty - ERROR_NOT_VALID_CHANNEL_ID = -15, ///< Channel id is not in the limits (>=1 and <=16) - ERROR_NOT_VALID_VALUE = -16 ///< Out of range value - }; -}; \ No newline at end of file diff --git a/src/api/class/include/class/class_structures.hpp b/src/api/class/include/class/class_structures.hpp deleted file mode 100644 index 51ddb68..0000000 --- a/src/api/class/include/class/class_structures.hpp +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @file class_structures.hpp - * @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> and Leire Querejeta Lomas <leire.querejeta@tecnalia.com> - * @date 2020 - * - * Copyright 2020 Tecnalia Research & Innovation. - * Distributed under the GNU GPL v3. - * For full terms see https://www.gnu.org/licenses/gpl.txt - * - * @brief Structures interchanged through the API methods - */ - -#pragma once - -#include <vector> - -//! Structure for a acquisition frame -struct AcqFrame -{ - double timestamp; ///< timestamp of the acquisition frame - std::vector<double> values; ///< values for the different channels -}; - -//! Structure for pattern -struct Pattern -{ - std::string amp; - std::string pw; - std::string r; - int ampval; - int pwval; - int time; -}; diff --git a/src/api/class/src/.gitkeep b/src/api/class/src/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/api/class/src/class.cpp b/src/api/class/src/class.cpp deleted file mode 100644 index 867e09d..0000000 --- a/src/api/class/src/class.cpp +++ /dev/null @@ -1,597 +0,0 @@ -/** - * @file class.cpp - * @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> and Leire Querejeta Lomas <leire.querejeta@tecnalia.com> - * @date 2020 - * - * Copyright 2020 Tecnalia Research & Innovation. - * Distributed under the GNU GPL v3. - * For full terms see https://www.gnu.org/licenses/gpl.txt - * - * @brief Interface to the funcionalities of Class - */ - -#include "class/class.hpp" -#include <iostream> -#include <class/class_core.hpp> - -using namespace std; - -Class::Class() -{ - std::cout << "Class cstr" << std::endl; - core_ = new ClassCore(); -} - -Class::~Class() -{ - std::cout << "Class dstr" << std::endl; - if (core_ != NULL) - { - delete core_; - core_ = NULL; - } -} - -ClassError::Error Class::connect() -{ - std::string server_ip = DEFAULT_SERVER_IP; - int server_port = DEFAULT_SERVER_PORT; - - std::string local_ip = DEFAULT_LOCAL_IP; - int local_port = DEFAULT_LOCAL_PORT; - - return core_->connect(server_ip, server_port, local_ip, local_port); -} - -ClassError::Error Class::connect(const std::string & server_ip, const int & server_port) -{ - std::string local_ip = DEFAULT_LOCAL_IP; - int local_port = DEFAULT_LOCAL_PORT; - - return core_->connect(server_ip, server_port, local_ip, local_port); -} - -ClassError::Error Class::connect(const std::string & server_ip, const int & server_port, const int & local_port) -{ - std::string local_ip = DEFAULT_LOCAL_IP; - - return core_->connect(server_ip, server_port, local_ip, local_port); -} - -ClassError::Error Class::connect(const std::string & server_ip, const int & server_port, const std::string & local_ip, const int & local_port) -{ - return core_->connect(server_ip, server_port, local_ip, local_port); -} - -ClassError::Error Class::disconnect() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->disconnect(); -} - -ClassError::Error Class::setAcqConfig(const AcqFreq& frequency, const std::vector<int>& channel_numbers, const AcqGain& gain, const AcqInput& input) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - double freq = 0.0; - int channel_numb = 0; - double acq_gain = 0.0; - std::string input_str = "test"; - - switch(frequency) - { - case FREQ_250 : freq = 250; break; - case FREQ_500 : freq = 500; break; - case FREQ_1000 : freq = 1000; break; - case FREQ_2000 : freq = 2000; break; - case FREQ_4000 : freq = 4000; break; - case FREQ_8000 : freq = 8000; break; - } - - switch(gain) - { - case GAIN_1 : acq_gain = 1; break; - case GAIN_2 : acq_gain = 2; break; - case GAIN_4 : acq_gain = 4; break; - case GAIN_8 : acq_gain = 8; break; - case GAIN_12 : acq_gain = 12; break; - case GAIN_24 : acq_gain = 24; break; - } - - switch(input) - { - case NORMAL : input_str = "normal"; break; - case TEST : input_str = "test"; break; - } - - return core_->setAcqConfig(freq, channel_numbers, acq_gain, input_str); -} - -ClassError::Error Class::startAcq() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->acqOn(); -} - -ClassError::Error Class::startImpedanceAcq() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->acqImpedanceOn(); -} - -ClassError::Error Class::stopAcq() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->acqOff(); -} - -ClassError::Error Class::stopImpedanceAcq() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->acqImpedanceOff(); -} - -ClassError::Error Class::startAcqStream() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->acqStreamOn(); -} - -ClassError::Error Class::stopAcqStream() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->acqStreamOff(); -} - -ClassError::Error Class::setAcqInputNormal() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->acqInputNormal(); -} - -ClassError::Error Class::setAcqInputTest() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->acqInputTest(); -} - -ClassError::Error Class::initCommunication() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->initCommunication("DESKTOP"); -} - -ClassError::Error Class::initCommunication(const InitMode& mode) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - std::string init_mode = ""; - - switch(mode) - { - case TACTILITY: init_mode = "TACTILITY"; break; - case PAIN: init_mode = "PAIN"; break; - case DESKTOP: init_mode = "DESKTOP"; break; - case NEURORESEARCH: init_mode = "DESKTOP"; break; - case ICARE: init_mode = "ICARE"; break; - case TIB_ACQ: init_mode = "TIB_ACQ"; break; - case TIB_STIM: init_mode = "TIB_STIM"; break; - default: init_mode = "DESKTOP"; break; - } - - return core_->initCommunication(init_mode); -} - -std::vector<AcqFrame> Class::getAcqFrames() -{ - //std::cout << "getAcqFrames" << std::endl; - std::vector<AcqFrame> acqFrames; - - if (core_ == NULL) - return acqFrames; - - int output = core_->getAcqFrames(acqFrames); - - return acqFrames; -} - -ClassError::Error Class::patternDelete(const int& id) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->patternClear(id); -} - -ClassError::Error Class::getPatternStatus(const int& id) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->patternStatus(id); -} - -ClassError::Error Class::startStim() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->stimOn(); -} - -ClassError::Error Class::stopStim() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->stimOff(); -} - -ClassError::Error Class::setElecPads(const int& id, const int& pads_number) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->elecPads(id, pads_number); -} - -ClassError::Error Class::setStimFreq(const double& frequency) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->stimFreq(frequency); -} - -ClassError::Error Class::setDevName(const std::string& devicename) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->devName(devicename); -} - -ClassError::Error Class::sethvValue(const std::string& hvvalue) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->hvValue(hvvalue); -} - -ClassError::Error Class::sethvOn() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->hvOn(); -} - -ClassError::Error Class::sethvOff() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->hvOff(); -} - -ClassError::Error Class::setlogeventsOn() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->logeventsOn(); -} - -ClassError::Error Class::setlogeventsOff() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->logeventsOff(); -} - -ClassError::Error Class::setintervalValue(const std::string& interval) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->intervalValue(interval); -} - -ClassError::Error Class::setbuzzerTempo(const std::string& tempo) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->buzzerTempo(tempo); -} - -ClassError::Error Class::setbuzzerPlay() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->buzzerPlay(); -} - -ClassError::Error Class::setrtcDate(const std::string& rtcDate) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->rtcDate(rtcDate); -} - -ClassError::Error Class::setrtcTime(const std::string& rtcTime) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->rtcTime(rtcTime); -} - -ClassError::Error Class::setShutdown() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->shutdown(); -} - -ClassError::Error Class::setSDFunctionName(const std::string& sdfunctionname) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->sdFunctionName(sdfunctionname); -} - -ClassError::Error Class::setSDUName(const std::string& sduname) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->sdUName(sduname); -} - -ClassError::Error Class::setPattern(const int& id, const std::vector<Pattern>& pat) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->pattern(id,pat); -} - -ClassError::Error Class::setVelec(const int& id, const std::string& name, const int& electrode_id, const std::vector<int>& cathodes, const std::vector<int>& anodes, const std::vector<double>& amp, const std::vector<int>& width, const bool& selected, const bool& sync) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->velec(id, name, electrode_id, cathodes, anodes, amp, width, selected, sync); -} - -ClassError::Error Class::setVelecSelected(const int& id, const bool& selected) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->velecSelected(id, selected); -} - -ClassError::Error Class::setVelecsSelected(const std::vector<int>& id, const std::vector<bool>& selected) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->velecsSelected(id, selected); -} - -ClassError::Error Class::stimVelec(const std::string& name) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->stimVelec(name); -} - -ClassError::Error Class::getVelecStatus(const int& id) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->velecStatus(id); -} - -ClassError::Error Class::setAcqImpedanceConfig(const AcqImpedanceSign& type) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - bool positive = false; - switch(type) - { - case TYPE_POSITIVE : positive = true; break; - case TYPE_NEGATIVE : positive = false; break; - } - - return core_->acqImpedanceConfig(positive); -} - -ClassError::Error Class::setAcqImpedancePolarity(const AcqImpedancePolarity& polarity) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - bool unipolar = false; - switch(polarity) - { - case TYPE_UNIPOLAR : unipolar = true; break; - case TYPE_BIPOLAR : unipolar = false; break; - } - - return core_->acqImpedancePolarity(unipolar); -} - -ClassError::Error Class::setImpedanceFilterOrder(const int& order) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->impedanceFilterOrder(order); -} - -ClassError::Error Class::setBufferDuration(const double& ms) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->bufferDuration(ms); -} - -ClassError::Error Class::setCallback(ClassCallback * callback, std::string language) -{ - if (callback) - { - return core_->setCallback(callback, language); - } - else - { - return ClassError::ERROR_CB_NULL; - } -} - -ClassError::Error Class::getBattery() -{ - - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->batteryLevel(); -} - -ClassError::Error Class::getFirmware() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->firmwareVersion(); -} - -ClassError::Error Class::getDeviceName() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->deviceName(); -} - -ClassError::Error Class::getLogevents() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->logeventsStatus(); -} - -ClassError::Error Class::getHv() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->hvStatus(); -} - -ClassError::Error Class::getInterval() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->intervalStatus(); -} - -ClassError::Error Class::getRTC() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->rtcStatus(); -} - -ClassError::Error Class::getBuzzer() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->buzzerStatus(); -} - -ClassError::Error Class::getFrequency() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->frequencyStatus(); -} - -ClassError::Error Class::getHardware() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->hardwareVersion(); -} - -ClassError::Error Class::getSDFunction() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->sdfunctionStatus(); -} - -ClassError::Error Class::getSDUName() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->sdunameStatus(); -} - -ClassError::Error Class::getTic() -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->tic(); -} - -ClassError::Error Class::sendCustomCmd(const std::string& cmd) -{ - if (core_ == NULL) - return ClassError::ERROR_CORE_NULL; - - return core_->sendCustomCmd(cmd); -} \ No newline at end of file diff --git a/src/api/class/src/class_core.cpp b/src/api/class/src/class_core.cpp deleted file mode 100644 index 5cba6bb..0000000 --- a/src/api/class/src/class_core.cpp +++ /dev/null @@ -1,2415 +0,0 @@ -/** - * @file ClassCore.cpp - * @author Alfonso Domminguez <alfonso.dominguez@tecnalia.com> and Leire Querejeta Lomas <leire.querejeta@tecnalia.com> - * @date 2020 - * - * Copyright 2' Tecnalia Research & Innovation. - * Distributed under the GNU GPL v3. - * For full terms see https://www.gnu.org/licenses/gpl.txt - * - * @brief Interface to the funcionalities of Class. - */ - -#include <class/class_core.hpp> -#include <iostream> -#include <numeric> -#include <chrono> -#include <thread> -#include <regex> -#include <math.h> -#include <bitset> -#include "../../commands.h" - -const double ACQ_SECONDS_DEFAULT = 2.0; -const int FILTER_ORDER_DEFAULT = 2; -const int MSG_MAX_LENGTH = 1024; - -ClassCore::ClassCore() -{ - - udp_server_ = nullptr; - udp_client_ = nullptr; - butterworth_ = nullptr; - - channels_number_ = 0; - gain_ = 0.0; - acq_seconds_ = ACQ_SECONDS_DEFAULT; - acq_buffer_capacity_ = 0; - is_acquiring_frames_ = false; - filter_order_ = FILTER_ORDER_DEFAULT; - - waiting_ = false; - - is_waiting_battery_ = false; - battery_received_ = false; - battery_ = 0.0; - tic_ = ""; - firmware_ = ""; - hardware_ = ""; - device_ = ""; - logevents_ = ""; - hvstatus_ = ""; - intervalstatus_ = ""; - rtcstatus_ = ""; - buzzerstatus_ = ""; - frequencystatus_ = ""; - sdfunctionstatus_ = ""; - sdunamestatus_ = ""; - patternstatus_ = ""; - velecstatus_ = ""; - - lock_error_number_ = 0; - lock_error_number_max_ = 10; - - language_ = ""; - class_cb_ = nullptr; - waiting_thread_ = new std::thread(&ClassCore::waitingThreadFunction_, this); - - lock_error_thread_ = new std::thread(&ClassCore::lockErrorThreadFunction_, this); - // myfile_.open ("msgs.txt"); -} - -ClassCore::~ClassCore() -{ - if (udp_server_ != nullptr) - { - delete udp_server_; - } - - if (udp_client_ != nullptr) - { - delete udp_client_; - } - - if (butterworth_ != nullptr) - { - for (int i = 0; i < channels_number_; ++i) - { - delete butterworth_[i]; - } - delete[] butterworth_; - } - - waiting_mutex_.lock(); - waiting_ = false; - waiting_mutex_.unlock(); - - lock_error_mutex_.lock(); - error_waiting_ = false; - lock_error_mutex_.unlock(); - // myfile_.close(); -} - -ClassError::Error ClassCore::connect(const std::string &server_ip, const int &server_port, const std::string &local_ip, const int &local_port) -{ - if (udp_server_ != nullptr) - { - log_("[connect] There is already a local UDP server listening for messages coming from CLASS server"); - } - else - { - log_stream_("[connect] Starting local server on port " << local_port); - udp_server_ = new UDPServer(local_port); - local_port_ = local_port; - udp_server_->startListening(); - udp_server_->addListener((UdpServerListener *)this); - log_stream_("[connect] Waiting for CLASS server messages on local port " << local_port); - } - - std::string server_port_str; - std::stringstream ss; - ss << server_port; - server_port_str = ss.str(); - log_stream_("[connect] Creating a client which connects to CLASS server (IP " << server_ip << ":" << server_port_str << ")"); - udp_client_ = new UDPClient(server_ip, server_port_str, -1); - - ss.str(std::string()); - ss << "connect " << local_port_; - std::string msg = ss.str(); - - return sendMsg(msg); -} - -ClassError::Error ClassCore::disconnect() -{ - std::stringstream ss; - ss << "disconnect " << local_port_; - std::string msg = ss.str(); - - int response = sendMsg(msg); - if (response != 0) - { - return ClassError::ERROR_SENDING_MSG; - } - - log_("[disconnect] Stopping local UPD server..."); - if (udp_server_ != nullptr) - { - udp_server_->removeListener((UdpServerListener *)this); - udp_server_->stopListening(); - } - log_("[disconnect] Local UDP server stopped"); - - return ClassError::CLASS_NO_ERROR; -} - -ClassError::Error ClassCore::setAcqConfig(const double &frequency, const std::vector<int> &channel_numbers, const double &gain, const std::string &input) -{ - // log_stream_("[setAcqConfig]"); - gain_ = gain; - - if (butterworth_ != nullptr) - { - for (int i = 0; i < channels_number_; ++i) - { - if (butterworth_[i] != nullptr) - { - delete butterworth_[i]; - } - } - delete[] butterworth_; - } - - channels_number_ = channel_numbers.size(); - log_stream_("[setAcqConfig] Number of channels: " << channels_number_); - - int channels_int = 0; - - for (int i = 0; i < channel_numbers.size(); i++) - { - if (channel_numbers[i] < 1 || channel_numbers[i] > 16) - { - log_error_stream_("[setAcqConfig] Channel " << i << "has the id " << channel_numbers[i] << " which is not in the interval >=1 and <=16"); - return ClassError::ERROR_NOT_VALID_CHANNEL_ID; - } - channels_int += (int)(pow(2, channel_numbers[i] - 1)); - } - - std::stringstream stream; - stream << "0x" - << std::setfill('0') << std::setw(2) // 2 digits hex 0xAA (4 digits does not work) - << std::hex << channels_int; - std::string channels_hex_str(stream.str()); - log_stream_("[setAcqConfig] channels hex: " << channels_hex_str); - - acq_frequency_ = frequency; - - log_("[setAcqConfig] setBufferCapacity"); - setBufferCapacity(acq_seconds_); - - log_("[setAcqConfig] configureFilter"); - configureFilter(filter_order_, 30.2, 32.2, acq_frequency_); - - return sendMsg(commands::ClassCommands::CMD_CONFIGACQ + std::to_string(frequency) + commands::ClassCommands::CMD_CONFIGACQ_PARAM1 +channels_hex_str + commands::ClassCommands::CMD_CONFIGACQ_PARAM2 + std::to_string(gain) + commands::ClassCommands::CMD_CONFIGACQ_PARAM3 + input + commands::ClassCommands::CMD_AUX); -} - -ClassError::Error ClassCore::setBufferCapacity(const double &seconds) -{ - acq_seconds_ = seconds; - if (acq_buffer_mutex_.try_lock()) - { - acq_buffer_.clear(); - acq_buffer_capacity_ = (int)(acq_frequency_ * acq_seconds_); - acq_buffer_.set_capacity(acq_buffer_capacity_); - acq_buffer_mutex_.unlock(); - } - else - { - log_warn_stream_("[setBufferCapacity] lock not acquired"); - increaseLockError(); - } - - return ClassError::CLASS_NO_ERROR; -} - -ClassError::Error ClassCore::configureFilter(int order, double low_freq, double up_freq, double acq_freq) -{ - filter_order_ = order; - double low = low_freq / (acq_freq / 2.0); - double up = up_freq / (acq_freq / 2.0); - - if (butterworth_ != nullptr) - { - for (int i = 0; i < channels_number_; ++i) - { - delete butterworth_[i]; - } - delete[] butterworth_; - } - - butterworth_ = new Butterworth *[channels_number_]; - for (int i = 0; i < channels_number_; ++i) - { - butterworth_[i] = new Butterworth(); - butterworth_[i]->configure(order, low, up); - } - - return ClassError::CLASS_NO_ERROR; -} - -ClassError::Error ClassCore::acqOn() -{ - ClassError::Error response = sendMsg(commands::ClassCommands::CMD_BUFFERFLUSH); - if (response != ClassError::CLASS_NO_ERROR) - { - return ClassError::ERROR_SENDING_MSG; - } - - if (acq_buffer_mutex_.try_lock()) - { - acq_buffer_.clear(); - acq_buffer_mutex_.unlock(); - } - else - { - log_warn_stream_("[acqOn] lock not acquired"); - increaseLockError(); - } - - is_acquiring_frames_mutex_.lock(); - is_acquiring_frames_ = true; - is_acquiring_frames_mutex_.unlock(); - - return sendMsg(commands::ClassCommands::CMD_ONACQ); -} - -ClassError::Error ClassCore::acqOff() -{ - is_acquiring_frames_mutex_.lock(); - is_acquiring_frames_ = false; - is_acquiring_frames_mutex_.unlock(); - - return sendMsg(commands::ClassCommands::CMD_OFFACQ); -} - -ClassError::Error ClassCore::acqImpedanceOn() -{ - ClassError::Error response = sendMsg(commands::ClassCommands::CMD_BUFFERFLUSH); - if (response != ClassError::CLASS_NO_ERROR) - { - return ClassError::ERROR_SENDING_MSG; - } - - if (acq_buffer_mutex_.try_lock()) - { - acq_buffer_.clear(); - acq_buffer_mutex_.unlock(); - } - else - { - log_warn_stream_("[acqImpedanceOn] lock not acquired"); - increaseLockError(); - } - - is_acquiring_impedances_mutex_.lock(); - is_acquiring_impedances_ = true; - is_acquiring_impedances_mutex_.unlock(); - - std::vector<std::string> msg_list; - msg_list.push_back(commands::ClassCommands::CMD_ONIMPEDANCEACQ); - msg_list.push_back(commands::ClassCommands::CMD_ONACQ); - - return sendMsgs(msg_list); -} - -ClassError::Error ClassCore::acqImpedanceOff() -{ - is_acquiring_impedances_mutex_.lock(); - is_acquiring_impedances_ = false; - is_acquiring_impedances_mutex_.unlock(); - - std::vector<std::string> msg_list; - msg_list.push_back(commands::ClassCommands::CMD_OFFIMPEDANCEACQ); - msg_list.push_back(commands::ClassCommands::CMD_OFFACQ); - - return sendMsgs(msg_list); -} - -ClassError::Error ClassCore::acqStreamOn() -{ - return sendMsg(commands::ClassCommands::CMD_STREAMONACQ); -} - -ClassError::Error ClassCore::acqStreamOff() -{ - return sendMsg(commands::ClassCommands::CMD_STREAMOFFACQ); -} - -ClassError::Error ClassCore::acqInputNormal() -{ - return sendMsg(commands::ClassCommands::CMD_NORMALACQ); -} - -ClassError::Error ClassCore::acqInputTest() -{ - return sendMsg(commands::ClassCommands::CMD_TESTACQ); -} - -ClassError::Error ClassCore::stimOn() -{ - return sendMsg(commands::ClassCommands::CMD_ONSTIM); -} - -ClassError::Error ClassCore::stimOff() -{ - return sendMsg(commands::ClassCommands::CMD_OFFSTIM); -} - -ClassError::Error ClassCore::elecPads(const int &id, const int &pads_number) -{ - // chek whether the electrode exists. If so, update, if not insert - int electrode_index = -1; - for (unsigned int i = 0; i < electrodes_.size(); i++) - { - Elec elec = electrodes_[i]; - if (elec.id == id) - { - electrode_index = i; - break; - } - } - - if (electrode_index >= 0) - { - electrodes_[electrode_index].pads_number = pads_number; - } - else - { - Elec elec; - elec.id = id; - elec.pads_number = pads_number; - - electrodes_.push_back(elec); - } - - return sendMsg(commands::ClassCommands::CMD_CONFIGELEC + std::to_string(id) + commands::ClassCommands::CMD_CONFIGACQ_PARAM1 + std::to_string(pads_number) + commands::ClassCommands::CMD_AUX); -} - -ClassError::Error ClassCore::stimFreq(const double &frequency) -{ - if(frequency < FREQ_MIN_VALUE || frequency > FREQ_MAX_VALUE) - { - return ClassError::ERROR_NOT_VALID_VALUE; - } - else - { - return sendMsg(commands::ClassCommands::CMD_SETFREQ + std::to_string(frequency) + commands::ClassCommands::CMD_AUX); - } -} - -ClassError::Error ClassCore::devName(const std::string& devicename) -{ - return sendMsg(commands::ClassCommands::CMD_SETDEVICENAME + devicename + commands::ClassCommands::CMD_AUX); -} - -ClassError::Error ClassCore::hvValue(const std::string& hvvalue) -{ - if(std::stoi(hvvalue) < HV_MIN_VALUE || std::stoi(hvvalue) > HV_MAX_VALUE) - { - return ClassError::ERROR_NOT_VALID_VALUE; - } - else - { - return sendMsg(commands::ClassCommands::CMD_SETHV + hvvalue + commands::ClassCommands::CMD_AUX); - } -} - -ClassError::Error ClassCore::hvOn() -{ - return sendMsg(commands::ClassCommands::CMD_ONHV); -} - -ClassError::Error ClassCore::hvOff() -{ - return sendMsg(commands::ClassCommands::CMD_OFFHV); -} - -ClassError::Error ClassCore::logeventsOn() -{ - return sendMsg(commands::ClassCommands::CMD_ONLOGEVENTS); -} - -ClassError::Error ClassCore::logeventsOff() -{ - return sendMsg(commands::ClassCommands::CMD_OFFLOGEVENTS); -} - -ClassError::Error ClassCore::intervalValue(const std::string& interval) -{ - if(std::stoi(interval) < INTERVAL_MIN_VALUE || std::stoi(interval) > INTERVAL_MAX_VALUE) - { - return ClassError::ERROR_NOT_VALID_VALUE; - } - else - { - return sendMsg(commands::ClassCommands::CMD_SETINTERVAL + interval + commands::ClassCommands::CMD_AUX); - } -} - -ClassError::Error ClassCore::buzzerTempo(const std::string& tempo) -{ - if(std::stoi(tempo) < BUZZER_MIN_VALUE || std::stoi(tempo) > BUZZER_MAX_VALUE) - { - return ClassError::ERROR_NOT_VALID_VALUE; - } - else - { - return sendMsg(commands::ClassCommands::CMD_SETBUZZER + tempo + commands::ClassCommands::CMD_AUX); - } -} - -ClassError::Error ClassCore::buzzerPlay() -{ - return sendMsg(commands::ClassCommands::CMD_PLAYBUZZER); -} - -ClassError::Error ClassCore::rtcDate(const std::string& rtcDate) -{ - return sendMsg(commands::ClassCommands::CMD_SETRTCDATE + rtcDate + commands::ClassCommands::CMD_AUX); -} - -ClassError::Error ClassCore::rtcTime(const std::string& rtcTime) -{ - return sendMsg(commands::ClassCommands::CMD_SETRTCTIME + rtcTime + commands::ClassCommands::CMD_AUX); -} - -ClassError::Error ClassCore::shutdown() -{ - return sendMsg(commands::ClassCommands::CMD_SWITCHOFF); -} - -ClassError::Error ClassCore::sdFunctionName(const std::string& sdfunctionname) -{ - return sendMsg(commands::ClassCommands::CMD_SETSDFUNCTION + sdfunctionname + commands::ClassCommands::CMD_AUX); -} - -ClassError::Error ClassCore::sdUName(const std::string& sduname) -{ - return sendMsg(commands::ClassCommands::CMD_SETSDNAME + sduname + commands::ClassCommands::CMD_AUX); -} - -ClassError::Error ClassCore::pattern(const int &id, const std::vector<Pattern> &pat) -{ - sendMsg(commands::ClassCommands::CMD_CONFIGPATTERN + std::to_string(id) + commands::ClassCommands::CMD_CONFIGACQ_PARAM1 + commands::ClassCommands::CMD_AUX); - - std::this_thread::sleep_for(std::chrono::milliseconds(300)); - for(int i = 0; i < pat.size() ; i++) - { - - sendMsg(commands::ClassCommands::CMD_CONFIGPATTERN + std::to_string(id) + commands::ClassCommands::CMD_CONFIGPATTERN_PARAM2 + pat[i].amp + " " + pat[i].pw + " " + pat[i].r + " " + std::to_string(pat[i].ampval) + " " + std::to_string(pat[i].pwval) + " " + std::to_string(pat[i].time) + commands::ClassCommands::CMD_AUX); - std::this_thread::sleep_for(std::chrono::milliseconds(300)); - } - - return sendMsg(commands::ClassCommands::CMD_CONFIGPATTERN + std::to_string(id) + commands::ClassCommands::CMD_CONFIGACQ_PARAM3 + commands::ClassCommands::CMD_AUX); - -} - -ClassError::Error ClassCore::patternClear(const int& id) -{ - return sendMsg(commands::ClassCommands::CMD_CONFIGPATTERN + std::to_string(id) + commands::ClassCommands::CMD_CONFIGACQ_PARAM1 + commands::ClassCommands::CMD_AUX); -} - -ClassError::Error ClassCore::velec(const int &id, const std::string &name, const int &electrode_id, const std::vector<int> &cathodes, const std::vector<int> &anodes, const std::vector<double> &, const std::vector<int> &width, const bool &selected, const bool &sync) -{ - // check whether there is an electrode in the list with id=electrode_id - int electrode_index = -1; - for (unsigned int i = 0; i < electrodes_.size(); i++) - { - Elec elec = electrodes_[i]; - if (elec.id == electrode_id) - { - electrode_index = i; - break; - } - } - - if (electrode_index < 0) - { - log_warn_stream_("[velec] No electrode with id: " << electrode_id << " configured from this client."); - } - - // check whether there is an virtual_electrode in the list with id=id - int virtual_electrode_index = -1; - for (unsigned int i = 0; i < virtual_electrodes_.size(); i++) - { - Velec elec = virtual_electrodes_[i]; - if (elec.id == id) - { - virtual_electrode_index = i; - break; - } - } - - if (virtual_electrode_index < 0) - { - // new velec - Velec elec; - elec.id = id; - elec.selected = selected; - elec.name = name; - - virtual_electrodes_.push_back(elec); - } - else - { - // update velec - virtual_electrodes_[electrode_index].selected = selected; - virtual_electrodes_[electrode_index].name = name; - } - - if (cathodes.size() == 0) - { - log_err_("[virtualElectrode] Cathode vector is empty"); - return ClassError::ERROR_EMPTY_CATHODE; - } - - if (anodes.size() == 0) - { - log_err_("[virtualElectrode] Anode vector is empty"); - return ClassError::ERROR_EMPTY_ANODE; - } - - // check whether the length of cathodes, amp and width are the same - if (cathodes.size() != amp.size() || cathodes.size() != width.size()) - { - log_err_("[virtualElectrode] Cathode, amp and width vector sizes are different"); - return ClassError::ERROR_CATHODE_AMP_DIFFERENT_SIZE; - } - - std::stringstream ss; - ss << "velec " << id << " *name " << name << " *elec " << electrode_id << " *pads "; - //commands::ClassCommands::CMD_CONFIGVELEC + id + commands::ClassCommands::CMD_CONFIGVELEC_PARAM1 + name + commands::ClassCommands::CMD_CONFIGVELEC_PARAM2 + electrode_id + commands::ClassCommands::CMD_CONFIGVELEC_PARAM3 - for (unsigned int i = 0; i < cathodes.size(); i++) - { - ss << cathodes[i] << "=C" - << ","; - } - for (unsigned int i = 0; i < anodes.size(); i++) - { - ss << anodes[i] << "=A" - << ","; - } - - ss << " *amp "; - for (unsigned int i = 0; i < amp.size(); i++) - { - ss << cathodes[i] << "=" << amp[i] << ","; - } - ss << " *width "; - for (unsigned int i = 0; i < width.size(); i++) - { - ss << cathodes[i] << "=" << width[i] << ","; - } - ss << " *selected "; - if (selected) - { - ss << "1"; - } - else - { - ss << "0"; - } - ss << " *sync "; - if (sync) - { - ss << "1"; - } - else - { - ss << "0"; - } - ss << "\r\n"; - std::string msg = ss.str(); - - return sendMsg(msg); -} - -ClassError::Error ClassCore::velecSelected(const int &id, const bool &selected) -{ - int virtual_electrode_index = -1; - for (unsigned int i = 0; i < virtual_electrodes_.size(); i++) - { - Velec elec = virtual_electrodes_[i]; - if (elec.id == id) - { - virtual_electrode_index = i; - break; - } - } - - if (virtual_electrode_index < 0) - { - log_warn_stream_("[velecSelected] No virtual electrode with id: " << id << " configured from this client."); - } - - if(selected) - { - return sendMsg(commands::ClassCommands::CMD_CONFIGVELEC + std::to_string(id) + commands::ClassCommands::CMD_CONFIGVELEC_PARAM4 + commands::ClassCommands::CMD_VELEC_SELECTED + commands::ClassCommands::CMD_AUX); - } - else - { - return sendMsg(commands::ClassCommands::CMD_CONFIGVELEC + std::to_string(id) + commands::ClassCommands::CMD_CONFIGVELEC_PARAM4 + commands::ClassCommands::CMD_VELEC_NOTSELECTED + commands::ClassCommands::CMD_AUX); - } -} - -ClassError::Error ClassCore::velecsSelected(const std::vector<int> &id, const std::vector<bool> &selected) -{ - if (id.size() != selected.size()) - { - return ClassError::ERROR_DIF_VECTOR_SIZE; - } - - std::vector<std::string> msg_list; - - for (int velecIndex = 0; velecIndex < id.size(); velecIndex++) - { - int virtual_electrode_index = -1; - for (unsigned int i = 0; i < virtual_electrodes_.size(); i++) - { - Velec elec = virtual_electrodes_[i]; - if (elec.id == id[velecIndex]) - { - virtual_electrode_index = i; - break; - } - } - - if (virtual_electrode_index < 0) - { - log_warn_stream_("[velecsSelected] No virtual electrode with id: " << id[velecIndex] << "configured from this client."); - } - - if (selected[velecIndex]) - { - msg_list.push_back(commands::ClassCommands::CMD_CONFIGVELEC + std::to_string(id[velecIndex]) + commands::ClassCommands::CMD_CONFIGVELEC_PARAM4 + commands::ClassCommands::CMD_VELEC_SELECTED + commands::ClassCommands::CMD_AUX); - } - else - { - msg_list.push_back(commands::ClassCommands::CMD_CONFIGVELEC + std::to_string(id[velecIndex]) + commands::ClassCommands::CMD_CONFIGVELEC_PARAM4 + commands::ClassCommands::CMD_VELEC_NOTSELECTED + commands::ClassCommands::CMD_AUX); //ss << "0"; - } - } - return sendMsgs(msg_list); -} - -ClassError::Error ClassCore::stimVelec(const std::string &name) -{ - int virtual_electrode_index = -1; - for (unsigned int i = 0; i < virtual_electrodes_.size(); i++) - { - Velec elec = virtual_electrodes_[i]; - if (elec.name == name) - { - virtual_electrode_index = i; - break; - } - } - - if (virtual_electrode_index < 0) - { - log_warn_stream_("[velecSelected] No virtual electrode with name: " << name << " condifgured from this client."); - } - - return sendMsg(commands::ClassCommands::CMD_VELECSTIM + name + commands::ClassCommands::CMD_AUX); - -} - -ClassError::Error ClassCore::acqImpedanceConfig(const bool &positive) -{ - std::stringstream ss; - ss << "acq impedance_config *channels "; - if (positive) - { - ss << "positives"; - } - else - { - ss << "negatives"; - } - ss << "\r\n"; - std::string msg = ss.str(); - - return sendMsg(msg); -} - -ClassError::Error ClassCore::acqImpedancePolarity(const bool &unipolar) -{ - std::stringstream ss; - ss << "acq config type "; - if (unipolar) - { - ss << "unipolar"; - } - else - { - ss << "bipolar"; - } - ss << "\r\n"; - std::string msg = ss.str(); - - return sendMsg(msg); -} - -ClassError::Error ClassCore::initCommunication(const std::string &init_mode) -{ - return sendMsg(commands::ClassCommands::CMD_INIT + init_mode + commands::ClassCommands::CMD_AUX); -} - -ClassError::Error ClassCore::getAcqFrames(std::vector<AcqFrame> &acq_frames) -{ - bool is_acquiring_frames = false; - is_acquiring_frames_mutex_.lock(); - is_acquiring_frames = is_acquiring_frames_; - is_acquiring_frames_mutex_.unlock(); - - bool is_acquiring_impedances = false; - is_acquiring_impedances_mutex_.lock(); - is_acquiring_impedances = is_acquiring_impedances_; - is_acquiring_impedances_mutex_.unlock(); - - if (!is_acquiring_frames && !is_acquiring_impedances) - { - log_stream_("[getAcqFrames] The device is not acquiring frames"); - return ClassError::ERROR_DEVICE_NOT_ACQUIRING; - } - - acq_frames.clear(); - if (acq_buffer_mutex_.try_lock()) - { - for (int i = 0; i < acq_buffer_.size(); i++) - { - acq_frames.push_back(acq_buffer_[i]); - } - acq_buffer_mutex_.unlock(); - } - else - { - log_warn_stream_("[getAcqFrames] lock not acquired"); - increaseLockError(); - } - - return ClassError::CLASS_NO_ERROR; -} - -ClassError::Error ClassCore::sendMsg(std::string msg) -{ - log_stream_("[sendMsg] Msg:" << msg); - if (udp_client_ == nullptr) - { - return ClassError::ERROR_UDP_CLIENT_NULL; - } - udp_client_->send(msg); - - // log_stream_("[sendMsg] Msg sent"); - - return ClassError::CLASS_NO_ERROR; -} - -ClassError::Error ClassCore::sendMsgs(std::vector<std::string> msgs) -{ - // wrap msgs in a string until MSG_MAX_LENGTH - std::string string_with_msgs_prev = ""; - std::stringstream ss; - int msg_index = 0; - while (msg_index < msgs.size()) - { - ss << msgs[msg_index]; - std::string string_with_msgs = ss.str(); - - if (string_with_msgs.length() > MSG_MAX_LENGTH) - { - // clear stringstream - ss.str(std::string()); - // send prev and do not increase index - ClassError::Error response = sendMsg(string_with_msgs_prev); - if (response != ClassError::CLASS_NO_ERROR) - { - return response; - } - - string_with_msgs_prev = ""; - } - else - { - // continue concatenating - msg_index += 1; - string_with_msgs_prev = string_with_msgs; - } - } - - if (string_with_msgs_prev.length() != 0) - { - ClassError::Error response = sendMsg(string_with_msgs_prev); - if (response != ClassError::CLASS_NO_ERROR) - { - return response; - } - } - - return ClassError::CLASS_NO_ERROR; -} - -void ClassCore::udpMsgReceived(std::string msg, std::string ip) -{ - // processing depends on the status - //log_stream_("[udpMsgReceived] Msg received from CLASS server: " << msg << ". Msg length: " << msg.length()); - - tic_ = ""; - firmware_ = ""; - device_ = ""; - logevents_ = ""; - - - std::string temphvstatus; - if (!hvstatus_.empty()){ - temphvstatus = hvstatus_; - } else { - temphvstatus = ""; - } - hvstatus_ = ""; - - intervalstatus_ = ""; - rtcstatus_ = ""; - buzzerstatus_ = ""; - frequencystatus_ = ""; - sdfunctionstatus_ = ""; - sdunamestatus_ = ""; - patternstatus_ = ""; - hardware_ = ""; - velecstatus_ = ""; - - std::string battery_capacity_str = "battery *capacity="; - std::size_t found = msg.find(battery_capacity_str); - - if (msg.find("tic ") != std::string::npos) - { - tic_ = msg; - - double is_waiting_tic = false; - if (tic_mutex_.try_lock()) - { - is_waiting_tic = is_waiting_tic_; - tic_mutex_.unlock(); - } - - if (is_waiting_tic) - { - if (tic_mutex_.try_lock()) - { - tic_ = msg; - tic_received_ = true; - tic_mutex_.unlock(); - } - } - } - else if (msg.find("firmware") != std::string::npos) - { - // firmware_ = msg; - double is_waiting_firmware = false; - if (firmware_mutex_.try_lock()) - { - is_waiting_firmware = is_waiting_firmware_; - firmware_mutex_.unlock(); - } - - if (is_waiting_firmware) - { - if (firmware_mutex_.try_lock()) - { - firmware_ = msg; - firmware_received_ = true; - firmware_mutex_.unlock(); - } - } - } - else if (msg.find("velec ") != std::string::npos && (msg.find("*name ") == std::string::npos)) - { - double is_waiting_velecstatus = false; - if (velecstatus_mutex_.try_lock()) - { - is_waiting_velecstatus = is_waiting_velecstatus_; - velecstatus_mutex_.unlock(); - } - if (is_waiting_velecstatus) - { - if (velecstatus_mutex_.try_lock()) - { - velecstatus_ = msg; - velecstatus_received_ = true; - velecstatus_mutex_.unlock(); - } - } - } - else if (msg.find("device") != std::string::npos) - { - double is_waiting_device = false; - if (device_mutex_.try_lock()) - { - is_waiting_device = is_waiting_device_; - device_mutex_.unlock(); - } - - if (is_waiting_device) - { - if (device_mutex_.try_lock()) - { - device_ = msg; - device_received_ = true; - device_mutex_.unlock(); - } - } - } - else if (msg.find("hardware ") != std::string::npos) - { - //hardware_ = msg; - double is_waiting_hardware = false; - if (hardware_mutex_.try_lock()) - { - is_waiting_hardware = is_waiting_hardware_; - hardware_mutex_.unlock(); - } - - if (is_waiting_hardware) - { - if (hardware_mutex_.try_lock()) - { - hardware_ = msg; - hardware_received_ = true; - hardware_mutex_.unlock(); - } - } - } - else if (msg.find("logevents ") != std::string::npos) - { - double is_waiting_logevents = false; - if (logevents_mutex_.try_lock()) - { - is_waiting_logevents = is_waiting_logevents_; - logevents_mutex_.unlock(); - } - if (is_waiting_logevents) - { - if (logevents_mutex_.try_lock()) - { - logevents_ = msg; - logevents_received_ = true; - logevents_mutex_.unlock(); - } - } - } - else if (msg.find("hv ") != std::string::npos) - { - double is_waiting_hvstatus = false; - if (hvstatus_mutex_.try_lock()) - { - is_waiting_hvstatus = is_waiting_hvstatus_; - hvstatus_mutex_.unlock(); - } - if (is_waiting_hvstatus) - { - if (hvstatus_mutex_.try_lock()) - { - hvstatus_ = temphvstatus + msg; - hvstatus_received_ = true; - hvstatus_mutex_.unlock(); - } - } - } - else if (msg.find("interval ") != std::string::npos) - { - double is_waiting_intervalstatus = false; - if (intervalstatus_mutex_.try_lock()) - { - is_waiting_intervalstatus = is_waiting_intervalstatus_; - intervalstatus_mutex_.unlock(); - } - if (is_waiting_intervalstatus) - { - if (intervalstatus_mutex_.try_lock()) - { - intervalstatus_ = msg; - intervalstatus_received_ = true; - intervalstatus_mutex_.unlock(); - } - } - } - else if (msg.find("rtc ") != std::string::npos) - { - double is_waiting_rtcstatus = false; - if (rtcstatus_mutex_.try_lock()) - { - is_waiting_rtcstatus = is_waiting_rtcstatus_; - rtcstatus_mutex_.unlock(); - } - if (is_waiting_rtcstatus) - { - if (rtcstatus_mutex_.try_lock()) - { - rtcstatus_ = msg; - rtcstatus_received_ = true; - rtcstatus_mutex_.unlock(); - } - } - } - else if (msg.find("buzzer ") != std::string::npos) - { - double is_waiting_buzzerstatus = false; - if (buzzerstatus_mutex_.try_lock()) - { - is_waiting_buzzerstatus = is_waiting_buzzerstatus_; - buzzerstatus_mutex_.unlock(); - } - if (is_waiting_buzzerstatus) - { - if (buzzerstatus_mutex_.try_lock()) - { - buzzerstatus_ = msg; - buzzerstatus_received_ = true; - buzzerstatus_mutex_.unlock(); - } - } - } - else if (msg.find("freq ") != std::string::npos) - { - double is_waiting_frequencystatus = false; - if (frequencystatus_mutex_.try_lock()) - { - is_waiting_frequencystatus = is_waiting_frequencystatus_; - frequencystatus_mutex_.unlock(); - } - if (is_waiting_frequencystatus) - { - if (frequencystatus_mutex_.try_lock()) - { - frequencystatus_ = msg; - frequencystatus_received_ = true; - frequencystatus_mutex_.unlock(); - } - } - } - else if (msg.find("function ") != std::string::npos) - { - double is_waiting_sdfunction = false; - if (sdfunction_mutex_.try_lock()) - { - is_waiting_sdfunction = is_waiting_sdfunction_; - sdfunction_mutex_.unlock(); - } - if (is_waiting_sdfunction) - { - if (sdfunction_mutex_.try_lock()) - { - sdfunctionstatus_ = msg; - sdfunction_received_ = true; - sdfunction_mutex_.unlock(); - } - } - } - else if (msg.find("uname ") != std::string::npos) - { - double is_waiting_sduname = false; - if (sduname_mutex_.try_lock()) - { - is_waiting_sduname = is_waiting_sduname_; - sduname_mutex_.unlock(); - } - if (is_waiting_sduname) - { - if (sduname_mutex_.try_lock()) - { - sdunamestatus_ = msg; - sduname_received_ = true; - sduname_mutex_.unlock(); - } - } - } - else if (msg.find("pattern ") != std::string::npos) - { - double is_waiting_patternstatus = false; - if (patternstatus_mutex_.try_lock()) - { - is_waiting_patternstatus = is_waiting_patternstatus_; - patternstatus_mutex_.unlock(); - } - if (is_waiting_patternstatus) - { - if (patternstatus_mutex_.try_lock()) - { - patternstatus_ = msg; - patternstatus_received_ = true; - patternstatus_mutex_.unlock(); - } - } - } - else if (msg.find("battery *capacity=") != std::string::npos) - { - // log_stream_("[udpMsgReceived] battery msg received:" << msg); - double is_waiting_battery = false; - if (battery_mutex_.try_lock()) - { - is_waiting_battery = is_waiting_battery_; - battery_mutex_.unlock(); - } - - if (is_waiting_battery) - { - // log_stream_("[udpMsgReceived] extract battery level"); - // extract battery level - double battery; - - std::size_t found_space = msg.find(" ", found + battery_capacity_str.length()); - if (found_space != std::string::npos) - { - std::string battery_str = msg.substr(found + battery_capacity_str.length(), found_space - (found + battery_capacity_str.length())); - - // log_stream_("[udpMsgReceived] battery str: " << battery_str); - - try - { - battery = std::stod(battery_str); - // log_stream_("[udpMsgReceived] battery: " << battery); - if (battery_mutex_.try_lock()) - { - battery_ = battery; - battery_received_ = true; - battery_mutex_.unlock(); - } - } - catch (std::exception ex) - { - log_error_stream_("[udpMsgReceived] battery. cannot convert to double: " << battery_str); - return; - } - } - } - } - else if (msg.find("turnning off") != std::string::npos) - { - log_stream_("[udpMsgReceived] turn off received:" << msg); - - if (class_cb_ != nullptr) - { - if (language_ == "python") - { - class PyThreadStateLock PyThreadLock; // fix segmentation fault - class_cb_->turnOffHandle(); - } - else - { - class_cb_->turnOffHandle(); - } - } - } - else - { - bool is_acquiring_frames = false; - is_acquiring_frames_mutex_.lock(); - is_acquiring_frames = is_acquiring_frames_; - is_acquiring_frames_mutex_.unlock(); - - bool is_acquiring_impedances = false; - is_acquiring_impedances_mutex_.lock(); - is_acquiring_impedances = is_acquiring_impedances_; - is_acquiring_impedances_mutex_.unlock(); - - if (is_acquiring_frames || is_acquiring_impedances) - { - // myfile_ << msg; - int expected_bytes = 3 + 1 + 2 + channels_number_ * (1 + 3); - if (msg.length() != expected_bytes) - { - // log_warn_stream_("[udpMsgReceived] The message , which length is " << msg.length() << ", does not have the expected length " << expected_bytes << ". Msg:" << msg); - return; - } - - std::string timestamp_bin = msg.substr(0, 6); - - if (timestamp_bin.substr(3, 1) != ".") - { - // log_warn_stream_("[udpMsgReceived] Timestamp part does not have a point: " << msg); - return; - } - - AcqFrame frame; - - try - { - std::string timestamp_ms_bin = timestamp_bin.substr(0, 3); - int timestamp_ms = ((unsigned char)(timestamp_ms_bin.at(0)) << 16) + ((unsigned char)(timestamp_ms_bin.at(1)) << 8) + (unsigned char)(timestamp_ms_bin.at(2)); - - std::string timestamp_us_bin = timestamp_bin.substr(4, 2); - int timestamp_us = ((unsigned char)(timestamp_us_bin.at(0)) << 8) + (unsigned char)(timestamp_us_bin.at(1)); - - std::stringstream timestamp_stream; - timestamp_stream << timestamp_ms << "." << timestamp_us; - std::string timestamp_str(timestamp_stream.str()); - - frame.timestamp = std::stod(timestamp_str); - - int starting_index = 7; - for (int i = 0; i < channels_number_; i++) - { - std::string data_bin_str = msg.substr(starting_index, 3); - int data_bin = ((unsigned char)(data_bin_str.at(0)) << 16) + ((unsigned char)(data_bin_str.at(1)) << 8) + (unsigned char)(data_bin_str.at(2)); - - // get value of bit 24 - int bit_number = 23; - int mask = 1 << bit_number; - int masked_n = data_bin & mask; - int bit_value = masked_n >> bit_number; - - int data; - if (bit_value == 1) - { - int data_bin_xor = data_bin ^ int(pow(2, bit_number + 1) - 1); // bit xor - data = (data_bin_xor + 1) * -1; - } - else - { - data = data_bin; - } - - double chDataV = (data * 2 * 2.048) / (gain_ * pow(2.0, 24.0)); - double chDataUV = round(chDataV * 1000000); - - if (is_acquiring_frames) - { - // log_stream_("[udpMsgReceived] Value " << i << ": " << chDataUV); - // truncate - const auto result = trunc_n(chDataUV, 4); - frame.values.push_back(result.first); - } - else - { - // log_stream_("[udpMsgReceived] Value before filtering " << i << ": " << chDataUV); - if (butterworth_ == nullptr) - { - return; - } - double chDataUV_filtered; - butterworth_[i]->update(chDataUV, chDataUV_filtered); - double chDataNV_filtered = chDataUV_filtered * 1000.0; - // log_stream_("[udpMsgReceived] Value after filtering " << i << ": " << chDataNV_filtered); - double currentNA = 6; - // CHECK: it is a value per channel or unique value? do we have to take into account previous values? - double impedance = (chDataNV_filtered / currentNA) - 4700; - // log_stream_("[udpMsgReceived] Impedance " << i << ": " << impedance); - - // truncate - const auto result = trunc_n(impedance, 4); - frame.values.push_back(result.first); - } - starting_index += (3 + 1); // 3bytes of channel info + 1byte of space - } - } - catch (std::exception ex) - { - // log_error_stream_("[udpMsgReceived] error converting to double some of the values: " << msg); - return; - } - - if (is_acquiring_frames || is_acquiring_impedances) - { - if (acq_buffer_mutex_.try_lock()) - { - acq_buffer_.push_back(frame); - acq_buffer_mutex_.unlock(); - } - else - { - log_warn_stream_("[udpMsgReceived] lock not acquired"); - increaseLockError(); - } - } - else - { - acq_impedances_buffer_mutex_.lock(); - acq_impedances_buffer_.push_back(frame); - acq_impedances_buffer_mutex_.unlock(); - } - } - } -} - -ClassError::Error ClassCore::impedanceFilterOrder(const int &order) -{ - if (order == filter_order_) - { - return ClassError::CLASS_NO_ERROR; - } - - if (order <= 0) - { - return ClassError::ERROR_NOT_VALID_ORDER; - } - - return configureFilter(order, 30.2, 32.2, acq_frequency_); -} - -ClassError::Error ClassCore::bufferDuration(const double &ms) -{ - if (ms <= 0) - { - return ClassError::ERROR_NOT_VALID_MS; - } - - return setBufferCapacity(ms / 1000.0); -} - -ClassError::Error ClassCore::batteryLevel() -{ - ClassError::Error response = sendMsg(commands::ClassCommands::CMD_GETBATTERY); - - if (battery_mutex_.try_lock()) - { - is_waiting_battery_ = true; - battery_mutex_.unlock(); - } - - return response; -} - -ClassError::Error ClassCore::firmwareVersion() -{ - ClassError::Error response = sendMsg(commands::ClassCommands::CMD_GETFW); - - if (firmware_mutex_.try_lock()) - { - is_waiting_firmware_ = true; - firmware_mutex_.unlock(); - } - - return response; -} - -ClassError::Error ClassCore::deviceName() -{ - ClassError::Error response = sendMsg(commands::ClassCommands::CMD_GETDEVICENAME); - if (device_mutex_.try_lock()) - { - is_waiting_device_ = true; - device_mutex_.unlock(); - } - - return response; -} - -ClassError::Error ClassCore::logeventsStatus() -{ - ClassError::Error response = sendMsg(commands::ClassCommands::CMD_GETLOGEVENTS); - - if (logevents_mutex_.try_lock()) - { - is_waiting_logevents_ = true; - logevents_mutex_.unlock(); - } - - return response; -} - -ClassError::Error ClassCore::velecStatus(const int &number) -{ - - ClassError::Error response = sendMsg(commands::ClassCommands::CMD_CONFIGVELEC + std::to_string(number) + commands::ClassCommands::CMD_AUX); - - if (velecstatus_mutex_.try_lock()) - { - is_waiting_velecstatus_ = true; - velecstatus_mutex_.unlock(); - } - - return response; -} - -ClassError::Error ClassCore::patternStatus(const int &number) -{ - ClassError::Error response = sendMsg(commands::ClassCommands::CMD_CONFIGPATTERN + std::to_string(number) + commands::ClassCommands::CMD_AUX2 + commands::ClassCommands::CMD_AUX); - if (patternstatus_mutex_.try_lock()) - { - is_waiting_patternstatus_ = true; - patternstatus_mutex_.unlock(); - } - - return response; -} - -ClassError::Error ClassCore::hvStatus() -{ - ClassError::Error response = sendMsg(commands::ClassCommands::CMD_GETHV); - - if (hvstatus_mutex_.try_lock()) - { - is_waiting_hvstatus_ = true; - hvstatus_mutex_.unlock(); - } - - return response; -} - -ClassError::Error ClassCore::intervalStatus() -{ - ClassError::Error response = sendMsg(commands::ClassCommands::CMD_GETINTERVAL); - - if (intervalstatus_mutex_.try_lock()) - { - is_waiting_intervalstatus_ = true; - intervalstatus_mutex_.unlock(); - } - - return response; -} - -ClassError::Error ClassCore::rtcStatus() -{ - ClassError::Error response = sendMsg(commands::ClassCommands::CMD_GETRTC); - - if (rtcstatus_mutex_.try_lock()) - { - is_waiting_rtcstatus_ = true; - rtcstatus_mutex_.unlock(); - } - - return response; -} - -ClassError::Error ClassCore::buzzerStatus() -{ - ClassError::Error response = sendMsg(commands::ClassCommands::CMD_GETBUZZER); - - if (buzzerstatus_mutex_.try_lock()) - { - is_waiting_buzzerstatus_ = true; - buzzerstatus_mutex_.unlock(); - } - - return response; -} - -ClassError::Error ClassCore::frequencyStatus() -{ - ClassError::Error response = sendMsg(commands::ClassCommands::CMD_GETFRQUENCY); - - if (frequencystatus_mutex_.try_lock()) - { - is_waiting_frequencystatus_ = true; - frequencystatus_mutex_.unlock(); - } - - return response; -} - -ClassError::Error ClassCore::sdfunctionStatus() -{ - ClassError::Error response = sendMsg(commands::ClassCommands::CMD_GETSDFUNCTION); - - if (sdfunction_mutex_.try_lock()) - { - is_waiting_sdfunction_ = true; - sdfunction_mutex_.unlock(); - } - - return response; -} - -ClassError::Error ClassCore::sdunameStatus() -{ - ClassError::Error response = sendMsg(commands::ClassCommands::CMD_GETSDNAME); - - if (sduname_mutex_.try_lock()) - { - is_waiting_sduname_ = true; - sduname_mutex_.unlock(); - } - - return response; -} - -ClassError::Error ClassCore::hardwareVersion() -{ - ClassError::Error response = sendMsg(commands::ClassCommands::CMD_GETHW); - - if (hardware_mutex_.try_lock()) - { - is_waiting_hardware_ = true; - hardware_mutex_.unlock(); - } - - return response; -} - -ClassError::Error ClassCore::tic() -{ - ClassError::Error response = sendMsg(commands::ClassCommands::CMD_GETTIC); - - if (tic_mutex_.try_lock()) - { - is_waiting_tic_ = true; - tic_mutex_.unlock(); - } - - return response; -} - -void ClassCore::waitingThreadFunction_() -{ - waiting_ = true; - - bool waiting = true; - - log_stream_("[waitingThreadFunction_] Thread for waiting for callback messages started"); - - do - { - bool is_waiting_tic = false; - bool tic_received = false; - - bool is_waiting_firmware = false; - bool firmware_received = false; - - bool is_waiting_hardware = false; - bool hardware_received = false; - - bool is_waiting_battery = false; - bool battery_received = false; - - bool is_waiting_device = false; - bool device_received = false; - - bool is_waiting_logevents = false; - bool logevents_received = false; - - bool is_waiting_hvstatus = false; - bool hvstatus_received = false; - - bool is_waiting_intervalstatus = false; - bool intervalstatus_received = false; - - bool is_waiting_rtcstatus = false; - bool rtcstatus_received = false; - - bool is_waiting_buzzerstatus = false; - bool buzzerstatus_received = false; - - bool is_waiting_frequencystatus = false; - bool frequencystatus_received = false; - - bool is_waiting_sdfunctionstatus = false; - bool sdfunctionstatus_received = false; - - bool is_waiting_sdunamestatus = false; - bool sdunamestatus_received = false; - - bool is_waiting_patternstatus = false; - bool patternstatus_received = false; - - bool is_waiting_velecstatus = false; - bool velecstatus_received = false; - - double battery = 0; - - std::string tic = ""; - std::string firmware = ""; - std::string hardware = ""; - std::string device = ""; - std::string logevents = ""; - std::string hvstatus = ""; - std::string intervalstatus = ""; - std::string rtcstatus = ""; - std::string buzzerstatus = ""; - std::string frequencystatus = ""; - std::string sdfunctionstatus = ""; - std::string sdunamestatus = ""; - std::string patternstatus = ""; - std::string velecstatus = ""; - - // FIRMWARE - // if (firmware_ != "") - // { - // firmware = firmware_; - // class_cb_->firmwareHandle(firmware); - // } - - if (firmware_mutex_.try_lock()) - { - is_waiting_firmware = is_waiting_firmware_; - firmware_received = firmware_received_; - firmware = firmware_; - firmware_mutex_.unlock(); - } - else - { - log_warn_stream_("[waitingThreadFunction_] firmware lock not acquired"); - } - - if (is_waiting_firmware && firmware_received) - { - if (class_cb_ != nullptr) - { - if (language_ == "python") - { - class PyThreadStateLock PyThreadLock; // fix segmentation fault - class_cb_->firmwareHandle(firmware); - } - else - { - class_cb_->firmwareHandle(firmware); - } - if (firmware_mutex_.try_lock()) - { - firmware_received_ = false; - is_waiting_firmware_ = false; - firmware_mutex_.unlock(); - } - } - else - { - log_error_stream_("[waitingThreadFunction_] callback is null"); - } - } - // END OF FIRMWARE -if (velecstatus_mutex_.try_lock()) - { - is_waiting_velecstatus = is_waiting_velecstatus_; - velecstatus_received = velecstatus_received_; - velecstatus = velecstatus_; - velecstatus_mutex_.unlock(); - } - else - { - log_warn_stream_("[waitingThreadFunction_] velecstatus lock not acquired"); - } - - if (is_waiting_velecstatus && velecstatus_received) - { - if (class_cb_ != nullptr) - { - if (language_ == "python") - { - class PyThreadStateLock PyThreadLock; // fix segmentation fault - class_cb_->velecstatusHandle(velecstatus); - } - else - { - class_cb_->velecstatusHandle(velecstatus); - } - if (velecstatus_mutex_.try_lock()) - { - velecstatus_received_ = false; - is_waiting_velecstatus_ = false; - velecstatus_mutex_.unlock(); - } - } - else - { - log_error_stream_("[waitingThreadFunction_] callback is null"); - } - } - // END OF VELECSTATUS - if (device_mutex_.try_lock()) - { - is_waiting_device = is_waiting_device_; - device_received = device_received_; - device = device_; - device_mutex_.unlock(); - } - else - { - log_warn_stream_("[waitingThreadFunction_] device lock not acquired"); - } - - if (is_waiting_device && device_received) - { - if (class_cb_ != nullptr) - { - if (language_ == "python") - { - class PyThreadStateLock PyThreadLock; // fix segmentation fault - class_cb_->deviceNameHandle(device); - } - else - { - class_cb_->deviceNameHandle(device); - } - if (device_mutex_.try_lock()) - { - device_received_ = false; - is_waiting_device_ = false; - device_mutex_.unlock(); - } - } - else - { - log_error_stream_("[waitingThreadFunction_] callback is null"); - } - } - - //END of device name - if (logevents_mutex_.try_lock()) - { - is_waiting_logevents = is_waiting_logevents_; - logevents_received = logevents_received_; - logevents = logevents_; - logevents_mutex_.unlock(); - } - else - { - log_warn_stream_("[waitingThreadFunction_] logevents lock not acquired"); - } - - if (is_waiting_logevents && logevents_received) - { - if (class_cb_ != nullptr) - { - if (language_ == "python") - { - class PyThreadStateLock PyThreadLock; // fix segmentation fault - class_cb_->logeventsHandle(logevents); - } - else - { - class_cb_->logeventsHandle(logevents); - } - if (logevents_mutex_.try_lock()) - { - logevents_received_ = false; - is_waiting_logevents_ = false; - logevents_mutex_.unlock(); - } - } - else - { - log_error_stream_("[waitingThreadFunction_] callback is null"); - } - } - //END of logevents - if (patternstatus_mutex_.try_lock()) - { - is_waiting_patternstatus = is_waiting_patternstatus_; - patternstatus_received = patternstatus_received_; - patternstatus = patternstatus_; - patternstatus_mutex_.unlock(); - } - else - { - log_warn_stream_("[waitingThreadFunction_] patternstatus lock not acquired"); - } - - if (is_waiting_patternstatus && patternstatus_received) - { - if (class_cb_ != nullptr) - { - if (language_ == "python") - { - class PyThreadStateLock PyThreadLock; // fix segmentation fault - class_cb_->patternHandle(patternstatus); - } - else - { - class_cb_->patternHandle(patternstatus); - } - if (patternstatus_mutex_.try_lock()) - { - patternstatus_received_ = false; - is_waiting_patternstatus_ = false; - patternstatus_mutex_.unlock(); - } - } - else - { - log_error_stream_("[waitingThreadFunction_] callback is null"); - } - } - //END of pattern status - if (hvstatus_mutex_.try_lock()) - { - is_waiting_hvstatus = is_waiting_hvstatus_; - hvstatus_received = hvstatus_received_; - hvstatus = hvstatus_; - hvstatus_mutex_.unlock(); - } - else - { - log_warn_stream_("[waitingThreadFunction_] hvstatus lock not acquired"); - } - - if (is_waiting_hvstatus && hvstatus_received) - { - if (class_cb_ != nullptr) - { - if (language_ == "python") - { - class PyThreadStateLock PyThreadLock; // fix segmentation fault - class_cb_->hvHandle(hvstatus); - } - else - { - class_cb_->hvHandle(hvstatus); - } - if (hvstatus_mutex_.try_lock()) - { - hvstatus_received_ = false; - is_waiting_hvstatus_ = false; - hvstatus_mutex_.unlock(); - } - } - else - { - log_error_stream_("[waitingThreadFunction_] callback is null"); - } - } - //END of hvstatus - - if (intervalstatus_mutex_.try_lock()) - { - is_waiting_intervalstatus = is_waiting_intervalstatus_; - intervalstatus_received = intervalstatus_received_; - intervalstatus = intervalstatus_; - intervalstatus_mutex_.unlock(); - } - else - { - log_warn_stream_("[waitingThreadFunction_] intervalstatus lock not acquired"); - } - - if (is_waiting_intervalstatus && intervalstatus_received) - { - if (class_cb_ != nullptr) - { - if (language_ == "python") - { - class PyThreadStateLock PyThreadLock; // fix segmentation fault - class_cb_->intervalHandle(intervalstatus); - } - else - { - class_cb_->intervalHandle(intervalstatus); - } - if (intervalstatus_mutex_.try_lock()) - { - intervalstatus_received_ = false; - is_waiting_intervalstatus_ = false; - intervalstatus_mutex_.unlock(); - } - } - else - { - log_error_stream_("[waitingThreadFunction_] callback is null"); - } - } - //END of intervalstatus - - if (rtcstatus_mutex_.try_lock()) - { - is_waiting_rtcstatus = is_waiting_rtcstatus_; - rtcstatus_received = rtcstatus_received_; - rtcstatus = rtcstatus_; - rtcstatus_mutex_.unlock(); - } - else - { - log_warn_stream_("[waitingThreadFunction_] rtcstatus lock not acquired"); - } - - if (is_waiting_rtcstatus && rtcstatus_received) - { - if (class_cb_ != nullptr) - { - if (language_ == "python") - { - class PyThreadStateLock PyThreadLock; // fix segmentation fault - class_cb_->rtcHandle(rtcstatus); - } - else - { - class_cb_->rtcHandle(rtcstatus); - } - if (rtcstatus_mutex_.try_lock()) - { - rtcstatus_received_ = false; - is_waiting_rtcstatus_ = false; - rtcstatus_mutex_.unlock(); - } - } - else - { - log_error_stream_("[waitingThreadFunction_] callback is null"); - } - } - //END of rtc - - if (buzzerstatus_mutex_.try_lock()) - { - is_waiting_buzzerstatus = is_waiting_buzzerstatus_; - buzzerstatus_received = buzzerstatus_received_; - buzzerstatus = buzzerstatus_; - buzzerstatus_mutex_.unlock(); - } - else - { - log_warn_stream_("[waitingThreadFunction_] buzzer lock not acquired"); - } - - if (is_waiting_buzzerstatus && buzzerstatus_received) - { - if (class_cb_ != nullptr) - { - if (language_ == "python") - { - class PyThreadStateLock PyThreadLock; // fix segmentation fault - class_cb_->buzzerHandle(buzzerstatus); - } - else - { - class_cb_->buzzerHandle(buzzerstatus); - } - if (buzzerstatus_mutex_.try_lock()) - { - buzzerstatus_received_ = false; - is_waiting_buzzerstatus_ = false; - buzzerstatus_mutex_.unlock(); - } - } - else - { - log_error_stream_("[waitingThreadFunction_] callback is null"); - } - } - //END of buzzer - if (frequencystatus_mutex_.try_lock()) - { - is_waiting_frequencystatus = is_waiting_frequencystatus_; - frequencystatus_received = frequencystatus_received_; - frequencystatus = frequencystatus_; - frequencystatus_mutex_.unlock(); - } - else - { - log_warn_stream_("[waitingThreadFunction_] frequency lock not acquired"); - } - - if (is_waiting_frequencystatus && frequencystatus_received) - { - if (class_cb_ != nullptr) - { - if (language_ == "python") - { - class PyThreadStateLock PyThreadLock; // fix segmentation fault - class_cb_->frequencyHandle(frequencystatus); - } - else - { - class_cb_->frequencyHandle(frequencystatus); - } - if (frequencystatus_mutex_.try_lock()) - { - frequencystatus_received_ = false; - is_waiting_frequencystatus_ = false; - frequencystatus_mutex_.unlock(); - } - } - else - { - log_error_stream_("[waitingThreadFunction_] callback is null"); - } - } - //END of frequency - - if (sdfunction_mutex_.try_lock()) - { - is_waiting_sdfunctionstatus = is_waiting_sdfunction_; - sdfunctionstatus_received = sdfunction_received_; - sdfunctionstatus = sdfunctionstatus_; - sdfunction_mutex_.unlock(); - } - else - { - log_warn_stream_("[waitingThreadFunction_] sdfunction lock not acquired"); - } - - if (is_waiting_sdfunctionstatus && sdfunctionstatus_received) - { - if (class_cb_ != nullptr) - { - if (language_ == "python") - { - class PyThreadStateLock PyThreadLock; // fix segmentation fault - class_cb_->sdfunctionHandle(sdfunctionstatus); - } - else - { - class_cb_->sdfunctionHandle(sdfunctionstatus); - } - if (sdfunction_mutex_.try_lock()) - { - sdfunction_received_ = false; - is_waiting_sdfunction_ = false; - sdfunction_mutex_.unlock(); - } - } - else - { - log_error_stream_("[waitingThreadFunction_] callback is null"); - } - } - //END of sdfunction - - if (sduname_mutex_.try_lock()) - { - is_waiting_sdunamestatus = is_waiting_sduname_; - sdunamestatus_received = sduname_received_; - sdunamestatus = sdunamestatus_; - sduname_mutex_.unlock(); - } - else - { - log_warn_stream_("[waitingThreadFunction_] sduname lock not acquired"); - } - - if (is_waiting_sdunamestatus && sdunamestatus_received) - { - if (class_cb_ != nullptr) - { - if (language_ == "python") - { - class PyThreadStateLock PyThreadLock; // fix segmentation fault - class_cb_->sdunameHandle(sdunamestatus); - } - else - { - class_cb_->sdunameHandle(sdunamestatus); - } - if (sduname_mutex_.try_lock()) - { - sduname_received_ = false; - is_waiting_sduname_ = false; - sduname_mutex_.unlock(); - } - } - else - { - log_error_stream_("[waitingThreadFunction_] callback is null"); - } - } - //END of sduname - - // HARDWARE - // if (hardware_ != "") - // { - // hardware = hardware_; - // class_cb_->hardwareHandle(hardware); - // } - - if (hardware_mutex_.try_lock()) - { - is_waiting_hardware = is_waiting_hardware_; - hardware_received = hardware_received_; - hardware = hardware_; - hardware_mutex_.unlock(); - } - else - { - log_warn_stream_("[waitingThreadFunction_] hardware lock not acquired"); - } - - if (is_waiting_hardware && hardware_received) - { - if (class_cb_ != nullptr) - { - if (language_ == "python") - { - class PyThreadStateLock PyThreadLock; // fix segmentation fault - class_cb_->hardwareHandle(hardware); - } - else - { - class_cb_->hardwareHandle(hardware); - } - if (hardware_mutex_.try_lock()) - { - hardware_received_ = false; - is_waiting_hardware_ = false; - hardware_mutex_.unlock(); - } - } - else - { - log_error_stream_("[waitingThreadFunction_] callback is null"); - } - } - - // END OF HARDWARE - - // TIC - // if (tic_ != "") - // { - // tic = tic_; - // class_cb_->ticHandle(tic); - // } - if (tic_mutex_.try_lock()) - { - is_waiting_tic = is_waiting_tic_; - tic_received = tic_received_; - tic = tic_; - tic_mutex_.unlock(); - } - else - { - log_warn_stream_("[waitingThreadFunction_] tic lock not acquired"); - } - - if (is_waiting_tic && tic_received) - { - if (class_cb_ != nullptr) - { - if (language_ == "python") - { - class PyThreadStateLock PyThreadLock; // fix segmentation fault - class_cb_->ticHandle(tic); - } - else - { - class_cb_->ticHandle(tic); - } - if (tic_mutex_.try_lock()) - { - tic_received_ = false; - is_waiting_tic_ = false; - tic_mutex_.unlock(); - } - } - else - { - log_error_stream_("[waitingThreadFunction_] callback is null"); - } - } - // END OF TIC - - // BATTERY - if (battery_mutex_.try_lock()) - { - is_waiting_battery = is_waiting_battery_; - battery_received = battery_received_; - battery = battery_; - battery_mutex_.unlock(); - } - else - { - log_warn_stream_("[waitingThreadFunction_] battery lock not acquired"); - } - - if (is_waiting_battery && battery_received) - { - if (class_cb_ != nullptr) - { - if (language_ == "python") - { - class PyThreadStateLock PyThreadLock; // fix segmentation fault - class_cb_->batteryHandle(battery); - } - else - { - class_cb_->batteryHandle(battery); - } - if (battery_mutex_.try_lock()) - { - battery_received_ = false; - is_waiting_battery_ = false; - battery_mutex_.unlock(); - } - } - else - { - log_error_stream_("[waitingThreadFunction_] callback is null"); - } - } - // END OF BATTERY - - std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 0.5 secs - - if (waiting_mutex_.try_lock()) - { - waiting = waiting_; - waiting_mutex_.unlock(); - } - else - { - log_warn_stream_("[waitingThreadFunction_] waiting lock not acquired"); - } - } while (waiting); -} - -void ClassCore::lockErrorThreadFunction_() -{ - error_waiting_ = true; - - bool error_waiting = true; - - log_stream_("[lockErrorThreadFunction_] Thread for taking into account lock errors started"); - - do - { - int lock_error_number = 0; - - if (lock_error_mutex_.try_lock()) - { - lock_error_number = lock_error_number_; - lock_error_mutex_.unlock(); - } - else - { - log_warn_stream_("[lockErrorThreadFunction_] error lock not acquired"); - } - - if (lock_error_number >= lock_error_number_max_) - { - log_error_stream_("[lockErrorThreadFunction_] Number of errors when getting lock has overcome the limit. Check processes in your computer."); - } - - if (lock_error_mutex_.try_lock()) - { - lock_error_number_ = 0; - lock_error_mutex_.unlock(); - } - else - { - log_warn_stream_("[lockErrorThreadFunction_] error lock not acquired"); - } - - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // 1secs - - if (lock_error_mutex_.try_lock()) - { - error_waiting = error_waiting_; - lock_error_mutex_.unlock(); - } - else - { - log_warn_stream_("[lockErrorThreadFunction_] error lock not acquired"); - } - } while (error_waiting); -} - -ClassError::Error ClassCore::setCallback(ClassCallback *callback, std::string language) -{ - if (class_cb_ != nullptr) - { - delete class_cb_; - class_cb_ = nullptr; - } - - if (callback) - { - class_cb_ = callback; - } - else - { - return ClassError::ERROR_CB_NULL; - } - - language_ = language; - - return ClassError::CLASS_NO_ERROR; -} - -std::pair<double, bool> ClassCore::trunc_n(double value, std::size_t digits_after_decimal = 0) -{ - static constexpr std::intmax_t maxv = std::numeric_limits<std::intmax_t>::max(); - static constexpr std::intmax_t minv = std::numeric_limits<std::intmax_t>::min(); - - unsigned long long multiplier = 1; - for (std::size_t i = 0; i < digits_after_decimal; ++i) - multiplier *= 10; - - const auto scaled_value = value * multiplier; - - const bool did_trunc = scaled_value != scaled_value + 0.5 && scaled_value != scaled_value - 0.5; - - if (scaled_value >= minv && scaled_value <= maxv) - return {double(std::intmax_t(scaled_value)) / multiplier, did_trunc}; - else - return {std::trunc(scaled_value) / multiplier, did_trunc}; -} - -void ClassCore::increaseLockError() -{ - lock_error_mutex_.lock(); - lock_error_number_ += 1; - lock_error_mutex_.unlock(); -} -ClassError::Error ClassCore::sendCustomCmd(const std::string &cmd) -{ - return sendMsg(cmd); -} - -/*LOGGING functions*/ - -void ClassCore::log_(const std::string &text) -{ - LOG_INFO << "[ClassCore]: " << text; -} - -void ClassCore::log_(std::ostream &text) -{ - LOG_INFO << "[ClassCore]: " << text.rdbuf(); -} - -void ClassCore::log_(const std::stringstream &text) -{ - LOG_INFO << "[ClassCore]: " << text.str(); -} - -// see http://www.cplusplus.com/forum/unices/36461/ -// see https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute - -#ifdef __unix__ -const std::string bold_red("\033[1;31m"); -const std::string bold_yellow("\033[1;33m"); -const std::string reset("\033[0m"); -#endif - -void ClassCore::log_err_(const std::string &text) -{ -#ifdef __unix__ - LOG_ERROR << bold_red << "[ClassCore]: " << text << reset; -#elif defined(_WIN32) || defined(WIN32) - HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(hConsole, 12); - LOG_ERROR << "[ClassCore]: " << text; - SetConsoleTextAttribute(hConsole, 7); -#endif -} - -void ClassCore::log_err_(std::ostream &text) -{ -#ifdef __unix__ - LOG_ERROR << bold_red << "[ClassCore]: " << text.rdbuf() << reset; -#elif defined(_WIN32) || defined(WIN32) - HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(hConsole, 12); - LOG_ERROR << "[ClassCore]: " << text.rdbuf(); - SetConsoleTextAttribute(hConsole, 7); -#endif -} - -void ClassCore::log_warn_(const std::string &text) -{ -#ifdef __unix__ - LOG_WARNING << bold_yellow << "[ClassCore]: " << text << reset; -#elif defined(_WIN32) || defined(WIN32) - HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(hConsole, 14); - LOG_WARNING << "[ClassCore]: " << text; - SetConsoleTextAttribute(hConsole, 7); -#endif -} - -void ClassCore::log_warn_(std::ostream &text) -{ -#ifdef __unix__ - LOG_WARNING << bold_yellow << "[ClassCore]: " << text.rdbuf() << reset; -#elif defined(_WIN32) || defined(WIN32) - HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(hConsole, 14); - LOG_WARNING << "[ClassCore]: " << text.rdbuf(); - SetConsoleTextAttribute(hConsole, 7); -#endif -} diff --git a/src/class_server/.gitkeep b/src/class_server/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/class_server/CMakeLists.txt b/src/class_server/CMakeLists.txt deleted file mode 100644 index 554f349..0000000 --- a/src/class_server/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 3.14) -project (class_server) - -include_directories(include ${Boost_INCLUDE_DIRS} ${YAML_INCLUDE_DIR}) - -add_executable(${PROJECT_NAME} src/class_server_main.cpp src/class_server.cpp) -target_link_libraries(${PROJECT_NAME} logger communication yaml_tools yaml-cpp ${Boost_LIBRARIES}) - -add_executable(class_client src/class_client_example.cpp) -target_link_libraries(class_client logger communication yaml_tools yaml-cpp ${Boost_LIBRARIES}) - - -# 'make install' to the correct location -install(TARGETS ${PROJECT_NAME} - RUNTIME DESTINATION class_server) diff --git a/src/class_server/include/.gitkeep b/src/class_server/include/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/class_server/include/class_server/.gitkeep b/src/class_server/include/class_server/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/class_server/include/class_server/class_server.hpp b/src/class_server/include/class_server/class_server.hpp deleted file mode 100644 index dc45e07..0000000 --- a/src/class_server/include/class_server/class_server.hpp +++ /dev/null @@ -1,178 +0,0 @@ -/** - * @file class_server.hpp - * @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> - * @date 2020 - * - * Copyright 2020 Tecnalia Research & Innovation. - * Distributed under the GNU GPL v3. - * For full terms see https://www.gnu.org/licenses/gpl.txt - * - * @brief Class for server which connects to CLASS device and waits for client connections - */ - -#ifndef CLASSSERVER_HPP -#define CLASSSERVER_HPP - -#include <string> -#include <sstream> -#include <thread> -#include <memory> -#include <mutex> -#include <fstream> - -#include <logger/logger.hpp> -//#include <communication/serial.hpp> -#include <communication/async_serial.hpp> -#include <communication/udp_server.hpp> -#include <communication/udp_client.hpp> - - -#if defined(_WIN32) || defined(WIN32) - #include <windows.h> -#endif - -class ClassServer: public UdpServerListener, public SerialListener -{ -public: - //! Basic constructor - ClassServer(); - //! Basic destructor - ~ClassServer(); - - /*! - \brief Connects to the CLASS and starts the server - \param port CLASS COM port to connect with. - \param listening_port Port in which server listens for connections. - \return Integer indicating the success of the connection (0-success, <0 error) - */ - int start(const std::string & port, const int & listening_port, boost::asio::io_service *io_service); - - /*! - \brief Disconnects from the CLASS and stops the server - \return Integer indicating the success of the connection (0-success, <0 error) - */ - int stop(); - - /*! - \brief Handles the reception of an UDP msg from a client - \param msg Msg - \param ip IP the message comes from - */ - void udpMsgReceived(std::string msg, std::string client_ip); - - /*! - \brief Handles the reception of a msg comming from serial - \param msg Msg - */ - void serialMsgReceived(std::string msg); - -private: - - //! thread for looking after variables - std::thread *wait_thread_; - //! function called by thread - void waitThreadFunction(); - - //! serial port for communication with CLASS - //Serial serial_; - AsyncSerial *serial_; - //! thread for sending msgs to CLASS - std::thread *serial_send_thread_; - //! function called by thread - void serialSendThreadFunction(); - //! list of msgs to be send to CLASS - std::vector<std::string> msgs_to_class_; - //! mutex for accessing msg list - std::mutex msgs_to_class_mutex_; - //! list of msgs received from CLASS - std::vector<std::string> msgs_from_class_; - //! mutex for accessing msg list - std::mutex msgs_from_class_mutex_; - - //! UDP server - UDPServer *udp_server_; - //! List of the ips connected to the server - std::vector<std::string> udp_client_ips_; - //! List of the ips connected to the server - std::vector<UDPClient*> udp_clients_; - //! thread for sending msgs to client - std::thread *udp_send_thread_; - //! function called by thread - void udpSendThreadFunction(); - //! mutex for upd_clients access - std::mutex udp_mutex_; - - //! indicates whether turn of has been received - bool turn_off_; - //! mutex for upd_clients access - std::mutex turn_off_mutex_; - //! indicates whether udp thread is running - bool continue_udp_; - //! indicates whether serial thread is running - bool continue_serial_; - - - // !external io_service - boost::asio::io_service *io_service_; - - // ! file where msgs are stored - //std::ofstream myfile_; - - - /*! - \brief Connects to the CLASS - \param port CLASS COM port to connect with. - \return Integer indicating the success of the connection (0-success, <0 error) - */ - int connectToSerial(const std::string & port); - - /*! - \brief Disconnects from the CLASS - \return Integer indicating the success of the connection (0-success, <0 error) - */ - int disconnectFromSerial(); - - /*! - \brief Starts listening fro UDP connections - \param port Port in which the server is listening. - \return Integer indicating the success of the connection (0-success, <0 error) - */ - int startUDPServer(const int & listening_port); - - /*! - \brief Stops listening - \param port Port in which the server is listening. - \return Integer indicating the success of the connection (0-success, <0 error) - */ - int stopUDPServer(); - - /*! - \brief Replaces substr in a string by another - \param str Original stringl - \param from substr to be replaced - \param to substr to replace - \return True if found and done - */ - bool replace(std::string& str, const std::string& from, const std::string& to); - - /*! - \brief Trims a string - \param str Original stringl - \return The trimmed string - */ - std::string trim(std::string str); - - //! log function - #define log_stream_(x) log_(std::stringstream() << x); - #define log_warn_stream_(x) log_warn_(std::stringstream() << x); - #define log_error_stream_(x) log_err_(std::stringstream() << x); - void log_(const std::string &text); - void log_(const std::stringstream &text); - void log_(std::ostream & text); - void log_err_(const std::string &text); - void log_err_(std::ostream & text); - void log_warn_(const std::string &text); - void log_warn_(std::ostream & text); -}; - -#endif // CLASSSERVER_HPP \ No newline at end of file diff --git a/src/class_server/src/.gitkeep b/src/class_server/src/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/class_server/src/class_client_example.cpp b/src/class_server/src/class_client_example.cpp deleted file mode 100644 index 7e93b2c..0000000 --- a/src/class_server/src/class_client_example.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/** - * @file class_client_example.cpp - * @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> - * @date 2020 - * - * Copyright 2020 Tecnalia Research & Innovation. - * Distributed under the GNU GPL v3. - * For full terms see https://www.gnu.org/licenses/gpl.txt - * - * @brief CLASS client for testing purposes - */ - -#include <string> -#include <communication/udp_client.hpp> -#include <iostream> -#include <boost/chrono.hpp> -#include <boost/thread/thread.hpp> - -int main(int argc, char* argv[]) -{ - if (argc != 4) - { - std::cerr << "ERROR: Executable expects 3 arguments (IP and port where the server is listening) and port where the client will wait for server msgs"<< std::endl; - std::cerr << "Usage: ./class_client <server_host> <server_port> <local_port>" << std::endl; - return 1; - } - - UDPClient client(argv[1], argv[2], -1); - - std::string local_port = argv[3]; - - std::stringstream ss; - ss << "connect " << local_port; - std::string msg = ss.str(); - - client.send(msg); - - int numberLoopMax = 10; - int numberLoop = 0; - - std::cout << "Wait for 10 secs and then disconnect" << std::endl; - - client.send("iam DESKTOP\r\n"); - - client.send("firmware ?\r\n"); - - //client.send("device ?\r\n"); - - client.send("acq config *freq 250.0 *channels 2 *type monopolar\r\n"); - - client.send("acq stream on\r\n"); - - client.send("acq on\r\n"); - - while (numberLoop < numberLoopMax) - { - boost::this_thread::sleep_for(boost::chrono::milliseconds(1000)); - numberLoop += 1; - std::cout << numberLoop << " sec(s)" << std::endl; - } - - client.send("acq off\r\n"); - - ss.str(std::string()); - ss << "disconnect " << local_port; - msg = ss.str(); - client.send(msg); - - return 0; -} diff --git a/src/class_server/src/class_server.cpp b/src/class_server/src/class_server.cpp deleted file mode 100644 index 85011a9..0000000 --- a/src/class_server/src/class_server.cpp +++ /dev/null @@ -1,540 +0,0 @@ -/** - * @file class_server.cpp - * @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> - * @date 2020 - * - * Copyright 2020 Tecnalia Research & Innovation. - * Distributed under the GNU GPL v3. - * For full terms see https://www.gnu.org/licenses/gpl.txt - * - * @brief Class for server which connects to CLASS device and waits for client connections - */ - -#include <class_server/class_server.hpp> -#include <iostream> -#include <regex> -#include <typeinfo> -#include <stdio.h> -#include <string> -#include "../../commands.h" - -using namespace std; - -ClassServer::ClassServer() -{ - log_("Constructor"); - turn_off_ = false; - continue_serial_ = true; - continue_udp_ = true; -} - -ClassServer::~ClassServer() -{ - log_("Destructor"); - if (udp_server_ != nullptr) - { - delete udp_server_; - } - - if (udp_send_thread_ != nullptr) - { - delete udp_send_thread_; - } - - if (serial_ != nullptr) - { - delete serial_; - } - - if (serial_send_thread_ != nullptr) - { - delete serial_send_thread_; - } - - if (wait_thread_ != nullptr) - { - delete wait_thread_; - } - - for (unsigned int i = 0; i < udp_clients_.size(); i++) - { - if (udp_clients_[i] != nullptr) - { - delete udp_clients_[i]; - } - } - - // myfile_.close(); -} - -int ClassServer::start(const std::string &port, const int &listening_port, boost::asio::io_service *io_service) -{ - io_service_ = io_service; - if (connectToSerial(port) != 0) - { - log_error_stream_("[start] Error connecting to CLASS"); - return -1; - } - else - { - log_stream_("[start] Connected to CLASS"); - } - - if (startUDPServer(listening_port) != 0) - { - log_error_stream_("[start] Error starting UDP server"); - return -1; - } - else - { - log_stream_("[start] UDP server is listening"); - } - - wait_thread_ = new std::thread(&ClassServer::waitThreadFunction, this); - wait_thread_->detach(); - - // myfile_.open ("msgs.txt"); - - return 0; -} - -int ClassServer::stop() -{ - disconnectFromSerial(); - stopUDPServer(); - io_service_->stop(); - return 0; -} - -int ClassServer::connectToSerial(const std::string &port) -{ - serial_ = new AsyncSerial(port, 115200); - // starts a thread for sending msgs to CLASS each 100ms - serial_->addListener((SerialListener *)this); - - serial_send_thread_ = new std::thread(&ClassServer::serialSendThreadFunction, this); - serial_send_thread_->detach(); - - return 0; -} - -int ClassServer::disconnectFromSerial() -{ - if (serial_ != nullptr) - { - log_("[disconnectFromSerial] close serial"); - serial_->close(); - delete serial_; - serial_ = nullptr; - log_("[disconnectFromSerial] serial closed"); - } - - if (serial_send_thread_ != nullptr) - { - delete serial_send_thread_; - serial_send_thread_ = nullptr; - } - - turn_off_mutex_.lock(); - continue_serial_ = false; - turn_off_mutex_.unlock(); - - return 0; -} - -void ClassServer::serialMsgReceived(std::string msg) -{ - //log_stream_("[serialMsgReceived] Msg received from CLASS: " << msg) - // myfile_ << msg; - - if (msg.find("turnning off") != std::string::npos) - { - log_("[serialMsgReceived] Device turned OFF"); - turn_off_mutex_.lock(); - turn_off_ = true; - turn_off_mutex_.unlock(); - } - msgs_from_class_mutex_.lock(); - msgs_from_class_.push_back(msg); - msgs_from_class_mutex_.unlock(); - - if(msg.find("tic") != string::npos) - { - log_stream_("[serialMsgReceived] Msg received from CLASS: " << msg) - } -} - -void ClassServer::waitThreadFunction() -{ - bool continueLoop = true; - while (continueLoop) - { - turn_off_mutex_.lock(); - continueLoop = continue_udp_ || continue_serial_; - turn_off_mutex_.unlock(); - - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - - log_("[waitThreadFunction] Thread stopped. Stop external service"); - io_service_->stop(); -} - -void ClassServer::serialSendThreadFunction() -{ - bool continueLoop = true; - while (continueLoop) - { - turn_off_mutex_.lock(); - continueLoop = !turn_off_; - turn_off_mutex_.unlock(); - - bool is_empty; - msgs_to_class_mutex_.lock(); - is_empty = msgs_to_class_.empty(); - msgs_to_class_mutex_.unlock(); - - if (!is_empty) - { - // send the first msg in queue - std::string msg; - msgs_to_class_mutex_.lock(); - msg = msgs_to_class_[0]; - commands::ClassCommands d; - log_stream_("[serialSendThreadFunction] Msg sent to CLASS device : " << d.Log(msg)); - msgs_to_class_.erase(msgs_to_class_.begin()); - msgs_to_class_mutex_.unlock(); - serial_->writeString(msg); - std::string msg_without_ending = msg; - replace(msg_without_ending, "\r\n", "\\r\\n"); - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - } - else - { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - } - - log_("[serialSendThreadFunction] Thread stopped. Disconnect from serial"); - disconnectFromSerial(); -} - -int ClassServer::startUDPServer(const int &listening_port) -{ - if (udp_server_ != nullptr) - { - log_("[startUDPServer] There is already a UDP server listening"); - } - - log_stream_("[startUDPServer] Starting UDP server on port " << listening_port); - udp_server_ = new UDPServer(listening_port); - udp_server_->startListening(); - udp_server_->addListener((UdpServerListener *)this); - - // starts a thread for sending msgs to clients - udp_send_thread_ = new std::thread(&ClassServer::udpSendThreadFunction, this); - udp_send_thread_->detach(); - - return 0; -} - -int ClassServer::stopUDPServer() -{ - if (udp_server_ != nullptr) - { - log_("[stopUDPServer] Stopping UDP server"); - udp_server_->removeListener((UdpServerListener *)this); - udp_server_->stopListening(); - delete udp_server_; - udp_server_ = nullptr; - turn_off_mutex_.lock(); - continue_udp_ = false; - turn_off_mutex_.unlock(); - log_("[stopUDPServer] UDP server stopped"); - } - - return 0; -} - -void ClassServer::udpMsgReceived(std::string msg, std::string ip) -{ - // parse message from client - if (msg.find("flush") != std::string::npos) - { - log_stream_("[udpMsgReceived] Message received from client (IP: " << ip << "): Flush"); - // flush async_serial buffer - if (serial_ != nullptr) - { - serial_->flush(); - } - // flush internal buffer - msgs_from_class_mutex_.lock(); - msgs_from_class_.clear(); - msgs_from_class_mutex_.unlock(); - } - else if (msg.find("disconnect") != std::string::npos) - { - // extract port where the client was listenening - std::regex regex("\\ "); - std::vector<std::string> out( - std::sregex_token_iterator(msg.begin(), msg.end(), regex, -1), - std::sregex_token_iterator()); - if (out.size() != 2) - { - return; - } - int client_port = 0; - try - { - client_port = std::stoi(out[1]); - } - catch (const std::exception &e) - { - log_error_stream_("[udpMsgReceived] disconnect command must be followed by an integer (port in which the client listens) but (" << out[1] << ") has been provided. Exc:" << e.what()); - return; - } - - if (udp_mutex_.try_lock()) - { - for (unsigned int i = 0; i < udp_clients_.size(); i++) - { - if (udp_clients_[i]->getIP() == ip && udp_clients_[i]->getPort() == client_port) - { - log_stream_("[udpMsgReceived] Client (IP: " << ip << ", port: " << client_port << ") is not listening anymore"); - delete udp_clients_[i]; - udp_clients_.erase(udp_clients_.begin() + i); - break; - } - } - udp_mutex_.unlock(); - } - } - else if (msg.find("connect") != std::string::npos) - { - // extract port where the client will listen - // extract port - std::regex regex("\\ "); - std::vector<std::string> out( - std::sregex_token_iterator(msg.begin(), msg.end(), regex, -1), - std::sregex_token_iterator()); - if (out.size() != 2) - { - return; - } - int client_port = 0; - try - { - client_port = std::stoi(out[1]); - } - catch (const std::exception &e) - { - log_error_stream_("[udpMsgReceived] connect command must be followed by an iteger (port in which the client listens) but (" << out[1] << ") has been provided. Exc:" << e.what()); - return; - } - - if (udp_mutex_.try_lock()) - { - bool found = false; - for (unsigned int i = 0; i < udp_clients_.size(); i++) - { - if (udp_clients_[i]->getIP() == ip && udp_clients_[i]->getPort() == client_port) - { - log_stream_("[udpMsgReceived] There is a client already listening (IP: " << ip << ", port: " << client_port << ")"); - found = true; - break; - } - } - - if (!found) - { - log_stream_("[udpMsgReceived] New client is listening (IP: " << ip << ", port: " << client_port << ")"); - udp_client_ips_.push_back(ip); - udp_clients_.push_back(new UDPClient(ip, out[1], -1)); - } - udp_mutex_.unlock(); - } - } - else - { - // send msg to CLASS_DEVICE - std::vector<string>::iterator it = std::find(udp_client_ips_.begin(), udp_client_ips_.end(), ip); - if (it == udp_client_ips_.end()) - { - log_warn_stream_("[udpMsgReceived] Client (IP: " << ip << ") is not connected"); - } - else - { - // std::string msg_without_ending = msg; - // replace(msg_without_ending, "\r\n", "\\r\\n"); - // log_stream_("[udpMsgReceived] Message received from client (IP: " << ip << "): " << msg_without_ending); - msgs_to_class_mutex_.lock(); - msgs_to_class_.push_back(msg); - msgs_to_class_mutex_.unlock(); - } - } -} - -bool ClassServer::replace(std::string &str, const std::string &from, const std::string &to) -{ - size_t start_pos = str.find(from); - while (start_pos != std::string::npos) - { - str.replace(start_pos, from.length(), to); - start_pos = str.find(from); - } - return true; -} - -std::string ClassServer::trim(string str) -{ - size_t first = str.find_first_not_of(' '); - if (string::npos == first) - { - return ""; - } - size_t last = str.find_last_not_of(' '); - return str.substr(first, (last - first + 1)); -} - -void ClassServer::udpSendThreadFunction() -{ - bool continueLoop = true; - while (continueLoop) - { - turn_off_mutex_.lock(); - continueLoop = !turn_off_; - turn_off_mutex_.unlock(); - - bool is_empty; - msgs_from_class_mutex_.lock(); - is_empty = msgs_from_class_.empty(); - msgs_from_class_mutex_.unlock(); - - if (!is_empty) - { - if (!udp_clients_.empty()) - // send the first msg in queue - { - std::vector<std::string> msgs; - msgs_from_class_mutex_.lock(); - msgs = msgs_from_class_; - msgs_from_class_.clear(); - msgs_from_class_mutex_.unlock(); - - if (!msgs.empty()) - { - if (udp_mutex_.try_lock()) - { - for (unsigned int i = 0; i < msgs.size(); i++) - { - for (unsigned int j = 0; j < udp_clients_.size(); j++) - { - msgs[i].erase(std::remove(msgs[i].begin(), msgs[i].end(), '\n'), msgs[i].end()); - - msgs[i].erase(std::remove(msgs[i].begin(), msgs[i].end(), '\r'), msgs[i].end()); - trim(msgs[i]); - - if (msgs[i].find("mac ") != std::string::npos){ - //log_stream_("mac text found"); - continue; - } - - if (!msgs[i].empty()) - { - //log_stream_("[SENDING ---->>>>] " << msgs[i]); - udp_clients_[j]->send(msgs[i]); - } - - // if (!msgs[i].empty()) - // { - // udp_clients_[j]->send(msgs[i]); - // } - } - } - udp_mutex_.unlock(); - } - // else - // { - // log_warn_stream_("[udpSendThreadFunction] udp_mutex_ not get amd msgs pending: " << msgs[0]); - // } - } - } - } - - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - log_("[udpSendThreadFunction] Thread stopped. UDP server stopped"); - stopUDPServer(); -} - -/*logging*/ - -void ClassServer::log_(const std::string &text) -{ - LOG_INFO << "[ClassServer]: " << text; -} - -void ClassServer::log_(std::ostream &text) -{ - LOG_INFO << "[ClassServer]: " << text.rdbuf(); -} -void ClassServer::log_(const std::stringstream &text) -{ - LOG_INFO << "[ClassServer]: " << text.str(); -} -// see http://www.cplusplus.com/forum/unices/36461/ -// see https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute - -#ifdef __unix__ -const std::string bold_red("\033[1;31m"); -const std::string bold_yellow("\033[1;33m"); -const std::string reset("\033[0m"); -#endif - -void ClassServer::log_err_(const std::string &text) -{ -#ifdef __unix__ - LOG_ERROR << bold_red << "[ClassServer]: " << text << reset; -#elif defined(_WIN32) || defined(WIN32) - HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(hConsole, 12); - LOG_ERROR << "[ClassServer]: " << text; - SetConsoleTextAttribute(hConsole, 7); -#endif -} - -void ClassServer::log_err_(std::ostream &text) -{ -#ifdef __unix__ - LOG_ERROR << bold_red << "[ClassServer]: " << text.rdbuf() << reset; -#elif defined(_WIN32) || defined(WIN32) - HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(hConsole, 12); - LOG_ERROR << "[ClassServer]: " << text.rdbuf(); - SetConsoleTextAttribute(hConsole, 7); -#endif -} - -void ClassServer::log_warn_(const std::string &text) -{ -#ifdef __unix__ - LOG_WARNING << bold_yellow << "[ClassServer]: " << text << reset; -#elif defined(_WIN32) || defined(WIN32) - HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(hConsole, 14); - LOG_WARNING << "[ClassServer]: " << text; - SetConsoleTextAttribute(hConsole, 7); -#endif -} - -void ClassServer::log_warn_(std::ostream &text) -{ -#ifdef __unix__ - LOG_WARNING << bold_yellow << "[ClassServer]: " << text.rdbuf() << reset; -#elif defined(_WIN32) || defined(WIN32) - HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(hConsole, 14); - LOG_WARNING << "[ClassServer]: " << text.rdbuf(); - SetConsoleTextAttribute(hConsole, 7); -#endif -} \ No newline at end of file diff --git a/src/class_server/src/class_server_main.cpp b/src/class_server/src/class_server_main.cpp deleted file mode 100644 index 40d05f5..0000000 --- a/src/class_server/src/class_server_main.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @file class_server_main.cpp - * @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> - * @date 2020 - * - * Copyright 2020 Tecnalia Research & Innovation. - * Distributed under the GNU GPL v3. - * For full terms see https://www.gnu.org/licenses/gpl.txt - * - * @brief CLASS server main program - */ - -#include <class_server/class_server.hpp> -#include <thread> -#include <memory> - -ClassServer server; - -void handler( const boost::system::error_code& error , int signal_number ) -{ - std::cout << "[CLASS server] Handling signal " << signal_number << std::endl; - server.stop(); - - exit(1); -} - -int main (int argc, char *argv[]) -{ - if (argc != 3) - { - std::cerr << "[CLASS server] ERROR: Executable expects 2 arguments (BT port to connect to CLASS device and UDP port where this server will listen for client msgs)"<< std::endl; - std::cerr << "[CLASS server] Usage example:"<< std::endl; - #ifdef _WINDOWS - std::cerr << "[CLASS server] class_server.exe COM14 50000" << std::endl; - #else - std::cerr << "[CLASS server] ./class_server COM14 50000" << std::endl; - #endif - - return 1; - } - - std::string bt_port = argv[1]; - std::cout << "[CLASS server] Connect to BT port: " << bt_port << std::endl; - std::string udp_port_str = argv[2]; - int udp_port = 0; - try - { - udp_port = std::stoi(udp_port_str); - } - catch(const std::exception& e) - { - std::cerr << "2nd parameter is not an integer (" << udp_port_str << "). Exc:" << e.what() << std::endl; - return 1; - } - std::cout << "[CLASS server] Listen in UDP port: " << udp_port << std::endl; - - - boost::asio::io_service *io_service = new boost::asio::io_service(); - boost::asio::signal_set signals(*io_service, SIGINT ); - - // Start an asynchronous wait for one of the signals to occur. - signals.async_wait( handler ); - - if(server.start(bt_port, udp_port, io_service) == 0) - { - std::cout << "[CLASS server] Waiting for connections. Ctrl+C for stop" << std::endl; - io_service->run(); - } - - std::cout << "[CLASS server] Bye" << std::endl; -} \ No newline at end of file diff --git a/src/cmspandoc.cmake b/src/cmspandoc.cmake deleted file mode 100644 index 1fcdf8b..0000000 --- a/src/cmspandoc.cmake +++ /dev/null @@ -1,105 +0,0 @@ -################################################################################ -## Configure Pandoc to use -## Usage: -## 1º Include Module: -## LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/Modules/cmake-pandocology") -## INCLUDE(pandocology) -## INCLUDE(cmspandoc) -## 2º Call macro CONFIGUREPANDOCOLOGY() to configure -################################################################################ - -MACRO(CONFIGUREPANDOCOLOGY) - MESSAGE(STATUS "Configuring pandoc variables...") - - # Set predefined options - SET(PANDOC_OUTPUT_FORMAT pdf CACHE STRING "Choose final document") - SET(PANDOC_STANDALONE YES CACHE BOOL "Produce output with an appropriate header and footer (e.g. a standalone HTML, LaTeX, TEI, - or RTF file, not a fragment). This option is set automatically for pdf, epub, epub3, fb2, docx, and odt output.") - SET(PANDOC_DOCUMENTCLASS "report" CACHE STRING "Document class with Latex") - SET(PANDOC_LANGUAGE en CACHE STRING "Language with BCP 47 identifier") - SET(PANDOC_LATEX_ENGINE xelatex CACHE STRING "Latex engine") - SET(PANDOC_FONT_SIZE 12pt CACHE STRING "Font size") - SET(PANDOC_PAPER_SIZE a4 CACHE STRING "Paper size") - SET(PANDOC_MARGIN_LEFT 3cm CACHE STRING "Margin left") - SET(PANDOC_MARGIN_RIGHT 3cm CACHE STRING "Margin right") - SET(PANDOC_MARGIN_TOP 3cm CACHE STRING "Margin top") - SET(PANDOC_MARGIN_BOTTOM 3cm CACHE STRING "Margin bottom") - SET(PANDOC_FONT_MAIN "Liberation Sans" CACHE STRING "Font") - SET(PANDOC_FONT_MONO "Liberation Sans" CACHE STRING "Font") - SET(PANDOC_INCLUDE_CONTENT TRUE CACHE BOOL "Choose if you want include Tables of content (toc)") - SET(PANDOC_INCLUDE_FIGURE FALSE CACHE BOOL "Choose if you want include Tables of content (lof)") - SET(PANDOC_INCLUDE_TABLE FALSE CACHE BOOL "Choose if you want include Tables of content (lot)") - #SET(PANDOC_SECTION_NUMBERS en CACHE STRING "Add section numbers") - - # Set multiple options in cache variables - SET_PROPERTY(CACHE PANDOC_DOCUMENTCLASS PROPERTY STRINGS article report book letter slides) - SET_PROPERTY(CACHE PANDOC_OUTPUT_FORMAT PROPERTY STRINGS pdf doc odt html) - SET_PROPERTY(CACHE PANDOC_LANGUAGE PROPERTY STRINGS en es) - SET_PROPERTY(CACHE PANDOC_LATEX_ENGINE PROPERTY STRINGS pdflatex xelatex lualatex) - SET_PROPERTY(CACHE PANDOC_FONT_SIZE PROPERTY STRINGS 10pt 12pt 14pt 16pt 18pt 20pt 30pt 35pt 40pt) - SET_PROPERTY(CACHE PANDOC_PAPER_SIZE PROPERTY STRINGS a4 a3) - LIST(APPEND PANDOC_FONTS "Liberation Sans" "Inconsolata" "FreeMono" "Palatino") - SET_PROPERTY(CACHE PANDOC_FONT_MAIN PROPERTY STRINGS ${PANDOC_FONTS}) - SET_PROPERTY(CACHE PANDOC_FONT_MONO PROPERTY STRINGS ${PANDOC_FONTS}) - #SET_PROPERTY(CACHE PANDOC_SECTION_NUMBERS PROPERTY STRINGS yes no) - - # CMake Options - SET(CMAKE_BUILD_TYPE - Release - CACHE STRING "Release" FORCE) - SET(CMAKE_INSTALL_PREFIX - ${PROJECT_BINARY_DIR}/install - CACHE STRING "Build path" FORCE) - - # FORMAT Options - - # Pdf Options - SET(IMG_EXT_PDF pdf) - #LIST(APPEND PARAMS_PDF "-V babel-lang=$(LANG)") - - # Others - # odt TODO - # doc TODO - # html TODO - # presentation TODO - - # Condition - IF(PANDOC_STANDALONE) - SET(PANDOC_STANDALONE_VAR -s) - ELSE() - SET(PANDOC_STANDALONE_VAR ) - ENDIF() - - # Configure pandoc for all projects, you can use your own variable into different modules - SET(PANDOC_PDF_CONFIG - ${PANDOC_STANDALONE_VAR} - --variable documentclass=${PANDOC_DOCUMENTCLASS} - --latex-engine=${PANDOC_LATEX_ENGINE} - --default-image-extension=${IMG_EXT_PDF} - --variable lang=${PANDOC_LANGUAGE} - --variable fontsize=${PANDOC_FONT_SIZE} - --variable papersize=${PANDOC_PAPER_SIZE} - #--variable fontfamily=${PANDOC_FONT_FAMILY} # only pdflatex - --variable mainfont=${PANDOC_FONT_MAIN} - --variable monofont=${PANDOC_FONT_MONO} - --variable margin-left=${PANDOC_MARGIN_LEFT} - --variable margin-right=${PANDOC_MARGIN_RIGHT} - --variable margin-top=${PANDOC_MARGIN_TOP} - --variable margin-bottom=${PANDOC_MARGIN_BOTTOM} - ) - - # Add extra includes - IF(PANDOC_INCLUDE_CONTENT) - SET(PANDOC_PDF_CONFIG ${PANDOC_PDF_CONFIG} --variable toc) - ENDIF() - - IF(PANDOC_INCLUDE_FIGURE) - SET(PANDOC_PDF_CONFIG ${PANDOC_PDF_CONFIG} --variable lof) - ENDIF() - - IF(PANDOC_INCLUDE_TABLE) - SET(PANDOC_PDF_CONFIG ${PANDOC_PDF_CONFIG} --variable lot) - ENDIF() - - MESSAGE(STATUS "pandoc variables configured!") -ENDMACRO() diff --git a/src/commands.h b/src/commands.h deleted file mode 100644 index 3e1373b..0000000 --- a/src/commands.h +++ /dev/null @@ -1,103 +0,0 @@ -#pragma once -#include <string> - -namespace commands -{ - class ClassCommands - { - public: - - static std::string ClassCommands::CMD_ONACQ; - static std::string ClassCommands::CMD_OFFACQ; - static std::string ClassCommands::CMD_STREAMONACQ; - static std::string ClassCommands::CMD_STREAMOFFACQ; - static std::string ClassCommands::CMD_NORMALACQ; - - static std::string ClassCommands::CMD_ONIMPEDANCEACQ; - static std::string ClassCommands::CMD_OFFIMPEDANCEACQ; - - static std::string ClassCommands::CMD_TESTACQ; - static std::string ClassCommands::CMD_CONFIGACQ; - static std::string ClassCommands::CMD_CONFIGACQ_PARAM1; - static std::string ClassCommands::CMD_CONFIGACQ_PARAM2; - static std::string ClassCommands::CMD_CONFIGACQ_PARAM3; - static std::string ClassCommands::CMD_CONFIGVELEC_PARAM4; - - static std::string ClassCommands::CMD_VELEC_SELECTED; - static std::string ClassCommands::CMD_VELEC_NOTSELECTED; - - - static std::string ClassCommands::CMD_ONSTIM; - static std::string ClassCommands::CMD_OFFSTIM; - static std::string ClassCommands::CMD_VELECSTIM; - - - static std::string ClassCommands::CMD_CONFIGELEC; - static std::string ClassCommands::CMD_CONFIGELEC_PARAM1; - - static std::string ClassCommands::CMD_CONFIGPATTERN; - static std::string ClassCommands::CMD_CONFIGPATTERN_PARAM1; - static std::string ClassCommands::CMD_CONFIGPATTERN_PARAM2; - static std::string ClassCommands::CMD_CONFIGPATTERN_PARAM3; - - static std::string ClassCommands::CMD_CONFIGVELEC; - static std::string ClassCommands::CMD_CONFIGVELEC_PARAM1; - static std::string ClassCommands::CMD_CONFIGVELEC_PARAM2; - static std::string ClassCommands::CMD_CONFIGVELEC_PARAM3; - - static std::string ClassCommands::CMD_SETFREQ; - - static std::string ClassCommands::CMD_SETDEVICENAME; - static std::string ClassCommands::CMD_SETHV; - static std::string ClassCommands::CMD_ONHV; - static std::string ClassCommands::CMD_OFFHV; - static std::string ClassCommands::CMD_ONLOGEVENTS; - - static std::string ClassCommands::CMD_OFFLOGEVENTS; - static std::string ClassCommands::CMD_SETINTERVAL; - static std::string ClassCommands::CMD_SETBUZZER; - static std::string ClassCommands::CMD_PLAYBUZZER; - static std::string ClassCommands::CMD_SETRTCDATE; - - static std::string ClassCommands::CMD_SETRTCTIME; - static std::string ClassCommands::CMD_SWITCHOFF; - static std::string ClassCommands::CMD_SETSDFUNCTION; - static std::string ClassCommands::CMD_SETSDNAME; - //PATTERN - - //paTTERN CLEAR PONER IGUAL DELETE - //VELEC - //VELEC SELECTED - //sTIM VELEC - //ACQIMPEDANCECONFIG - //ACQIMPEDANCEPOLARITY - - static std::string ClassCommands::CMD_INIT; - - static std::string ClassCommands::CMD_GETBATTERY; - static std::string ClassCommands::CMD_GETFW; - static std::string ClassCommands::CMD_GETDEVICENAME; - static std::string ClassCommands::CMD_GETLOGEVENTS; - //velec - static std::string ClassCommands::CMD_GETPATTERN; - static std::string ClassCommands::CMD_GETHV; - static std::string ClassCommands::CMD_GETINTERVAL; - static std::string ClassCommands::CMD_GETRTC; - static std::string ClassCommands::CMD_GETBUZZER; - static std::string ClassCommands::CMD_GETFRQUENCY; - static std::string ClassCommands::CMD_GETSDFUNCTION; - static std::string ClassCommands::CMD_GETSDNAME; - static std::string ClassCommands::CMD_GETHW; - static std::string ClassCommands::CMD_GETTIC; - - static std::string ClassCommands::CMD_BUFFERFLUSH; - - static std::string ClassCommands::CMD_AUX; - static std::string ClassCommands::CMD_AUX2; - - - - std::string ClassCommands::Log(std::string val); - - }; -} \ No newline at end of file diff --git a/src/commands.lib b/src/commands.lib deleted file mode 100644 index 2c6c5c1789c267362d537d5567966680fba04819..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 190998 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 E0ONeJxBvhE diff --git a/src/communication/.gitkeep b/src/communication/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/communication/CMakeLists.txt b/src/communication/CMakeLists.txt deleted file mode 100644 index 288f5b5..0000000 --- a/src/communication/CMakeLists.txt +++ /dev/null @@ -1,43 +0,0 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) -project(communication) -message("cmake ${PROJECT_NAME}") -if (DEBUG_CMAKE) - set(CMAKE_VERBOSE_MAKEFILE yes) -endif (DEBUG_CMAKE) - -include_directories(include ${Boost_INCLUDE_DIR}) - -set(communication_files_local - ${CMAKE_CURRENT_SOURCE_DIR}/src/udp_client.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/communication/udp_client.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/udp_server.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/communication/udp_server.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/async_serial.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/communication/async_serial.hpp) - -set(communication_files - ${communication_files_local} - PARENT_SCOPE) - -add_library(communication - ${communication_files_local}) - -target_link_libraries(communication logger ) -set_target_properties(communication PROPERTIES FOLDER communication) - -target_link_libraries(communication ${CMAKE_CURRENT_SOURCE_DIR}/../commands.lib) - -# Define headers for this library. PUBLIC headers are used for -# compiling the library, and will be added to consumers' build -# paths. -target_include_directories(communication PUBLIC - $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> - $<INSTALL_INTERFACE:include> - PRIVATE src) - -# This makes the project importable from the build directory -export(TARGETS communication - logger - yaml_tools - yaml-cpp - FILE communication_Config.cmake) \ No newline at end of file diff --git a/src/communication/include/.gitkeep b/src/communication/include/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/communication/include/communication/.gitkeep b/src/communication/include/communication/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/communication/include/communication/async_serial.hpp b/src/communication/include/communication/async_serial.hpp deleted file mode 100644 index 53d0d07..0000000 --- a/src/communication/include/communication/async_serial.hpp +++ /dev/null @@ -1,267 +0,0 @@ -/** -* @file async_serial.hpp -* @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> -* @date 2020 -* -* Copyright 2020 Tecnalia Research & Innovation. -* Distributed under the GNU GPL v3. For full terms see https://www.gnu.org/licenses/gpl.txt -* -* @brief Asynchronous serial port. -*/ - -#ifndef ASYNCSERIAL_H -#define ASYNCSERIAL_H - -#include <vector> -#include <thread> -#include <mutex> -#include <boost/asio.hpp> -#include <boost/bind.hpp> -#include <boost/utility.hpp> -#include <boost/function.hpp> -#include <boost/shared_array.hpp> - - -#include <communication/serial_listener.hpp> - -/** - * Used internally (pimpl) - */ -class AsyncSerialImpl; - -/** - * Asyncronous serial class. - * Intended to be a base class. - */ -class AsyncSerial: private boost::noncopyable -{ -public: - AsyncSerial(); - - /** - * Constructor. Creates and opens a serial device. - * \param devname serial device name, example "/dev/ttyS0" or "COM1" - * \param baud_rate serial baud rate - * \param opt_parity serial parity, default none - * \param opt_csize serial character size, default 8bit - * \param opt_flow serial flow control, default none - * \param opt_stop serial stop bits, default 1 - * \throws boost::system::system_error if cannot open the - * serial device - */ - AsyncSerial(const std::string& devname, unsigned int baud_rate, - boost::asio::serial_port_base::parity opt_parity= - boost::asio::serial_port_base::parity( - boost::asio::serial_port_base::parity::none), - boost::asio::serial_port_base::character_size opt_csize= - boost::asio::serial_port_base::character_size(8), - boost::asio::serial_port_base::flow_control opt_flow= - boost::asio::serial_port_base::flow_control( - boost::asio::serial_port_base::flow_control::none), - boost::asio::serial_port_base::stop_bits opt_stop= - boost::asio::serial_port_base::stop_bits( - boost::asio::serial_port_base::stop_bits::one)); - - /** - * Opens a serial device. - * \param devname serial device name, example "/dev/ttyS0" or "COM1" - * \param baud_rate serial baud rate - * \param opt_parity serial parity, default none - * \param opt_csize serial character size, default 8bit - * \param opt_flow serial flow control, default none - * \param opt_stop serial stop bits, default 1 - * \throws boost::system::system_error if cannot open the - * serial device - */ - void open(const std::string& devname, unsigned int baud_rate, - boost::asio::serial_port_base::parity opt_parity= - boost::asio::serial_port_base::parity( - boost::asio::serial_port_base::parity::none), - boost::asio::serial_port_base::character_size opt_csize= - boost::asio::serial_port_base::character_size(8), - boost::asio::serial_port_base::flow_control opt_flow= - boost::asio::serial_port_base::flow_control( - boost::asio::serial_port_base::flow_control::none), - boost::asio::serial_port_base::stop_bits opt_stop= - boost::asio::serial_port_base::stop_bits( - boost::asio::serial_port_base::stop_bits::one)); - - /** - * \return true if serial device is open - */ - bool isOpen() const; - - /** - * \return true if error were found - */ - bool errorStatus() const; - - /** - * Close the serial device - * \throws boost::system::system_error if any error - */ - void close(); - - /** - * Write data asynchronously. Returns immediately. - * \param data array of char to be sent through the serial device - * \param size array size - */ - void write(const char *data, size_t size); - - /** - * Write data asynchronously. Returns immediately. - * \param data to be sent through the serial device - */ - void write(const std::vector<char>& data); - - /** - * Write a string asynchronously. Returns immediately. - * Can be used to send ASCII data to the serial device. - * To send binary data, use write() - * \param s string to send - */ - void writeString(const std::string& s); - - ~AsyncSerial(); - - /** - * @brief Adds a listener to the msgs comming from serial - * @param listener Listener - */ - void addListener(SerialListener *listener); - - /** - * @brief Removes a listener to the msgs comming from serial - * @param listener Listener - */ - void removeListener(SerialListener *listener); - - /** - * Read buffer maximum size - */ - static const int readBufferSize=512; - - /** - * Flushes the read buffer - */ - void flush(); -private: - - /** - * Callback called to start an asynchronous read operation. - * This callback is called by the io_service in the spawned thread. - */ - void doRead(); - - /** - * Callback called at the end of the asynchronous operation. - * This callback is called by the io_service in the spawned thread. - */ - void readEnd(const boost::system::error_code& error, - size_t bytes_transferred); - - /** - * Callback called to start an asynchronous write operation. - * If it is already in progress, does nothing. - * This callback is called by the io_service in the spawned thread. - */ - void doWrite(); - - /** - * Callback called at the end of an asynchronuous write operation, - * if there is more data to write, restarts a new write operation. - * This callback is called by the io_service in the spawned thread. - */ - void writeEnd(const boost::system::error_code& error); - - /** - * Callback to close serial port - */ - void doClose(); - - boost::shared_ptr<AsyncSerialImpl> pimpl; - - //! indicates whether the buffer is being read in order to flush it - bool flushing_; - -protected: - - /** - * To allow derived classes to report errors - * \param e error status - */ - void setErrorStatus(bool e); - - /** - * To allow derived classes to set a read callback - */ - void setReadCallback(const - boost::function<void (const char*, size_t)>& callback); - - /** - * To unregister the read callback in the derived class destructor so it - * does not get called after the derived class destructor but before the - * base class destructor - */ - void clearReadCallback(); - -private: - //! listeners - std::vector<SerialListener*> listeners_; - -}; - -/** - * Asynchronous serial class with read callback. User code can write data - * from one thread, and read data will be reported through a callback called - * from a separate thred. - */ -class CallbackAsyncSerial: public AsyncSerial -{ -public: - CallbackAsyncSerial(); - - /** - * Opens a serial device. - * \param devname serial device name, example "/dev/ttyS0" or "COM1" - * \param baud_rate serial baud rate - * \param opt_parity serial parity, default none - * \param opt_csize serial character size, default 8bit - * \param opt_flow serial flow control, default none - * \param opt_stop serial stop bits, default 1 - * \throws boost::system::system_error if cannot open the - * serial device - */ - CallbackAsyncSerial(const std::string& devname, unsigned int baud_rate, - boost::asio::serial_port_base::parity opt_parity= - boost::asio::serial_port_base::parity( - boost::asio::serial_port_base::parity::none), - boost::asio::serial_port_base::character_size opt_csize= - boost::asio::serial_port_base::character_size(8), - boost::asio::serial_port_base::flow_control opt_flow= - boost::asio::serial_port_base::flow_control( - boost::asio::serial_port_base::flow_control::none), - boost::asio::serial_port_base::stop_bits opt_stop= - boost::asio::serial_port_base::stop_bits( - boost::asio::serial_port_base::stop_bits::one)); - - /** - * Set the read callback, the callback will be called from a thread - * owned by the CallbackAsyncSerial class when data arrives from the - * serial port. - * \param callback the receive callback - */ - void setCallback(const - boost::function<void (const char*, size_t)>& callback); - - /** - * Removes the callback. Any data received after this function call will - * be lost. - */ - void clearCallback(); - - virtual ~CallbackAsyncSerial(); -}; - -#endif //ASYNCSERIAL_H \ No newline at end of file diff --git a/src/communication/include/communication/serial_listener.hpp b/src/communication/include/communication/serial_listener.hpp deleted file mode 100644 index 7bff29f..0000000 --- a/src/communication/include/communication/serial_listener.hpp +++ /dev/null @@ -1,5 +0,0 @@ -class SerialListener -{ -public: - virtual void serialMsgReceived(std::string msg){} -}; \ No newline at end of file diff --git a/src/communication/include/communication/udp_client.hpp b/src/communication/include/communication/udp_client.hpp deleted file mode 100644 index 6a94d00..0000000 --- a/src/communication/include/communication/udp_client.hpp +++ /dev/null @@ -1,121 +0,0 @@ -/** -* @file udp_client.hpp -* @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> -* @date 2017 -* -* Copyright 2017 Tecnalia Research & Innovation. -* Distributed under the GNU GPL v3. For full terms see https://www.gnu.org/licenses/gpl.txt -* -* @brief Asynchronous udp client -*/ -#ifndef UDP_CLIENT_HPP -#define UDP_CLIENT_HPP - -#include <boost/asio.hpp> -#include <boost/bind.hpp> -#include <boost/thread.hpp> -#include <boost/asio/deadline_timer.hpp> -#include <iostream> -#include <mutex> - -using boost::asio::ip::udp; -using boost::asio::deadline_timer; - -class UDPClient -{ -public: - UDPClient(const std::string& host, const std::string& port, int timeout); - ~UDPClient(); - - /** - * @brief send message to endpoint - * @param msg - */ - void send(const std::string& msg); - - /** - * @brief start the client's loop - */ - void start(); - - /** - * @brief closes service and socket - */ - void close(); - - /** - * @brief get the latest received data - * @return data - */ - std::string getData(); - - /** - * @brief indicates if server is available - * @return server availability - */ - bool isServerAvailable(); - - /** - * @brief set timeout - * @return timeout - */ - void setTimeout(int timeout); - - /** - * @brief gets the IP of the endpoint - * @return IP of the endpoint - */ - std::string getIP(); - - /** - * @brief gets the port of the endpoint - * @return Port of the endpoint - */ - int getPort(); - -private: - boost::asio::io_service io_service_; - udp::socket socket_; - udp::endpoint endpoint_; - deadline_timer deadline_; - //! runner thread - boost::thread t; - //! buffer where received data is going to be stored - //boost::asio::mutable_buffer buffer_; - //! Variable in which the serial data that is read is stored - unsigned char *bytes_read_; - //! number of bytes to be read each time - std::size_t number_bytes_to_be_read_; - //! shared data - std::string data_; - //! mutex for accesing buffer - std::mutex mtx_; - //! error number - int error_number_; - //! max error number - int error_max_; - //! is server available? - bool server_available_; - //! timeout millis (if negative it is set to infinite) - int timeout_; - - /** - * @brief Check whether the deadline has passed - * @param error - */ - void check_deadline(const boost::system::error_code& error); - - /** - * @brief receive messahge - */ - void receive_(); - - /** - * @brief handles a connection from a client sending the current data - * @param error - * @param bytes_transferred - */ - void handleReceive_(const boost::system::error_code& e, std::size_t size); -}; - -#endif // UDP_CLIENT_HPP \ No newline at end of file diff --git a/src/communication/include/communication/udp_server.hpp b/src/communication/include/communication/udp_server.hpp deleted file mode 100644 index 34c3dd2..0000000 --- a/src/communication/include/communication/udp_server.hpp +++ /dev/null @@ -1,100 +0,0 @@ -/** -* @file udp_server.hpp -* @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> -* @date 2017 -* -* Copyright 2017 Tecnalia Research & Innovation. -* Distributed under the GNU GPL v3. For full terms see https://www.gnu.org/licenses/gpl.txt -* -* @brief Asynchronous udp server -*/ -#ifndef UDP_SERVER_HPP -#define UDP_SERVER_HPP - -#include <boost/asio.hpp> -#include <boost/bind.hpp> -#include <boost/thread.hpp> -#include <boost/array.hpp> -#include <boost/asio/deadline_timer.hpp> -#include <iostream> -#include <boost/circular_buffer.hpp> -#include <communication/udp_server_listener.hpp> - -using boost::asio::ip::udp; -using boost::asio::deadline_timer; - -class UDPServer -{ -public: - UDPServer(int listening_port); - ~UDPServer(); - - /** - * @brief start the listening process - */ - void startListening(); - - /** - * @brief stop the listening process - */ - void stopListening(); - - /** - * @brief get the first data in the buffer - * @param data_str the data - * @param messages_in_queue number of messages in the queue - * @return the first data in the queue. False if there is not data in the queue - */ - bool getFirstDataInQueue(std::string &data_str, int &messages_in_queue); - - /** - * @brief Adds a listener to the msgs the server receives - * @param listener Listener - */ - void addListener(UdpServerListener *listener); - - /** - * @brief Removes a listener to the msgs the server receives - * @param listener Listener - */ - void removeListener(UdpServerListener *listener); - -private: - //! listening socket - udp::socket* socket_; - //! server thread - boost::thread* server_thread_; - //! io service - boost::asio::io_service io_service_; - //! client end points - udp::endpoint temp_remote_endpoint_; - //!temporal buffer - boost::array<char, 255> temp_recv_buffer_; - //circular buffer for string received data - boost::circular_buffer<std::string> received_data_buffer_; - //mutex for buffer - boost::mutex buffer_mtx_; - //!thread; - boost::shared_ptr<boost::thread> thread_; - //! listeners - std::vector<UdpServerListener*> listeners_; - - /** - * @brief handles a connection from a client sending the current data - * @param error - * @param bytes_transferred - */ - void handleReceive(const boost::system::error_code& error, std::size_t bytes_transferred); - - /** - * @brief continue with the listening process - */ - void continueListening(); - - /** - * @brief run the service - */ - void serviceRun(); -}; - -#endif // UDP_SERVER_HPP diff --git a/src/communication/include/communication/udp_server_listener.hpp b/src/communication/include/communication/udp_server_listener.hpp deleted file mode 100644 index 72a446b..0000000 --- a/src/communication/include/communication/udp_server_listener.hpp +++ /dev/null @@ -1,5 +0,0 @@ -class UdpServerListener -{ -public: - virtual void udpMsgReceived(std::string msg, std::string ip){} -}; \ No newline at end of file diff --git a/src/communication/src/.gitkeep b/src/communication/src/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/communication/src/async_serial.cpp b/src/communication/src/async_serial.cpp deleted file mode 100644 index 58a291c..0000000 --- a/src/communication/src/async_serial.cpp +++ /dev/null @@ -1,395 +0,0 @@ -/** -* @file async_serial.cpp -* @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> -* @date 2017 -* -* Copyright 2017 Tecnalia Research & Innovation. -* Distributed under the GNU GPL v3. For full terms see https://www.gnu.org/licenses/gpl.txt -* -* @brief Asynchronous serial port. -*/ - - -#include <communication/async_serial.hpp> - -#include <string> -#include <algorithm> -#include <iostream> -#include <boost/bind.hpp> - -//using namespace std; -using namespace boost; - -// -//Class AsyncSerial -// - - -class AsyncSerialImpl: private boost::noncopyable -{ -public: - AsyncSerialImpl(): io(), port(io), backgroundThread(), open(false), - error(false) {} - - boost::asio::io_service io; ///< Io service object - boost::asio::serial_port port; ///< Serial port object - std::thread backgroundThread; ///< Thread that runs read/write operations - bool open; ///< True if port open - bool error; ///< Error flag - mutable std::mutex errorMutex; ///< Mutex for access to error - - /// Data are queued here before they go in writeBuffer - std::vector<char> writeQueue; - boost::shared_array<char> writeBuffer; ///< Data being written - size_t writeBufferSize; ///< Size of writeBuffer - std::mutex writeQueueMutex; ///< Mutex for access to writeQueue - //char readBuffer[AsyncSerial::readBufferSize]; ///< data being read - boost::asio::streambuf readBuffer_; - - /// Read complete callback - boost::function<void (const char*, size_t)> callback; -}; - -AsyncSerial::AsyncSerial(): pimpl(new AsyncSerialImpl) -{ - flushing_ = false; -} - -AsyncSerial::AsyncSerial(const std::string& devname, unsigned int baud_rate, - asio::serial_port_base::parity opt_parity, - asio::serial_port_base::character_size opt_csize, - asio::serial_port_base::flow_control opt_flow, - asio::serial_port_base::stop_bits opt_stop) - : pimpl(new AsyncSerialImpl) -{ - flushing_ = false; - open(devname,baud_rate,opt_parity,opt_csize,opt_flow,opt_stop); -} - -void AsyncSerial::open(const std::string& devname, unsigned int baud_rate, - asio::serial_port_base::parity opt_parity, - asio::serial_port_base::character_size opt_csize, - asio::serial_port_base::flow_control opt_flow, - asio::serial_port_base::stop_bits opt_stop) -{ - if(isOpen()) close(); - - setErrorStatus(true);//If an exception is thrown, error_ remains true - pimpl->port.open(devname); - pimpl->port.set_option(asio::serial_port_base::baud_rate(baud_rate)); - pimpl->port.set_option(opt_parity); - pimpl->port.set_option(opt_csize); - pimpl->port.set_option(opt_flow); - pimpl->port.set_option(opt_stop); - - //This gives some work to the io_service before it is started - pimpl->io.post(boost::bind(&AsyncSerial::doRead, this)); - - std::thread t(boost::bind(&asio::io_service::run, &pimpl->io)); - pimpl->backgroundThread.swap(t); - setErrorStatus(false);//If we get here, no error - pimpl->open=true; //Port is now open -} - -bool AsyncSerial::isOpen() const -{ - return pimpl->open; -} - -bool AsyncSerial::errorStatus() const -{ - std::lock_guard<std::mutex> l(pimpl->errorMutex); - return pimpl->error; -} - -void AsyncSerial::close() -{ - if(!isOpen()) return; - - pimpl->open=false; - pimpl->io.post(boost::bind(&AsyncSerial::doClose, this)); - pimpl->backgroundThread.join(); - pimpl->io.reset(); - if(errorStatus()) - { - throw(boost::system::system_error(boost::system::error_code(), - "Error while closing the device")); - } -} - -void AsyncSerial::write(const char *data, size_t size) -{ - { - std::lock_guard<std::mutex> l(pimpl->writeQueueMutex); - pimpl->writeQueue.insert(pimpl->writeQueue.end(),data,data+size); - } - pimpl->io.post(boost::bind(&AsyncSerial::doWrite, this)); -} - -void AsyncSerial::write(const std::vector<char>& data) -{ - { - std::lock_guard<std::mutex> l(pimpl->writeQueueMutex); - pimpl->writeQueue.insert(pimpl->writeQueue.end(),data.begin(), - data.end()); - } - pimpl->io.post(boost::bind(&AsyncSerial::doWrite, this)); -} - -void AsyncSerial::writeString(const std::string& s) -{ - { - std::lock_guard<std::mutex> l(pimpl->writeQueueMutex); - pimpl->writeQueue.insert(pimpl->writeQueue.end(),s.begin(),s.end()); - } - pimpl->io.post(boost::bind(&AsyncSerial::doWrite, this)); -} - -AsyncSerial::~AsyncSerial() -{ - if(isOpen()) - { - try { - close(); - } catch(...) - { - //Don't throw from a destructor - } - } -} - -void AsyncSerial::doRead() -{ - /*pimpl->port.async_read_some(asio::buffer(pimpl->readBuffer,readBufferSize), - boost::bind(&AsyncSerial::readEnd, - this, - asio::placeholders::error, - asio::placeholders::bytes_transferred));*/ - boost::asio::async_read_until(pimpl->port, pimpl->readBuffer_, "\r", boost::bind(&AsyncSerial::readEnd, this, _1, _2)); -} - -void AsyncSerial::readEnd(const boost::system::error_code& error, - size_t bytes_transferred) -{ - if(error) - { - #ifdef __APPLE__ - if(error.value()==45) - { - //Bug on OS X, it might be necessary to repeat the setup - //http://osdir.com/ml/lib.boost.asio.user/2008-08/msg00004.html - doRead(); - return; - } - #endif //__APPLE__ - //error can be true even because the serial port was closed. - //In this case it is not a real error, so ignore - if(isOpen()) - { - doClose(); - setErrorStatus(true); - } - } - else - { - if(!flushing_) - { - flushing_ = false; - /*if(pimpl->callback) pimpl->callback(pimpl->readBuffer, - bytes_transferred);*/ - /*std::istream response_stream(&pimpl->readBuffer_); - std::string data_str; - std::getline(response_stream, data_str);*/ - std::string delimiter = "\r"; - std::string data_str{ - boost::asio::buffers_begin(pimpl->readBuffer_.data()), - boost::asio::buffers_begin(pimpl->readBuffer_.data()) + bytes_transferred - delimiter.size()}; - // Consume through the first delimiter. - pimpl->readBuffer_.consume(bytes_transferred); - - //std::cout << "[Serial::readEnd]Data str: " << data_str << ". Data length: " << data_str.length() << std::endl; - - for(int i = 0; i < listeners_.size(); i++) - { - listeners_[i]->serialMsgReceived(data_str); - } - } - doRead(); - } -} - -void AsyncSerial::doWrite() -{ - //If a write operation is already in progress, do nothing - if(pimpl->writeBuffer==0) - { - std::lock_guard<std::mutex> l(pimpl->writeQueueMutex); - pimpl->writeBufferSize=pimpl->writeQueue.size(); - pimpl->writeBuffer.reset(new char[pimpl->writeQueue.size()]); - copy(pimpl->writeQueue.begin(),pimpl->writeQueue.end(), - pimpl->writeBuffer.get()); - pimpl->writeQueue.clear(); - async_write(pimpl->port,asio::buffer(pimpl->writeBuffer.get(), - pimpl->writeBufferSize), - boost::bind(&AsyncSerial::writeEnd, this, asio::placeholders::error)); - } -} - -void AsyncSerial::writeEnd(const boost::system::error_code& error) -{ - if(!error) - { - std::lock_guard<std::mutex> l(pimpl->writeQueueMutex); - if(pimpl->writeQueue.empty()) - { - pimpl->writeBuffer.reset(); - pimpl->writeBufferSize=0; - - return; - } - pimpl->writeBufferSize=pimpl->writeQueue.size(); - pimpl->writeBuffer.reset(new char[pimpl->writeQueue.size()]); - copy(pimpl->writeQueue.begin(),pimpl->writeQueue.end(), - pimpl->writeBuffer.get()); - pimpl->writeQueue.clear(); - async_write(pimpl->port,asio::buffer(pimpl->writeBuffer.get(), - pimpl->writeBufferSize), - boost::bind(&AsyncSerial::writeEnd, this, asio::placeholders::error)); - } else { - setErrorStatus(true); - doClose(); - } -} - -void AsyncSerial::doClose() -{ - boost::system::error_code ec; - pimpl->port.cancel(ec); - if(ec) setErrorStatus(true); - pimpl->port.close(ec); - if(ec) setErrorStatus(true); -} - -void AsyncSerial::setErrorStatus(bool e) -{ - std::lock_guard<std::mutex> l(pimpl->errorMutex); - pimpl->error=e; -} - -void AsyncSerial::setReadCallback(const boost::function<void (const char*, size_t)>& callback) -{ - pimpl->callback=callback; -} - -void AsyncSerial::clearReadCallback() -{ - pimpl->callback.clear(); -} - -void AsyncSerial::addListener(SerialListener *listener) -{ - std::vector<SerialListener*>::iterator it = std::find(listeners_.begin(), listeners_.end(), listener); - if(it == listeners_.end()) - { - //std::cout << "[Serial::addListener]Add new listener" << std::endl; - listeners_.push_back(listener); - } -} - -void AsyncSerial::removeListener(SerialListener *listener) -{ - std::vector<SerialListener*>::iterator it = std::find(listeners_.begin(), listeners_.end(), listener); - if(it != listeners_.end()) - { - //std::cout << "[Serial::removeListener]Remove listener" << std::endl; - listeners_.erase(it); - } -} - -void AsyncSerial::flush() -{ - //std::cout << "[Serial::flush] Flush" << std::endl; - int value = 0; - bool error_in_method = false; - - #ifdef __unix__ - ::tcflush(pimpl->port.native_handle(), TCIOFLUSH); - #elif defined(_WIN32) || defined(WIN32) - if (PurgeComm(pimpl->port.native_handle(), PURGE_RXCLEAR | PURGE_TXCLEAR)) - { - //std::cout << "[Serial::flush] Flush done" << std::endl; - } - else - { - std::cout << "[Serial::flush] Error" << std::endl; - } - #endif - - // #ifdef __unix__ - // if (0 == ::ioctl(pimpl->port.lowest_layer().native_handle(), - // FIONREAD, &value)) - // { - // std::cout << "[Serial::flush] ioctl error " << std::endl; - // error_in_method = true; - // } - // #elif defined(_WIN32) || defined(WIN32) - // COMSTAT status; - // if (0 != ::ClearCommError(pimpl->port.lowest_layer().native_handle(), - // NULL, &status)) - // { - // value = status.cbInQue; - // } - // else - // { - // std::cout << "[Serial::flush] ClearCommError error" << std::endl; - // error_in_method = true; - // } - // #endif - - // if(!error_in_method) - // { - // std::size_t remaining_bytes = static_cast<size_t>(value); - // std::cout << "[Serial::flush] remaining_bytes:" << remaining_bytes << std::endl; - // if(remaining_bytes > 0) - // { - // flushing_ = true; - // boost::asio::async_read(pimpl->port, pimpl->readBuffer_, boost::bind(&AsyncSerial::readEnd, this, _1, _2)); - // } - // } -} - -// -//Class CallbackAsyncSerial -// - -CallbackAsyncSerial::CallbackAsyncSerial(): AsyncSerial() -{ - -} - -CallbackAsyncSerial::CallbackAsyncSerial(const std::string& devname, - unsigned int baud_rate, - asio::serial_port_base::parity opt_parity, - asio::serial_port_base::character_size opt_csize, - asio::serial_port_base::flow_control opt_flow, - asio::serial_port_base::stop_bits opt_stop) - :AsyncSerial(devname,baud_rate,opt_parity,opt_csize,opt_flow,opt_stop) -{ - -} - -void CallbackAsyncSerial::setCallback(const - boost::function<void (const char*, size_t)>& callback) -{ - setReadCallback(callback); -} - -void CallbackAsyncSerial::clearCallback() -{ - clearReadCallback(); -} - -CallbackAsyncSerial::~CallbackAsyncSerial() -{ - clearReadCallback(); -} \ No newline at end of file diff --git a/src/communication/src/udp_client.cpp b/src/communication/src/udp_client.cpp deleted file mode 100644 index 79c4304..0000000 --- a/src/communication/src/udp_client.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/** -* @file udp_client.cpp -* @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> -* @date 2017 -* -* Copyright 2017 Tecnalia Research & Innovation. -* Distributed under the GNU GPL v3. For full terms see https://www.gnu.org/licenses/gpl.txt -* -* @brief Asynchronous udp client -*/ - -#include <communication/udp_client.hpp> - -UDPClient::UDPClient(const std::string& host,const std::string& port, int timeout) - : socket_(io_service_, udp::endpoint(udp::v4(), 0)), - deadline_(io_service_) -{ - //std::cout << "UDPClient constructor start" << std::endl; - udp::resolver resolver(io_service_); - udp::resolver::query query(udp::v4(), host, port); - udp::resolver::iterator iter = resolver.resolve(query); - endpoint_ = *iter; - number_bytes_to_be_read_ = 32; - bytes_read_ = new unsigned char[number_bytes_to_be_read_]; - error_number_ = 0; - error_max_ = 10; - server_available_ = true; - timeout_ = timeout; -} - -UDPClient::~UDPClient() -{ - close(); - std::cout << "Wait thread to stop" << std::endl; - t.join(); - std::cout << "Thread stopped" << std::endl; - delete [] bytes_read_; -} - -void UDPClient::close() -{ - std::cout << "Close socket" << std::endl; - socket_.close(); - std::cout << "Close service" << std::endl; - io_service_.stop(); - std::cout << "Service closed" << std::endl; -} - -void UDPClient::start() -{ - //std::cout << "UDPClient start" << std::endl; - receive_(); - t = boost::thread(boost::bind(&boost::asio::io_service::run, &io_service_)); - t.detach(); -} - -void UDPClient::send(const std::string& msg) { - //std::cout << "UDPClient send" << msg << std::endl; - socket_.send_to(boost::asio::buffer(msg, msg.size()), endpoint_); -} - -void UDPClient::receive_() -{ - //std::cout << "UDPClient receive_" << std::endl; - int timeout; - mtx_.lock(); - timeout = timeout_; - mtx_.unlock(); - - if(timeout_ < 0) - { - deadline_.expires_at(boost::posix_time::pos_infin); - } - else - { - deadline_.expires_from_now(boost::posix_time::milliseconds(timeout)); - } - deadline_.async_wait(boost::bind(&UDPClient::check_deadline, this, boost::asio::placeholders::error)); - socket_.async_receive(boost::asio::buffer(&bytes_read_[0], number_bytes_to_be_read_), boost::bind(&UDPClient::handleReceive_, this, _1, _2)); -} - -void UDPClient::check_deadline(const boost::system::error_code& error) -{ - //std::cout << "check_deadline" << std::endl; - // Was the timeout cancelled? - if (error) - { - // yes - return; - } - //std::cout << "Deadline passed!!. Cancel socket async calls" << std::endl; - socket_.cancel(); -} - -void UDPClient::handleReceive_(const boost::system::error_code& e, std::size_t size) -{ - if (!e) - { - //std::cout << "UDPClient handleReceive_. size: " << size << std::endl; - // Read has finished, so cancel the timer. - deadline_.cancel(); - - mtx_.lock(); - if(bytes_read_ != NULL) - data_ = std::string (reinterpret_cast<char const*>(bytes_read_), size); - mtx_.unlock(); - } - else - { - //std::cout << "Read cancelled"<< std::endl; - error_number_ += 1; - if(error_number_ >= error_max_) - { - std::cout << "Server seems not to be available"<< std::endl; - mtx_.lock(); - server_available_ = false; - mtx_.unlock(); - return; - } - else - { - //std::cout << "Rearm reading"<< std::endl; - } - } - receive_(); -} - -std::string UDPClient::getData() -{ - std::string data; - mtx_.lock(); - data = data_; - data_ = ""; - mtx_.unlock(); - return data; -} - -std::string UDPClient::getIP() -{ - return endpoint_.address().to_string(); -} - -int UDPClient::getPort() -{ - return endpoint_.port(); -} - -bool UDPClient::isServerAvailable() -{ - bool server_available; - mtx_.lock(); - server_available = server_available_; - mtx_.unlock(); - return server_available; -} - - -void UDPClient::setTimeout(int timeout) -{ - mtx_.lock(); - timeout_ = timeout; - mtx_.unlock(); -} \ No newline at end of file diff --git a/src/communication/src/udp_server.cpp b/src/communication/src/udp_server.cpp deleted file mode 100644 index 845779c..0000000 --- a/src/communication/src/udp_server.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/** -* @file udp_server.cpp -* @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> -* @date 2017 -* -* Copyright 2017 Tecnalia Research & Innovation. -* Distributed under the GNU GPL v3. For full terms see https://www.gnu.org/licenses/gpl.txt -* -* @brief Asynchronous udp server -*/ -#include <communication/udp_server.hpp> -#include "../../commands.h" -UDPServer::UDPServer(int listening_port) -{ - socket_ = new udp::socket(io_service_, udp::endpoint(udp::v4(), listening_port)); - received_data_buffer_.set_capacity(1000); -} - -UDPServer::~UDPServer() -{ - if(socket_ != nullptr) - { - delete socket_; - } -} - -void UDPServer::startListening() -{ - std::cout << "[UDPServer::startListening] " << std::endl; - continueListening(); - thread_ = boost::shared_ptr< boost::thread >(new boost::thread(boost::bind(&UDPServer::serviceRun, this))); - thread_->detach(); -} - -void UDPServer::serviceRun() -{ - //std::cout << "[UDPServer::serviceRun] " << std::endl; - io_service_.run(); -} - -void UDPServer::continueListening() -{ - socket_->async_receive_from(boost::asio::buffer(temp_recv_buffer_), temp_remote_endpoint_, boost::bind(&UDPServer::handleReceive, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); -} - -void UDPServer::stopListening() -{ - std::cout << "[UDPServer::stopListening] Stopping" << std::endl; - socket_->shutdown(boost::asio::ip::udp::socket::shutdown_both); - socket_->close(); - io_service_.stop(); - std::cout << "[UDPServer::stopListening] Stopped" << std::endl; -} - -void UDPServer::handleReceive(const boost::system::error_code& error, std::size_t bytes_transferred) -{ - //std::cout << "[UDPServer] connection received" << std::endl; - - if (!error || error == boost::asio::error::message_size) - { - std::string message; - std::copy(temp_recv_buffer_.begin(), temp_recv_buffer_.begin() + bytes_transferred, std::back_inserter(message)); - commands::ClassCommands d; - std::string msg2 = d.Log(message); - - std::cout << "[UDPServer] connection received ---->>>>" << msg2 << std::endl;//message << std::endl; - //std::cout << "[UDPServer::handleReceive] Message: " << message << ". Message length: " << message.length()<< std::endl; - for(int i = 0; i < listeners_.size(); i++) - { - listeners_[i]->udpMsgReceived(message, temp_remote_endpoint_.address().to_string()); - } - } - - continueListening(); -} - -bool UDPServer::getFirstDataInQueue(std::string &data_str, int &messages_in_queue) -{ - bool response = false; - buffer_mtx_.lock(); - messages_in_queue = received_data_buffer_.size(); - if (!received_data_buffer_.empty()) - { - data_str = received_data_buffer_[0]; - received_data_buffer_.pop_front(); - response = true; - } - buffer_mtx_.unlock(); - - return response; -} - -void UDPServer::addListener(UdpServerListener *listener) -{ - std::vector<UdpServerListener*>::iterator it = std::find(listeners_.begin(), listeners_.end(), listener); - if(it == listeners_.end()) - { - //std::cout << "[UDPServer::addListener]Add new listener" << std::endl; - listeners_.push_back(listener); - } -} - -void UDPServer::removeListener(UdpServerListener *listener) -{ - std::vector<UdpServerListener*>::iterator it = std::find(listeners_.begin(), listeners_.end(), listener); - if(it != listeners_.end()) - { - //std::cout << "[UDPServer::removeListener]Remove listener" << std::endl; - listeners_.erase(it); - } -} \ No newline at end of file diff --git a/src/docs/.gitkeep b/src/docs/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/docs/Doxyfile.in b/src/docs/Doxyfile.in deleted file mode 100644 index a14ab9d..0000000 --- a/src/docs/Doxyfile.in +++ /dev/null @@ -1,9 +0,0 @@ -OUTPUT_DIRECTORY = @CMAKE_CURRENT_BINARY_DIR@/doc_doxygen/ -INPUT = @CMAKE_CURRENT_SOURCE_DIR@/../api/class/include/class/class.hpp -INPUT += @CMAKE_CURRENT_SOURCE_DIR@/../api/class/include/class/class_structures.hpp -INPUT += @CMAKE_CURRENT_SOURCE_DIR@/../api/class/include/class/class_error.hpp -INPUT += @CMAKE_CURRENT_SOURCE_DIR@/../api/class/include/class/class_cb.hpp -INPUT += @CMAKE_CURRENT_SOURCE_DIR@/README.md -HTML_FOOTER = @CMAKE_CURRENT_SOURCE_DIR@/footer.html -USE_MDFILE_AS_MAINPAGE = README.md -PROJECT_NAME = "CLASS API" diff --git a/src/docs/README.html b/src/docs/README.html deleted file mode 100644 index 2e2390c..0000000 --- a/src/docs/README.html +++ /dev/null @@ -1 +0,0 @@ -<meta http-equiv="REFRESH" content="0;URL=docs/index.html"> \ No newline at end of file diff --git a/src/docs/README.md b/src/docs/README.md deleted file mode 100644 index 33f894c..0000000 --- a/src/docs/README.md +++ /dev/null @@ -1,121 +0,0 @@ -The CLASS_API has been programmed in C++ (11). - -The documentation, you are just reading, has been automatically produced from comments on the source code using [Doxygen](https://www.doxygen.nl/index.html). You can dig in the different classes, methods and structures following the links in the webpage. - -Wrappers for C and C# have been generated using [SWIG](http://www.swig.org/). These wrappers allows for interacting with the CLASS_API from these programming languages. More languages will be taken into account in the future. - -The CLASS_API folder is structured as follows: - * [docs](../): the documentation of the CLASS_API is stored here. Automatically generated with [Doxygen](https://www.doxygen.nl/index.html). - * [tests](../../../tests): it contains examples on how to use the API for different programming languages. - * [c++](../../../api/class/Release): files for programming c++ client applications. - - * [C#](../../../wrappers/c_sharp/Release): wrapper to be used in C# scripts. - -# How to program your own code - -Examples on how to use the API for stimulation, acquisition and battery request can be found in [examples](../../../tests). These examples are available for C++ and C#. - -`CMakeLists.txt` files have been added where applicable to recompile the source code of the examples. - -Appropriate build tools (which depend on the OS and the language) must be installed before recompiling. Requisites for each of the languages can be found below. - -## C++ - -This point illustrates how to rebuild the c++ examples. For building your own program, take the source code and the CMakeLists.txt file in `src` folder as inspiration. - -### Pre-requisites - -- [CMake](https://cmake.org/): - - Windows: Download the [.msi file](https://cmake.org/download/) and install it. - - Linux: - - ``` - sudo snap install cmake --classic - ``` -- Compiler: - - Windows: Build tools for Visual Studio (2017): There is no official link but [this one](https://aka.ms/vs/15/release/vs_buildtools.exe) works. - - Linux: - - ``` - sudo apt-get install build-essential - ``` - -### Building with CMake - -Follow these steps in order to rebuild the examples (it is supposed `src` and `build` directories will be at same level): -- Create a `build` folder and enter the folder. -``` -mkdir build -cd build -``` -- Execute the following command to configure the platform: - - - Windows - ``` - cmake -G "Visual Studio 15 2017 Win64" CMAKE_BUILD_TYPE=Release ../src/cpp - ``` - - - Linux - ``` - cmake ../src/cpp - ``` -- Then execute the following command to build the executables: - - Windows - ``` - cmake --build . --target ALL_BUILD --config Release - ``` - - Linux - ``` - make - ``` - -> NOTE: The executables need the location of the libraries (dlls) to execute correctly. This can be done through a .bat file. - -## CSharp - -This point illustrates how to rebuild csharp examples using CMake and Microsoft Visual Studio. - -### Pre-requisites - -Two options: - - [CMake](https://cmake.org/download/) and Build tools for Visual Studio (2017): There is no official link but [this one](https://aka.ms/vs/15/release/vs_buildtools.exe) works. - - Microsoft Visual Studio - -### Building with CMake - -Follow these steps in order to rebuild the examples with CMake (it is supposed `src` and `build` directories will be at same level): -- Create a `build` folder and enter the folder. -``` -mkdir build -cd build -``` -- Execute the following command to configure the platform: -``` -cmake -G "Visual Studio 15 2017 Win64" CMAKE_BUILD_TYPE=Release ../src/csharp -``` -- Then execute the following command to build the executables: -``` -cmake --build . --target ALL_BUILD --config Release -``` -- The executables will be on `Release` folder in the created `build` directory. - -> NOTE: The executables need the location of the dlls to execute correctly. This can be done through a .bat file. There are some examples on how to do it [here](../../../tests/test_wrappers/csharp). - -### Building with Microsoft Visual Studio - -The user can use also Microsoft Visual Studio to program his/her examples in case the user is not used to [CMake](https://cmake.org/). Follow the next steps to do it: - -* Create and empty project (a Console Project, for example). -* Copy the source code in the main method of one of the [csharp examples](../../../tests/test_wrappers/csharp) to the main in your project or create new code using them as reference. -* To remove existing warnings/errors about non-existing references, click "Add existing elements" to the project and select all the `.cs` files that are located in the [csharp library folder](../../../wrappers/c_sharp/Release) or directly copy them to the source folder of the project. -* Then select between adding DLLs to the executable path or reference them in a .bat file: - * Add DLLS to executable path: - * Click "Add existing elements" to the project and select `csharpClass.dll` in the [csharp library folder](../../../wrappers/c_sharp/Release) and `class.dll` and `python37.dll` in the [csharp library folder](../../../wrappers/c_sharp/Release). - * Check the box in its properties to copy the file to the output folder when the executable is generated. - * Copy and modify one of the existing .bat files in [csharp examples folder] (../examples/src/csharp). -* Select `x64` as the platform for the solution and compile. - - -> NOTE: `csharpClass.dll` and `class.dll` should be in the same folder as the executable. If not, its location must be indicated through a `.bat` file which extends the `PATH` environment variable with the location of the `csharpClass.dll`. - -> NOTE: The csharp library has been tested with Microsoft Visual Studio 2017. \ No newline at end of file diff --git a/src/docs/footer.html b/src/docs/footer.html deleted file mode 100644 index 7cc103b..0000000 --- a/src/docs/footer.html +++ /dev/null @@ -1,9 +0,0 @@ -<hr> -<p align="center"> - <a href="https://www.tecnalia.com"> - <img width="200" height="60" src="./images/tecnalia.jpg" alt="Tecnalia"> - </a> -</p> -<p align="center"> -©Copyright 2021 Tecnalia -</p> \ No newline at end of file diff --git a/src/docs/images/.gitkeep b/src/docs/images/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/docs/images/sw_arch.png b/src/docs/images/sw_arch.png deleted file mode 100644 index fc71de5092b758e06e3478a2a48546a70db26ed7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56206 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®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 diff --git a/src/docs/images/tecnalia.jpg b/src/docs/images/tecnalia.jpg deleted file mode 100644 index b633abc6b439e736f79c4bc97a78186a7737ddcc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28558 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	q 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 diff --git a/src/filter/.gitkeep b/src/filter/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/filter/CMakeLists.txt b/src/filter/CMakeLists.txt deleted file mode 100644 index 927f413..0000000 --- a/src/filter/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) -project(filter) -message("cmake ${PROJECT_NAME}") -if (DEBUG_CMAKE) - set(CMAKE_VERBOSE_MAKEFILE yes) -endif (DEBUG_CMAKE) - -include_directories(include ${Boost_INCLUDE_DIR}) - -set(filter_files_local - ${CMAKE_CURRENT_SOURCE_DIR}/src/butterworth.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/filter/butterworth.hpp) - -set(filter_files - ${filter_files_local} - PARENT_SCOPE) - -add_library(filter - ${filter_files_local}) - -target_link_libraries(filter logger ) -set_target_properties(filter PROPERTIES FOLDER filter) - -# Define headers for this library. PUBLIC headers are used for -# compiling the library, and will be added to consumers' build -# paths. -target_include_directories(filter PUBLIC - $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> - $<INSTALL_INTERFACE:include> - PRIVATE src) - -# This makes the project importable from the build directory -export(TARGETS filter - logger - yaml_tools - yaml-cpp - FILE filter_Config.cmake) \ No newline at end of file diff --git a/src/filter/include/.gitkeep b/src/filter/include/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/filter/include/filter/.gitkeep b/src/filter/include/filter/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/filter/include/filter/butterworth.hpp b/src/filter/include/filter/butterworth.hpp deleted file mode 100644 index e3c66dc..0000000 --- a/src/filter/include/filter/butterworth.hpp +++ /dev/null @@ -1,42 +0,0 @@ -/** -* @file butterworth.hpp -* @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> -* @date 2020 -* -* Copyright 2020 Tecnalia Research & Innovation. -* Distributed under the GNU GPL v3. For full terms see https://www.gnu.org/licenses/gpl.txt -* -* @brief Implementation of a butterworth filter. -*/ -#include <boost/circular_buffer.hpp> -#include <boost/scoped_ptr.hpp> -#include <vector> - -class Butterworth -{ -public: - Butterworth(); - int configure (int filter_order, double low_cutoff, double up_cutoff); - bool update(const double & data_in, double & data_out); - -private: - double * ComputeLP(); - double * ComputeHP(); - double * TrinomialMultiply(double *b, double *c); - std::vector<double> ComputeNumCoeffs(); - std::vector<double> ComputeDenCoeffs(); - - int filter_order_; - double low_cutoff_; - double up_cutoff_; - - boost::scoped_ptr<boost::circular_buffer<double>> input_buffer_; //The input sample history. - boost::scoped_ptr<boost::circular_buffer<double>> output_buffer_; //The output sample history. - - std::vector<double> den_coeffs_; //Transfer functon coefficients (output). - std::vector<double> num_coeffs_; //Transfer functon coefficients (input). - - double temp_; //used for storage and preallocation - - bool configured_; //used for storage and preallocation -}; \ No newline at end of file diff --git a/src/filter/src/.gitkeep b/src/filter/src/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/filter/src/butterworth.cpp b/src/filter/src/butterworth.cpp deleted file mode 100644 index ca9eb37..0000000 --- a/src/filter/src/butterworth.cpp +++ /dev/null @@ -1,285 +0,0 @@ -/** -* @file butterworth.cpp -* @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> -* @date 2020 -* -* Copyright 2020 Tecnalia Research & Innovation. -* Distributed under the GNU GPL v3. For full terms see https://www.gnu.org/licenses/gpl.txt -* -* @brief Implementation of a butterworth filter. -*/ - -#include <filter/butterworth.hpp> - -#include <iostream> -#include <stdio.h> -#include <vector> -#include <math.h> - -#include <complex> - -using namespace std; - -#define N 10 //The number of images which construct a time series for each pixel -#define PI 3.1415926535897932384626433832795 - - -Butterworth::Butterworth() -{ - configured_ = false; - temp_ = 0.0; -} - - -int Butterworth::configure(int filter_order, double low_cutoff, double up_cutoff) -{ - filter_order_ = filter_order; - low_cutoff_ = low_cutoff; - up_cutoff_ = up_cutoff; - - den_coeffs_ = ComputeDenCoeffs(); - num_coeffs_ = ComputeNumCoeffs(); - - temp_ = 0.0; - - input_buffer_.reset(new boost::circular_buffer<double>(num_coeffs_.size()-1, temp_)); - output_buffer_.reset(new boost::circular_buffer<double>(den_coeffs_.size()-1, temp_)); - - configured_ = true; - - /*for (int i=0; i < den_coeffs_.size(); i++) - { - std::cout << "Den coff a" << i << ": " << den_coeffs_[i] << std::endl; - } - - for (int i=0; i < num_coeffs_.size(); i++) - { - std::cout << "Num coff b" << i << ": " << num_coeffs_[i] << std::endl; - }*/ - - return 0; -} - - -double * Butterworth::ComputeLP() -{ - double *NumCoeffs; - int m; - int i; - - NumCoeffs = (double *)calloc(filter_order_+1, sizeof(double)); - if(NumCoeffs == NULL) return(NULL); - - NumCoeffs[0] = 1; - NumCoeffs[1] = filter_order_; - m = filter_order_/2; - for(i=2; i <= m; ++i) - { - NumCoeffs[i] =(double) (filter_order_-i+1)*NumCoeffs[i-1]/i; - NumCoeffs[filter_order_-i]= NumCoeffs[i]; - } - NumCoeffs[filter_order_-1] = filter_order_; - NumCoeffs[filter_order_] = 1; - - return NumCoeffs; -} - - -double * Butterworth::ComputeHP() -{ - double *NumCoeffs; - int i; - - NumCoeffs = ComputeLP(); - if(NumCoeffs == NULL) return(NULL); - - for(i = 0; i <= filter_order_; ++i) - if(i % 2) NumCoeffs[i] = -NumCoeffs[i]; - - return NumCoeffs; -} - - -double * Butterworth::TrinomialMultiply(double *b, double *c) -{ - int i, j; - double *RetVal; - - RetVal = (double *)calloc(4 * filter_order_, sizeof(double)); - if(RetVal == NULL) return(NULL); - - RetVal[2] = c[0]; - RetVal[3] = c[1]; - RetVal[0] = b[0]; - RetVal[1] = b[1]; - - for(i = 1; i < filter_order_; ++i) - { - RetVal[2*(2*i+1)] += c[2*i] * RetVal[2*(2*i-1)] - c[2*i+1] * RetVal[2*(2*i-1)+1]; - RetVal[2*(2*i+1)+1] += c[2*i] * RetVal[2*(2*i-1)+1] + c[2*i+1] * RetVal[2*(2*i-1)]; - - for(j = 2*i; j > 1; --j) - { - RetVal[2*j] += b[2*i] * RetVal[2*(j-1)] - b[2*i+1] * RetVal[2*(j-1)+1] + - c[2*i] * RetVal[2*(j-2)] - c[2*i+1] * RetVal[2*(j-2)+1]; - RetVal[2*j+1] += b[2*i] * RetVal[2*(j-1)+1] + b[2*i+1] * RetVal[2*(j-1)] + - c[2*i] * RetVal[2*(j-2)+1] + c[2*i+1] * RetVal[2*(j-2)]; - } - - RetVal[2] += b[2*i] * RetVal[0] - b[2*i+1] * RetVal[1] + c[2*i]; - RetVal[3] += b[2*i] * RetVal[1] + b[2*i+1] * RetVal[0] + c[2*i+1]; - RetVal[0] += b[2*i]; - RetVal[1] += b[2*i+1]; - } - return RetVal; -} - - -std::vector<double> Butterworth::ComputeNumCoeffs() -{ - std::vector<double> num_coeffs; - double *TCoeffs; - double *NumCoeffs; - std::complex<double> *NormalizedKernel; - double Numbers[11]={0,1,2,3,4,5,6,7,8,9,10}; - int i; - - NumCoeffs = (double *)calloc(2*filter_order_+1, sizeof(double)); - if(NumCoeffs == NULL) return(num_coeffs); - - NormalizedKernel = (std::complex<double> *)calloc(2*filter_order_+1, sizeof(std::complex<double>)); - if(NormalizedKernel == NULL) return(num_coeffs); - - TCoeffs = ComputeHP(); - if(TCoeffs == NULL) return(num_coeffs); - - for(i = 0; i < filter_order_; ++i) - { - NumCoeffs[2*i] = TCoeffs[i]; - NumCoeffs[2*i+1] = 0.0; - } - NumCoeffs[2*filter_order_] = TCoeffs[filter_order_]; - double cp[2]; - //double Bw; - double Wn; - cp[0] = 2*2.0*tan(PI * low_cutoff_/ 2.0); - cp[1] = 2*2.0*tan(PI * up_cutoff_/2.0); - - //Bw = cp[1] - cp[0]; - //center frequency - Wn = sqrt(cp[0]*cp[1]); - Wn = 2*atan2(Wn,4); - //double kern; - const std::complex<double> result = std::complex<double>(-1,0); - - for(int k = 0; k<2*filter_order_+1; k++) - { - NormalizedKernel[k] = std::exp(-sqrt(result)*Wn*Numbers[k]); - } - double b=0; - double den=0; - for(int d = 0; d<2*filter_order_+1; d++) - { - b+=real(NormalizedKernel[d]*NumCoeffs[d]); - den+=real(NormalizedKernel[d]*den_coeffs_[d]); - } - for(int c = 0; c<2*filter_order_+1; c++) - { - NumCoeffs[c]=(NumCoeffs[c]*den)/b; - } - - for(int c = 0; c<2*filter_order_+1; c++) - { - num_coeffs.push_back(NumCoeffs[c]); - } - - free(TCoeffs); - - return num_coeffs; -} - - -std::vector<double> Butterworth::ComputeDenCoeffs() -{ - int k; // loop variables - double theta; // PI * (up_cutoff_ - low_cutoff_)/2.0 - double cp; // cosine of phi - double st; // sine of theta - double ct; // cosine of theta - double s2t; // sine of 2*theta - double c2t; // cosine 0f 2*theta - double *RCoeffs; // z^-2 coefficients - double *TCoeffs; // z^-1 coefficients - double *DenomCoeffs; // dk coefficients - double PoleAngle; // pole angle - double SinPoleAngle; // sine of pole angle - double CosPoleAngle; // cosine of pole angle - double a; // workspace variables - - cp = cos(PI * (up_cutoff_ + low_cutoff_)/2.0); - theta = PI * (up_cutoff_ - low_cutoff_)/2.0; - st = sin(theta); - ct = cos(theta); - s2t = 2.0*st*ct; // sine of 2*theta - c2t = 2.0*ct*ct - 1.0; // cosine of 2*theta - - RCoeffs = (double *)calloc(2 * filter_order_, sizeof(double)); - TCoeffs = (double *)calloc(2 * filter_order_, sizeof(double)); - - for(k = 0; k < filter_order_; ++k) - { - PoleAngle = PI * (double)(2*k+1)/(double)(2*filter_order_); - SinPoleAngle = sin(PoleAngle); - CosPoleAngle = cos(PoleAngle); - a = 1.0 + s2t*SinPoleAngle; - RCoeffs[2*k] = c2t/a; - RCoeffs[2*k+1] = s2t*CosPoleAngle/a; - TCoeffs[2*k] = -2.0*cp*(ct+st*SinPoleAngle)/a; - TCoeffs[2*k+1] = -2.0*cp*st*CosPoleAngle/a; - } - - DenomCoeffs = Butterworth::TrinomialMultiply(TCoeffs, RCoeffs); - free(TCoeffs); - free(RCoeffs); - - DenomCoeffs[1] = DenomCoeffs[0]; - DenomCoeffs[0] = 1.0; - for(k = 3; k <= 2*filter_order_; ++k) - DenomCoeffs[k] = DenomCoeffs[2*k-2]; - - std::vector<double> den_coeffs; - for(k = 0; k < 2*filter_order_ + 1; k++) - { - den_coeffs.push_back(DenomCoeffs[k]); - } - - return den_coeffs; -} - - -bool Butterworth::update(const double & data_in, double & data_out) -{ - if (!configured_) - { - return false; - } - - // Copy data to prevent mutation if in and out are the same ptr - temp_ = data_in; - - data_out=num_coeffs_[0] * temp_; - - for (uint32_t row = 1; row <= input_buffer_->size(); row++) - { - data_out += num_coeffs_[row] * (*input_buffer_)[row-1]; - } - for (uint32_t row = 1; row <= output_buffer_->size(); row++) - { - data_out -= den_coeffs_[row] * (*output_buffer_)[row-1]; - } - - input_buffer_->push_front(temp_); - output_buffer_->push_front(data_out); - - return true; -} \ No newline at end of file diff --git a/src/logger/.gitkeep b/src/logger/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/logger/CMakeLists.txt b/src/logger/CMakeLists.txt deleted file mode 100644 index 5535900..0000000 --- a/src/logger/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) -project(logger) -message("cmake ${PROJECT_NAME}") -if (DEBUG_CMAKE) - set(CMAKE_VERBOSE_MAKEFILE yes) -endif (DEBUG_CMAKE) - -set(logger_files - ${CMAKE_CURRENT_SOURCE_DIR}/src/logger.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/logger/logger.hpp - ) - -set(logger_files ${logger_files} PARENT_SCOPE) - -include_directories( - include - ${Boost_INCLUDE_DIR} - ${YAML_INCLUDE_DIR} - ) - -add_library(logger ${logger_files}) -add_dependencies(logger yaml-cpp) -target_link_libraries(logger yaml_tools yaml-cpp ${Boost_LIBRARIES}) -set_target_properties(logger PROPERTIES FOLDER logger) - -# Define headers for this library. PUBLIC headers are used for -# compiling the library, and will be added to consumers' build -# paths. -target_include_directories(logger PUBLIC - $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> - $<INSTALL_INTERFACE:include> - PRIVATE src) - -# This makes the project importable from the build directory -export(TARGETS logger - yaml_tools - yaml-cpp - FILE loggerConfig.cmake) diff --git a/src/logger/include/.gitkeep b/src/logger/include/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/logger/include/logger/.gitkeep b/src/logger/include/logger/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/logger/include/logger/logger.hpp b/src/logger/include/logger/logger.hpp deleted file mode 100644 index 800d148..0000000 --- a/src/logger/include/logger/logger.hpp +++ /dev/null @@ -1,50 +0,0 @@ -/** -* @logger.hpp -* @author Alfonso Dominguez (alfonso.dominguez@tecnalia.com) -* Copyright 2017 Tecnalia Research and Innovation -* -* @brief Logger class (inspired in https://github.com/gklingler/simpleLogger) -**/ - -#ifndef __LOGGER_HPP__ -#define __LOGGER_HPP__ - -#include <boost/log/trivial.hpp> -#include <boost/log/sources/global_logger_storage.hpp> -#include <string> - -// register a global logger -BOOST_LOG_GLOBAL_LOGGER(logger, boost::log::sources::severity_logger_mt<boost::log::trivial::severity_level>) - -// just a helper macro used by the macros below - don't use it in your code -#define LOG(severity) BOOST_LOG_SEV(logger::get(),boost::log::trivial::severity) - -// ===== log macros ===== -#define LOG_TRACE LOG(trace) -#define LOG_DEBUG LOG(debug) -#define LOG_INFO LOG(info) -#define LOG_WARNING LOG(warning) -#define LOG_ERROR LOG(error) -#define LOG_FATAL LOG(fatal) - -namespace equimetrix -{ - namespace Common - { - class Logger - { - public: - //! default constructor - Logger(); - ~Logger(); - /** - * @brief init the logger from file - * @param filename, configuration/launch file name - */ - void init(std::string filename); - - static std::string log_filename_; - }; - } // namespace Common -} // namespace equimetrix -#endif // LOGGER_HPP__ \ No newline at end of file diff --git a/src/logger/src/.gitkeep b/src/logger/src/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/logger/src/logger.cpp b/src/logger/src/logger.cpp deleted file mode 100644 index 21138f9..0000000 --- a/src/logger/src/logger.cpp +++ /dev/null @@ -1,274 +0,0 @@ -/** -* @logger.cpp -* @author Alfonso Dominguez (alfonso.dominguez@tecnalia.com) -* Copyright 2017 Tecnalia Research and Innovation -* -* @brief Logger class (inspired in https://github.com/gklingler/simpleLogger) -**/ - -#include <logger/logger.hpp> -#include <yaml_tools/yaml_tools.hpp> - -#if !defined(BOOST_USE_WINAPI_VERSION) -#if defined(_WIN32_WINNT) -#define BOOST_USE_WINAPI_VERSION _WIN32_WINNT -#elif defined(WINVER) -#define BOOST_USE_WINAPI_VERSION WINVER -#else -// By default use Windows Vista API on compilers that support it and XP on the others -#if (defined(_MSC_VER) && _MSC_VER < 1500) || defined(BOOST_WINAPI_IS_MINGW) -#define BOOST_USE_WINAPI_VERSION BOOST_WINAPI_VERSION_WINXP -#else -#define BOOST_USE_WINAPI_VERSION BOOST_WINAPI_VERSION_WIN6 -#endif -#endif -#endif - -#include <boost/log/core/core.hpp> -#include <boost/log/expressions/formatters/date_time.hpp> -#include <boost/log/expressions.hpp> -#include <boost/log/sinks/sync_frontend.hpp> -#include <boost/log/sinks/text_ostream_backend.hpp> -#include <boost/log/sources/severity_logger.hpp> -#include <boost/log/support/date_time.hpp> -#include <boost/log/trivial.hpp> -#include <boost/core/null_deleter.hpp> -#include <boost/log/utility/setup/common_attributes.hpp> -#include <boost/log/utility/setup/file.hpp> -#include <boost/make_shared.hpp> -#include <boost/shared_ptr.hpp> -#include <boost/filesystem.hpp> - -#include <fstream> -#include <ostream> - -#include <cstring> - -#include <stdio.h> /* defines FILENAME_MAX */ -#if defined(_WIN32) || defined(WIN32) -#include <direct.h> -#define GetCurrentDir _getcwd -#else -#include <unistd.h> -#define GetCurrentDir getcwd -#endif - -std::string equimetrix::Common::Logger::log_filename_ = ""; - -namespace logging = boost::log; -namespace src = boost::log::sources; -namespace expr = boost::log::expressions; -namespace sinks = boost::log::sinks; -namespace attrs = boost::log::attributes; - -BOOST_LOG_ATTRIBUTE_KEYWORD(line_id, "LineID", unsigned int) -BOOST_LOG_ATTRIBUTE_KEYWORD(timestamp, "TimeStamp", boost::posix_time::ptime) -BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", logging::trivial::severity_level) - -BOOST_LOG_GLOBAL_LOGGER_INIT(logger, src::severity_logger_mt) { - boost::filesystem::path::imbue(std::locale("C")); - - char cCurrentPath[FILENAME_MAX]; - GetCurrentDir(cCurrentPath, sizeof(cCurrentPath)); - cCurrentPath[sizeof(cCurrentPath) - 1] = '\0'; - std::cout << "[Logger]The current working directory is "<< cCurrentPath << std::endl; - - std::cout << "[Logger] Default logger initialization" << std::endl; - src::severity_logger_mt<boost::log::trivial::severity_level> logger; - - time_t t = time(0); - struct tm * now = localtime(&t); - - char buffer[80]; - strftime(buffer, sizeof(buffer), "%Y%m%d_%H%M%S", now); - - std::wstring ws{ boost::filesystem::path::preferred_separator }; - std::string separator(ws.begin(), ws.end()); - std::ostringstream oss; - oss << std::string(cCurrentPath) << separator << "logs"; - std::string log_dir = oss.str(); - oss << separator << "log_"<< std::string(buffer)<< ".log"; - equimetrix::Common::Logger::log_filename_ = oss.str(); - - if (!(boost::filesystem::is_directory(log_dir) && boost::filesystem::exists(log_dir))) - { - std::cout << "[Logger] Logs dir does not exist. Create it!" << std::endl; - boost::filesystem::create_directory(log_dir); - } - else - { - std::cout << "[Logger] Logs dir already exists" << std::endl; - } - - std::cout << "[Logger] Default log to console and file" << std::endl; - std::cout << "[Logger] Default log_filename: " << equimetrix::Common::Logger::log_filename_ << std::endl; - std::cout << "[Logger] Default severity: " << 0 << std::endl; - - // add attributes - // lines are sequentially numbered - logger.add_attribute("LineID", attrs::counter<unsigned int>(1)); - // each log line gets a timestamp - logger.add_attribute("TimeStamp", attrs::local_clock()); - - // add a text sink - typedef sinks::synchronous_sink<sinks::text_ostream_backend> text_sink; - boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>(); - - // add a logfile stream to our sink - sink->locked_backend()->add_stream(boost::make_shared<std::ofstream>(equimetrix::Common::Logger::log_filename_)); - // add "console" output stream to our sink - sink->locked_backend()->add_stream(boost::shared_ptr<std::ostream>(&std::clog, boost::null_deleter())); - - // specify the format of the log message - logging::formatter formatter = expr::stream - << std::setw(7) << std::setfill('0') << line_id << std::setfill(' ') << " | " - << expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f") << " " - << "[" << logging::trivial::severity << "]" - << " - " << expr::smessage; - sink->set_formatter(formatter); - - // only messages with severity >= SEVERITY_THRESHOLD are written - sink->set_filter(severity >= 0); - - // "register" our sink - logging::core::get()->add_sink(sink); - - return logger; -} - -namespace equimetrix -{ -namespace Common -{ - Logger::Logger() - { - } - - Logger::~Logger() - { - std::cout << "Logger destructed" << std::endl; - } - - - void Logger::init(std::string filename) - { - src::severity_logger_mt<boost::log::trivial::severity_level> &logger = logger::get(); - - int sink_mode = 2; - int severity_threshold = 0; - bool init_from_file_done = false; - - YAML::Node cfg; - - try - { - cfg = YAML::LoadFile(filename); - std::cout << "[Logger] Reading the config...done" << std::endl; - - std::vector <std::vector < std::string> > lconfig; - lconfig = {{"log", "sink_mode"}, - {"log", "severity_threshold"}}; - - if (!areTagsDefined(lconfig, cfg)) - { - std::cerr << "[Logger] Configuration file uncomplete " << std::endl; - } - else - { - sink_mode = cfg["log"]["sink_mode"].as<int>(); - severity_threshold = cfg["log"]["severity_threshold"].as<int>(); - init_from_file_done = true; - } - } - catch (YAML::BadFile &err) - { - std::cerr << "[Logger]Prb while opening file " << filename - << " : " << err.what() << std::endl; - } - catch (YAML::ParserException &err) - { - std::cerr << "Parsing error in file " << filename - << " : " << err.what() << std::endl; - } - catch (...) - { - std::cerr << "[Logger] Unexpected error while parsing the config file" << std::endl; - } - - if (init_from_file_done) - { - // first remove all the configured properties - std::cout << "[Logger] Custom initialization of the logger from a config file" << std::endl; - std::cout << "[Logger] First, remove default logger configuration (log file, sinks, severity)" << std::endl; - - logging::core::get()->remove_all_sinks(); - - if (std::remove(log_filename_.c_str()) != 0) // log file - { - std::cerr << "[Logger] Failed to remove default log file" << std::endl; - std::cerr << std::strerror(errno) << std::endl; - } - } - else - { - std::cout << "[Logger] Problems with config file, continue with the default configuration" << std::endl; - return; - } - - std::cout << "[Logger] sink_mode: " << sink_mode << std::endl; - std::cout << "[Logger] severity_threshold: " << severity_threshold << std::endl; - - // add a text sink - typedef sinks::synchronous_sink<sinks::text_ostream_backend> text_sink; - boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>(); - - time_t t = time(0); - struct tm * now = localtime(&t); - - char buffer[80]; - strftime(buffer, sizeof(buffer), "%Y%m%d_%H%M%S", now); - - if (sink_mode == 1 || sink_mode == 2) - { - char cCurrentPath[FILENAME_MAX]; - GetCurrentDir(cCurrentPath, sizeof(cCurrentPath)); - cCurrentPath[sizeof(cCurrentPath) - 1] = '\0'; - - // boost::filesystem::path::preferred_separator; - std::wstring ws{ boost::filesystem::path::preferred_separator }; - std::string separator(ws.begin(), ws.end()); - - equimetrix::Common::Logger::log_filename_ = ""; - std::ostringstream oss; - oss << std::string(cCurrentPath) << separator << "logs" << separator << "log_" << std::string(buffer) << ".log"; - equimetrix::Common::Logger::log_filename_ = oss.str(); - - std::cout << "[Logger] log_filename: " << log_filename_ << std::endl; - - // add a logfile stream to our sink - sink->locked_backend()->add_stream(boost::make_shared<std::ofstream>(log_filename_)); - // logging::add_file_log(log_filename_); - } - - if (sink_mode == 0 || sink_mode == 2) - { - // add "console" output stream to our sink - sink->locked_backend()->add_stream(boost::shared_ptr<std::ostream>(&std::clog, boost::null_deleter())); - } - - // specify the format of the log message - logging::formatter formatter = expr::stream - << std::setw(7) << std::setfill('0') << line_id << std::setfill(' ') << " | " - << expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f") << " " - << "[" << logging::trivial::severity << "]" - << " - " << expr::smessage; - sink->set_formatter(formatter); - - // only messages with severity >= SEVERITY_THRESHOLD are written - sink->set_filter(severity >= severity_threshold); - - // "register" our sink - logging::core::get()->add_sink(sink); - } -} // namespace Common -} // namespace equimetrix diff --git a/src/pandocology.cmake b/src/pandocology.cmake deleted file mode 100644 index 76fc542..0000000 --- a/src/pandocology.cmake +++ /dev/null @@ -1,482 +0,0 @@ -################################################################################ -## -## Provide Pandoc compilation support for the CMake build system -## -## Version: 0.0.1 -## Author: Jeet Sukumatan (jeetsukumaran@gmail.com) -## -## Copyright 2015 Jeet Sukumaran. -## -## This software is released under the BSD 3-Clause License. -## -## Redistribution and use in source and binary forms, with or without -## modification, are permitted provided that the following conditions are -## met: -## -## 1. Redistributions of source code must retain the above copyright notice, -## this list of conditions and the following disclaimer. -## -## 2. Redistributions in binary form must reproduce the above copyright -## notice, this list of conditions and the following disclaimer in the -## documentation and/or other materials provided with the distribution. -## -## 3. Neither the name of the copyright holder nor the names of its -## contributors may be used to endorse or promote products derived from this -## software without specific prior written permission. -## -## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS -## IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -## THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -## PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR -## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -## EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -## PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -## PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -## LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -## NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -## SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -## -################################################################################ - -include(CMakeParseArguments) - -if(NOT EXISTS ${PANDOC_EXECUTABLE}) - # find_program(PANDOC_EXECUTABLE NAMES pandoc) - find_program(PANDOC_EXECUTABLE pandoc) - mark_as_advanced(PANDOC_EXECUTABLE) - if(NOT EXISTS ${PANDOC_EXECUTABLE}) - message(FATAL_ERROR "Pandoc not found. Install Pandoc (http://johnmacfarlane.net/pandoc/) or set cache variable PANDOC_EXECUTABLE.") - return() - endif() -endif() -if(NOT EXISTS ${PP_EXECUTABLE}) - find_program(PP_EXECUTABLE pp) - mark_as_advanced(PP_EXECUTABLE) - if(NOT EXISTS ${PP_EXECUTABLE}) - message("PP not found. If this is wrong, please set cache variable PP_EXECUTABLE.") - endif() -endif() -if(NOT DEFINED ${PREPROCESS_TEMP_DIRECTORY}) - set(PREPROCESS_TEMP_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_preprocessed") -endif() - -############################################################################### -# Based on code from UseLATEX -# Author: Kenneth Moreland <kmorel@sandia.gov> -# Copyright 2004 Sandia Corporation. -# Under the terms of Contract DE-AC04-94AL85000, there is a non-exclusive -# license for use of this work by or on behalf of the -# U.S. Government. Redistribution and use in source and binary forms, with -# or without modification, are permitted provided that this Notice and any -# statement of authorship are reproduced on all copies. - -# Adds command to copy file from the source directory to the destination -# directory: used to move source files from source directory into build -# directory before main build -function(pandocology_add_input_file source_path dest_dir dest_filelist_var native_dest_filelist_var) - set(dest_filelist) - set(native_dest_filelist) - file(GLOB globbed_source_paths "${source_path}") - foreach(globbed_source_path ${globbed_source_paths}) - get_filename_component(filename ${globbed_source_path} NAME) - get_filename_component(absolute_dest_path ${dest_dir}/${filename} ABSOLUTE) - file(RELATIVE_PATH relative_dest_path ${CMAKE_CURRENT_BINARY_DIR} ${absolute_dest_path}) - list(APPEND dest_filelist ${absolute_dest_path}) - file(TO_NATIVE_PATH ${absolute_dest_path} native_dest_path) - list(APPEND native_dest_filelist ${native_dest_path}) - file(TO_NATIVE_PATH ${globbed_source_path} native_globbed_source_path) - ADD_CUSTOM_COMMAND( - OUTPUT ${relative_dest_path} - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${native_globbed_source_path} ${native_dest_path} - DEPENDS ${globbed_source_path} - ) - set(${dest_filelist_var} ${${dest_filelist_var}} ${dest_filelist} PARENT_SCOPE) - set(${native_dest_filelist_var} ${${native_dest_filelist_var}} ${native_dest_filelist} PARENT_SCOPE) - endforeach() -endfunction() - -# A version of GET_FILENAME_COMPONENT that treats extensions after the last -# period rather than the first. -function(pandocology_get_file_stemname varname filename) - SET(result) - GET_FILENAME_COMPONENT(name ${filename} NAME) - STRING(REGEX REPLACE "\\.[^.]*\$" "" result "${name}") - SET(${varname} "${result}" PARENT_SCOPE) -endfunction() - -function(pandocology_get_file_extension varname filename) - SET(result) - GET_FILENAME_COMPONENT(name ${filename} NAME) - STRING(REGEX MATCH "\\.[^.]*\$" result "${name}") - SET(${varname} "${result}" PARENT_SCOPE) -endfunction() -############################################################################### - -function(pandocology_add_input_dir source_dir dest_parent_dir dir_dest_filelist_var native_dir_dest_filelist_var) - set(dir_dest_filelist) - set(native_dir_dest_filelist) - file(TO_CMAKE_PATH ${source_dir} source_dir) - get_filename_component(dir_name ${source_dir} NAME) - get_filename_component(absolute_dest_dir ${dest_parent_dir}/${dir_name} ABSOLUTE) - file(RELATIVE_PATH relative_dest_dir ${CMAKE_CURRENT_BINARY_DIR} ${absolute_dest_dir}) - file(TO_NATIVE_PATH ${absolute_dest_dir} native_absolute_dest_dir) - add_custom_command( - OUTPUT ${relative_dest_dir} - COMMAND ${CMAKE_COMMAND} -E make_directory ${native_absolute_dest_dir} - DEPENDS ${source_dir} - ) - file(GLOB_RECURSE source_files "${source_dir}/*") - foreach(source_file ${source_files}) - file(RELATIVE_PATH relative_source_file ${source_dir} ${source_file}) - # get_filename_component(absolute_source_path ${CMAKE_CURRENT_SOURCE_DIR}/${source_file} ABSOLUTE) - get_filename_component(relative_source_dir ${relative_source_file} DIRECTORY) - pandocology_add_input_file(${source_file} ${absolute_dest_dir}/${relative_source_dir} dir_dest_filelist native_dir_dest_filelist) - endforeach() - set(${dir_dest_filelist_var} ${${dir_dest_filelist_var}} ${dir_dest_filelist} PARENT_SCOPE) - set(${native_dir_dest_filelist_var} ${${native_dir_dest_filelist_var}} ${native_dir_dest_filelist} PARENT_SCOPE) -endfunction() - -function(add_to_make_clean filepath) - file(TO_NATIVE_PATH ${CMAKE_CURRENT_BINARY_DIR}/${filepath} native_filepath) - get_directory_property(make_clean_files ADDITIONAL_MAKE_CLEAN_FILES) - set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${make_clean_files};${native_filepath}") -endfunction() - -function(disable_insource_build) - IF ( CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR AND NOT MSVC_IDE ) - MESSAGE(FATAL_ERROR "The build directory must be different from the main project source " -"directory. Please create a directory such as '${CMAKE_SOURCE_DIR}/build', " -"and run CMake from there, passing the path to this source directory as " -"the path argument. E.g.: - $ cd ${CMAKE_SOURCE_DIR} - $ mkdir build - $ cd build - $ cmake .. && make && sudo make install -This process created the file `CMakeCache.txt' and the directory `CMakeFiles'. -Please delete them: - $ rm -r CMakeFiles/ CmakeCache.txt -") - ENDIF() -endfunction() - -# This builds a document -# -# Usage: -# -# -# INCLUDE(pandocology) -# -# add_document( -# TARGET figures -# OUTPUT_FILE figures.tex -# SOURCES figures.md -# RESOURCE_DIRS figs -# PANDOC_DIRECTIVES -t latex -# NO_EXPORT_PRODUCT -# ) -# -# add_document( -# TARGET opus -# OUTPUT_FILE opus.pdf -# SOURCES opus.md -# RESOURCE_FILES opus.bib systbiol.template.latex systematic-biology.csl -# RESOURCE_DIRS figs -# PANDOC_DIRECTIVES -t latex -# --smart -# --template systbiol.template.latex -# --filter pandoc-citeproc -# --csl systematic-biology.csl -# --bibliography opus.bib -# --include-after-body=figures.tex -# DEPENDS figures -# EXPORT_ARCHIVE -# ) -# -# -# Deprecated command signature for backwards compatibility: -# -# add_document( -# figures.tex -# SOURCES figures.md -# RESOURCE_DIRS figs -# PANDOC_DIRECTIVES -t latex -# NO_EXPORT_PRODUCT -# ) -# -# This command signature will cause an error due to a circular dependency during -# build when the sources are at the top level directory of the project. A -# warning is issued. The solution is to use the alternative commande signature. -# -function(add_document) - set(options EXPORT_ARCHIVE NO_EXPORT_PRODUCT EXPORT_PDF DIRECT_TEX_TO_PDF VERBOSE PREPROCESS) - set(oneValueArgs TARGET OUTPUT_FILE PRODUCT_DIRECTORY) - set(multiValueArgs SOURCES RESOURCE_FILES RESOURCE_DIRS PANDOC_DIRECTIVES PP_DIRECTIVES DEPENDS) - cmake_parse_arguments(ADD_DOCUMENT "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) - - # this is because `make clean` will dangerously clean up source files - disable_insource_build() - - if ("${ADD_DOCUMENT_TARGET}" STREQUAL "" AND "${ADD_DOCUMENT_OUTPUT_FILE}" STREQUAL "") - set(bw_comp_mode_args TRUE) - endif() - - if(bw_comp_mode_args) - list(LENGTH ADD_DOCUMENT_UNPARSED_ARGUMENTS unparsed_args_num) - if (${unparsed_args_num} GREATER 1) - message(FATAL_ERROR "add_document() requires at most one target name") - endif() - - if (${CMAKE_CURRENT_SOURCE_DIR} STREQUAL ${CMAKE_SOURCE_DIR}) - message(WARNING "This add_document() signature does not support sources on the top level directory. \ - It will cause a circular dependency error during build.") - endif() - - list(GET ADD_DOCUMENT_UNPARSED_ARGUMENTS 0 ADD_DOCUMENT_TARGET) - set(ADD_DOCUMENT_OUTPUT_FILE ${ADD_DOCUMENT_TARGET}) - endif() - - if ("${ADD_DOCUMENT_TARGET}" STREQUAL "") - MESSAGE(FATAL_ERROR "add_document() requires a target name by using the TARGET option") - endif() - - if ("${ADD_DOCUMENT_OUTPUT_FILE}" STREQUAL "") - MESSAGE(FATAL_ERROR "add_document() requires an output file name by using the OUTPUT_FILE option") - endif() - - if (NOT bw_comp_mode_args) - if ("${ADD_DOCUMENT_TARGET}" STREQUAL "${ADD_DOCUMENT_OUTPUT_FILE}") - message(FATAL_ERROR "Target '${ADD_DOCUMENT_TARGET}': Must different from OUTPUT_FILE") - endif() - endif() - - set(output_file ${ADD_DOCUMENT_OUTPUT_FILE}) - file(TO_NATIVE_PATH ${output_file} native_output_file) - - # get the stem of the target name - pandocology_get_file_stemname(output_file_stemname ${output_file}) - pandocology_get_file_extension(output_file_extension ${output_file}) - - if (${ADD_DOCUMENT_EXPORT_PDF}) - if (NOT "${output_file_extension}" STREQUAL ".tex" AND NOT "${output_file_extension}" STREQUAL ".latex") - MESSAGE(FATAL_ERROR "Target '${ADD_DOCUMENT_TARGET}': Cannot use 'EXPORT_PDF' for target of type '${output_file_extension}': target type must be '.tex' or '.latex'") - endif() - endif() - - if (${ADD_DOCUMENT_DIRECT_TEX_TO_PDF}) - list(LENGTH ${ADD_DOCUMENT_SOURCES} SOURCE_LEN) - if (SOURCE_LEN GREATER 1) - MESSAGE(FATAL_ERROR "Target '${ADD_DOCUMENT_TARGET}': Only one source can be specified when using the 'DIRECT_TEX_TO_PDF' option") - endif() - - pandocology_get_file_stemname(source_stemname ${ADD_DOCUMENT_SOURCES}) - pandocology_get_file_extension(source_extension ${ADD_DOCUMENT_SOURCES}) - - if (NOT "${source_extension}" STREQUAL ".tex" AND NOT "${source_extension}" STREQUAL ".latex") - MESSAGE(FATAL_ERROR "Target '${ADD_DOCUMENT_TARGET}': Cannot use 'DIRECT_TEX_TO_PDF' for source of type '${source_extension}': source type must be '.tex' or '.latex'") - endif() - - set(check_target ${source_stemname}.pdf) - if (NOT ${check_target} STREQUAL ${output_file}) - MESSAGE(FATAL_ERROR "Target '${ADD_DOCUMENT_TARGET}': Must use target name of '${check_target}' if using 'DIRECT_TEX_TO_PDF'") - endif() - endif() - - ## set up output directory - if ("${ADD_DOCUMENT_PRODUCT_DIRECTORY}" STREQUAL "") - set(ADD_DOCUMENT_PRODUCT_DIRECTORY "product") - endif() - - get_filename_component(product_directory ${CMAKE_BINARY_DIR}/${ADD_DOCUMENT_PRODUCT_DIRECTORY} ABSOLUTE) - file(TO_NATIVE_PATH ${product_directory} native_product_directory) - - set(dest_output_file ${product_directory}/${output_file}) - file(TO_NATIVE_PATH ${dest_output_file} native_dest_output_file) - - ## get primary source - set(build_sources) - set(native_build_sources) - foreach(input_file ${ADD_DOCUMENT_SOURCES} ) - pandocology_add_input_file(${CMAKE_CURRENT_SOURCE_DIR}/${input_file} ${CMAKE_CURRENT_BINARY_DIR} build_sources native_build_sources) - endforeach() - - ## get resource files - # set(build_resources) - # set(native_build_resources) - # foreach(resource_file ${ADD_DOCUMENT_RESOURCE_FILES}) - # if(IS_ABSOLUTE ${resource_file}) - # pandocology_add_input_file(${resource_file} ${CMAKE_CURRENT_BINARY_DIR} build_resources native_build_resources) - # else() - # pandocology_add_input_file(${CMAKE_CURRENT_SOURCE_DIR}/${resource_file} ${CMAKE_CURRENT_BINARY_DIR} build_resources native_build_resources) - # endforeach() - - ## get resource dirs - set(exported_resources) - set(native_exported_resources) - foreach(resource_dir ${ADD_DOCUMENT_RESOURCE_DIRS}) - if(IS_ABSOLUTE ${resource_dir}) - pandocology_add_input_dir(${resource_dir} ${CMAKE_CURRENT_BINARY_DIR} build_resources native_build_resources) - else() - pandocology_add_input_dir(${CMAKE_CURRENT_SOURCE_DIR}/${resource_dir} ${CMAKE_CURRENT_BINARY_DIR} build_resources native_build_resources) - endif() - if (${ADD_DOCUMENT_EXPORT_ARCHIVE}) - pandocology_add_input_dir(${CMAKE_CURRENT_SOURCE_DIR}/${resource_dir} ${product_directory} exported_resources native_exported_resources) - endif() - endforeach() - - ## primary command - if (${ADD_DOCUMENT_DIRECT_TEX_TO_PDF}) - if (${ADD_DOCUMENT_VERBOSE}) - add_custom_command( - OUTPUT ${output_file} # note that this is in the build directory - DEPENDS ${build_sources} ${build_resources} ${ADD_DOCUMENT_DEPENDS} - COMMAND ${CMAKE_COMMAND} -E make_directory ${native_product_directory} - COMMAND latexmk -gg -halt-on-error -interaction=nonstopmode -file-line-error -pdf ${native_build_sources} - ) - else() - add_custom_command( - OUTPUT ${output_file} # note that this is in the build directory - DEPENDS ${build_sources} ${build_resources} ${ADD_DOCUMENT_DEPENDS} - COMMAND ${CMAKE_COMMAND} -E make_directory ${native_product_directory} - COMMAND latexmk -gg -halt-on-error -interaction=nonstopmode -file-line-error -pdf ${native_build_sources} 2>/dev/null >/dev/null || (grep --no-messages -A8 ".*:[0-9]*:.*" ${output_file_stemname}.log && false) - ) - endif() - add_to_make_clean(${output_file}) - else() - if(${ADD_DOCUMENT_PREPROCESS}) - if(EXISTS ${PP_EXECUTABLE}) - message("Preprocessing documents with PP.") - set(PREPROCESSING_WITH_PP) - file(MAKE_DIRECTORY ${PREPROCESS_TEMP_DIRECTORY}) - set(preprocessed_build_sources) - foreach(native_build_source ${native_build_sources}) - get_filename_component(native_source_name ${native_build_source} NAME) - set(preprocessed_build_source "${PREPROCESS_TEMP_DIRECTORY}/${native_source_name}") - set(pp_working_directory ${CMAKE_CURRENT_SOURCE_DIR}) - file(RELATIVE_PATH preprocessed_output_filepath ${pp_working_directory} ${preprocessed_build_source}) - add_custom_command( - OUTPUT ${preprocessed_build_source} - DEPENDS ${build_sources} ${build_resources} ${ADD_DOCUMENT_DEPENDS} - COMMAND ${PP_EXECUTABLE} ${ADD_DOCUMENT_PP_DIRECTIVES} ${native_build_source} > ${preprocessed_output_filepath} - VERBATIM - WORKING_DIRECTORY ${pp_working_directory} - ) - add_to_make_clean(${preprocessed_build_source}) - list(APPEND preprocessed_build_sources ${preprocessed_build_source}) - endforeach() - add_custom_command( - OUTPUT ${output_file} # note that this is in the build directory - DEPENDS ${preprocessed_build_sources} ${build_resources} ${ADD_DOCUMENT_DEPENDS} - COMMAND ${CMAKE_COMMAND} -E make_directory ${native_product_directory} - COMMAND ${PANDOC_EXECUTABLE} ${preprocessed_build_sources} ${ADD_DOCUMENT_PANDOC_DIRECTIVES} -o ${native_output_file} - ) - add_to_make_clean(${output_file}) - else() - message(FATAL_ERROR "PREPROCESS set but no preprocessor found!") - return() - endif() - else() - add_custom_command( - OUTPUT ${output_file} # note that this is in the build directory - DEPENDS ${build_sources} ${build_resources} ${ADD_DOCUMENT_DEPENDS} - COMMAND ${CMAKE_COMMAND} -E make_directory ${native_product_directory} - COMMAND ${PANDOC_EXECUTABLE} ${native_build_sources} ${ADD_DOCUMENT_PANDOC_DIRECTIVES} -o ${native_output_file} - ) - add_to_make_clean(${output_file}) - endif() - endif() - - add_custom_target ( - ${output_file}_product - DEPENDS ${output_file} - ) - - ## figure out what all is going to be produced by this build set, and set - ## those as dependencies of the primary target - set(primary_target_dependencies) - set(primary_target_dependencies ${primary_target_dependencies} ${CMAKE_CURRENT_BINARY_DIR}/${output_file}) - if (NOT ${ADD_DOCUMENT_NO_EXPORT_PRODUCT}) - set(primary_target_dependencies ${primary_target_dependencies} ${product_directory}/${output_file}) - endif() - if (${ADD_DOCUMENT_EXPORT_PDF}) - set(primary_target_dependencies ${primary_target_dependencies} ${CMAKE_CURRENT_BINARY_DIR}/${output_file_stemname}.pdf) - set(primary_target_dependencies ${primary_target_dependencies} ${product_directory}/${output_file_stemname}.pdf) - endif() - if (${ADD_DOCUMENT_EXPORT_ARCHIVE}) - set(primary_target_dependencies ${primary_target_dependencies} ${product_directory}/${output_file_stemname}.tbz) - endif() - - ## primary target - # # target cannot have same (absolute name) as dependencies: - # # http://www.cmake.org/pipermail/cmake/2011-March/043378.html - add_custom_target( - ${ADD_DOCUMENT_TARGET} - ALL - DEPENDS ${primary_target_dependencies} ${ADD_DOCUMENT_DEPENDS} - ) - - # run post-pdf - if (${ADD_DOCUMENT_EXPORT_PDF}) - add_custom_command( - OUTPUT ${native_product_directory}/${output_file_stemname}.pdf ${CMAKE_CURRENT_BINARY_DIR}/${output_file_stemname}.pdf - DEPENDS ${output_file} ${build_sources} ${build_resources} ${ADD_DOCUMENT_DEPENDS} - - # Does not work: custom template used to generate tex is ignored - # COMMAND ${PANDOC_EXECUTABLE} ${output_file} -f latex -o ${output_file_stemname}.pdf - - # (1) Apparently, both nonstopmode and batchmode produce an output file - # even if there was an error. This tricks latexmk into believing - # the file is actually up-to-date. - # So we use `-halt-on-error` or `-interaction=errorstopmode` - # instead. - # (2) `grep` returns a non-zero error code if the pattern is not - # found. So, in our scheme below to filter the output of - # `pdflatex`, it is precisely when there is NO error that - # grep returns a non-zero code, which fools CMake into thinking - # tex'ing failed. - # Hence the need for `| grep ...| cat` or `| grep || true`. - # But we can go better: - # latexmk .. || (grep .. && false) - # So we can have our cake and eat it too: here we want to - # re-raise the error after a successful grep if there was an - # error in `latexmk`. - # COMMAND latexmk -gg -halt-on-error -interaction=nonstopmode # -file-line-error -pdf ${output_file} 2>&1 | grep -A8 ".*:[0-9]*:.*" || true - COMMAND latexmk -gg -halt-on-error -interaction=nonstopmode -file-line-error -pdf ${native_output_file} 2>/dev/null >/dev/null || (grep --no-messages -A8 ".*:[0-9]*:.*" ${output_file_stemname}.log && false) - - COMMAND ${CMAKE_COMMAND} -E copy ${output_file_stemname}.pdf ${native_product_directory} - ) - add_to_make_clean(${output_file_stemname}.pdf) - add_to_make_clean(${product_directory}/${output_file_stemname}.pdf) - endif() - - ## copy products - if (NOT ${ADD_DOCUMENT_NO_EXPORT_PRODUCT}) - add_custom_command( - OUTPUT ${native_dest_output_file} - DEPENDS ${build_sources} ${build_resources} ${ADD_DOCUMENT_DEPENDS} ${output_file}_product - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${native_output_file} ${native_dest_output_file} - ) - add_to_make_clean(${product_directory}/${output_file}) - endif() - - ## copy resources - set(export_archive_file ${product_directory}/${output_file_stemname}.tbz) - file(TO_NATIVE_PATH ${export_archive_file} native_export_archive_file) - - if (${ADD_DOCUMENT_EXPORT_ARCHIVE}) - add_custom_command( - OUTPUT ${export_archive_file} - DEPENDS ${output_file} - COMMAND ${CMAKE_COMMAND} -E tar cjf ${native_export_archive_file} ${native_output_file} ${native_build_resources} ${ADD_DOCUMENT_DEPENDS} - ) - add_to_make_clean(${export_archive_file}) - endif() -endfunction(add_document) - -function(add_tex_document) - add_document(${ARGV} DIRECT_TEX_TO_PDF) -endfunction() - -# LEGACY SUPPORT -function(add_pandoc_document) - add_document(${ARGV}) -endfunction() \ No newline at end of file diff --git a/src/protobuf/.gitkeep b/src/protobuf/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/protobuf/class.proto b/src/protobuf/class.proto deleted file mode 100644 index cf6dc16..0000000 --- a/src/protobuf/class.proto +++ /dev/null @@ -1,22 +0,0 @@ -syntax = "proto2"; - -package class; - -message Command { - optional string name = 1; - optional int32 id = 2; - optional string email = 3; - - enum PhoneType { - MOBILE = 0; - HOME = 1; - WORK = 2; - } - - message PhoneNumber { - optional string number = 1; - optional PhoneType type = 2 [default = HOME]; - } - - repeated PhoneNumber phones = 4; -} \ No newline at end of file diff --git a/src/tests/.gitkeep b/src/tests/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt deleted file mode 100644 index bf95850..0000000 --- a/src/tests/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) -project (tests) - -add_subdirectory(test_cpp) -add_subdirectory(test_wrappers) \ No newline at end of file diff --git a/src/tests/test_cpp/.gitkeep b/src/tests/test_cpp/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/tests/test_cpp/CMakeLists.txt b/src/tests/test_cpp/CMakeLists.txt deleted file mode 100644 index c44b843..0000000 --- a/src/tests/test_cpp/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -cmake_minimum_required(VERSION 3.14) -project(test_cpp) - -include_directories(../../api/class/include/class) - -add_executable(test_cpp_acq test_cpp_acq.cpp) -target_link_libraries(test_cpp_acq class) - -add_executable(test_cpp_stim test_cpp_stim.cpp) -target_link_libraries(test_cpp_stim class) - -add_executable(test_cpp_callback test_cpp_callback.cpp) -target_link_libraries(test_cpp_callback class) - -install(TARGETS test_cpp_acq test_cpp_stim test_cpp_callback RUNTIME DESTINATION class_api/examples/bin/cpp) -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_cpp_acq.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_cpp_stim.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_cpp_callback.cpp DESTINATION class_api/examples/src/cpp) -if (WIN32) - install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_cpp_acq.bat ${CMAKE_CURRENT_SOURCE_DIR}/test_cpp_stim.bat ${CMAKE_CURRENT_SOURCE_DIR}/test_cpp_callback.bat DESTINATION class_api/examples/bin/cpp) -endif (WIN32) -install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists_install.txt DESTINATION class_api/examples/src/cpp RENAME CMakeLists.txt) \ No newline at end of file diff --git a/src/tests/test_cpp/CMakeLists_install.txt b/src/tests/test_cpp/CMakeLists_install.txt deleted file mode 100644 index 7e8cb98..0000000 --- a/src/tests/test_cpp/CMakeLists_install.txt +++ /dev/null @@ -1,19 +0,0 @@ -cmake_minimum_required(VERSION 3.14) -project(test_cpp) - -link_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/cpp) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../../include/class ${CMAKE_CURRENT_SOURCE_DIR}/../../../include) - -add_library(class_dll SHARED IMPORTED) -set_target_properties(class_dll PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/cpp/class.dll) -set_target_properties(class_dll PROPERTIES IMPORTED_IMPLIB ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/cpp/class.lib) -set_target_properties(class_dll PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/../../../include/class) - -add_executable(test_cpp_acq test_cpp_acq.cpp) -target_link_libraries(test_cpp_acq class_dll) - -add_executable(test_cpp_stim test_cpp_stim.cpp) -target_link_libraries(test_cpp_stim class_dll) - -add_executable(test_cpp_callback test_cpp_callback.cpp) -target_link_libraries(test_cpp_callback class_dll) \ No newline at end of file diff --git a/src/tests/test_cpp/test_cpp_acq.bat b/src/tests/test_cpp/test_cpp_acq.bat deleted file mode 100644 index e0889c8..0000000 --- a/src/tests/test_cpp/test_cpp_acq.bat +++ /dev/null @@ -1,3 +0,0 @@ -set CLASS_LIB_PATH=..\..\..\lib\cpp -echo %PATH%|find /i "%CLASS_LIB_PATH:"=%">nul || set PATH=%CLASS_LIB_PATH%;%PATH% -test_cpp_acq.exe \ No newline at end of file diff --git a/src/tests/test_cpp/test_cpp_acq.cpp b/src/tests/test_cpp/test_cpp_acq.cpp deleted file mode 100644 index e12bbab..0000000 --- a/src/tests/test_cpp/test_cpp_acq.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/** - * @file test_cpp_acq.hpp - * @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> and Leire Querejeta Lomas <leire.querejeta@tecnalia.com> - * @date 2020 - * - * Copyright 2020 Tecnalia Research & Innovation. - * - * @brief Example of use of CLASS_API in CPP for acquisition - */ - -#include "class.hpp" -#include <iostream> -#include <sstream> -#include <chrono> -#include <thread> -#include <iomanip> - -int main(int argc, char * argv[]) -{ - Class class_device; - std::cout << "[TestAcq] Connect" << std::endl; - int response = class_device.connect(); - if (response !=0 ) - { - return -1; - } - - std::cout << "[TestAcq] Configuring CLASS" << std::endl; - class_device.initCommunication(); - std::vector<int> channels; - //get acq from 7 - channels.push_back(7); - class_device.setAcqConfig(Class::FREQ_250, channels, Class::GAIN_24, Class::TEST); - class_device.startAcqStream(); - std::cout << "[TestAcq] Start acquisition" << std::endl; - class_device.startAcq(); - - std::cout << "[TestAcq] Wait 20 seconds" << std::endl; - int loop_number = 0; - while (loop_number < 20) - { - std::vector<AcqFrame> acq_frames = class_device.getAcqFrames(); - //std::cout << "[TestAcq]" << acq_frames.size() << " frames received" << std::endl; - for(int i=0; i <acq_frames.size(); i++) - { - std::ostringstream channels_string_oss; - int j = 1; - for(int j=0; j <acq_frames[i].values.size(); j++) - { - channels_string_oss << ", ch" << (j+1) << ": " << std::fixed << std::setprecision(4) << (double)acq_frames[i].values[j] << "(uV)"; - } - std::cout << "Frame " << i << ": Timestamp: " << std::fixed << std::setprecision(4) << acq_frames[i].timestamp << "(ms)" << channels_string_oss.str() << std::endl; - } - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - loop_number += 1; - std::cout << "[TestAcq] " << loop_number << " second(s)" << std::endl; - } - - std::cout << "[TestAcq] Stop acquisition" << std::endl; - class_device.stopAcq(); - - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - - class_device.setAcqImpedanceConfig(Class::TYPE_POSITIVE); - class_device.setAcqImpedancePolarity(Class::TYPE_UNIPOLAR); - - std::cout << "[TestAcq] Start impedance acquisition" << std::endl; - class_device.startAcqStream(); - class_device.startImpedanceAcq(); - std::cout << "[TestAcq] Wait 20 seconds" << std::endl; - loop_number = 0; - while (loop_number < 20) - { - std::vector<AcqFrame> acq_frames = class_device.getAcqFrames(); - for(int i=0; i <acq_frames.size(); i++) - { - std::ostringstream channels_string_oss; - int j = 1; - for(int j=0; j <acq_frames[i].values.size(); j++) - { - channels_string_oss << ", ch" << (j+1) << ": " << std::fixed << std::setprecision(4) << acq_frames[i].values[j] << "(ohms)"; - } - std::cout << "Frame " << i << ": Timestamp: " << std::fixed << std::setprecision(4) << acq_frames[i].timestamp << "(ms)" << channels_string_oss.str() << std::endl; - } - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - loop_number += 1; - std::cout << "[TestAcq] " << loop_number << " second(s)" << std::endl; - } - std::cout << "[TestAcq] Stop impedance acquisition" << std::endl; - class_device.stopImpedanceAcq(); - - std::cout << "[TestAcq] Disconnect" << std::endl; - response = class_device.disconnect(); - if (response !=0 ) - { - return -1; - } - - return 0; -} \ No newline at end of file diff --git a/src/tests/test_cpp/test_cpp_callback.bat b/src/tests/test_cpp/test_cpp_callback.bat deleted file mode 100644 index 87d94d3..0000000 --- a/src/tests/test_cpp/test_cpp_callback.bat +++ /dev/null @@ -1,3 +0,0 @@ -set CLASS_LIB_PATH=..\..\..\lib\cpp -echo %PATH%|find /i "%CLASS_LIB_PATH:"=%">nul || set PATH=%CLASS_LIB_PATH%;%PATH% -test_cpp_callback.exe \ No newline at end of file diff --git a/src/tests/test_cpp/test_cpp_callback.cpp b/src/tests/test_cpp/test_cpp_callback.cpp deleted file mode 100644 index 38782fa..0000000 --- a/src/tests/test_cpp/test_cpp_callback.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/** - * @file test_cpp.hpp - * @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> and Leire Querejeta Lomas <leire.querejeta@tecnalia.com> - * @date 2020 - * - * Copyright 2020 Tecnalia Research & Innovation. - * - * @brief Example of use of CLASS_API in CPP for stimulation - */ - -#include "class.hpp" -#include <iostream> -#include <sstream> -#include <chrono> -#include <thread> -#include <string> - -bool turnOffHandle_ = false; - -class TestCallback : ClassCallback -{ - public: - TestCallback() - { - - } - - void batteryHandle(double battery) - { - std::cout << "[TestCB] Received battery: " << battery << std::endl; - } - - void firmwareHandle(std::string firmware) - { - std::cout << "[TestCB] firmware value: " << firmware << std::endl; - } - - void deviceNameHandle(std::string device) - { - std::cout << "[TestCB] device value: " << device << std::endl; - } - - void logeventsHandle(std::string logevents) - { - std::cout << "[TestCB] logevents: " << logevents << std::endl; - } - - void patternHandle(std::string patternstatus) - { - std::cout << "[TestCB] patternstatus: " << patternstatus << std::endl; - } - - void velecstatusHandle(std::string velecstatus) - { - std::cout << "[TestCB] velecstatus: " << velecstatus << std::endl; - } - - void hvHandle(std::string hvstatus) - { - std::cout << "[TestCB] hvstatus: " << hvstatus << std::endl; - } - - void intervalHandle(std::string intervalstatus) - { - std::cout << "[TestCB] intervalstatus: " << intervalstatus << std::endl; - } - - void rtcHandle(std::string rtcstatus) - { - std::cout << "[TestCB] rtcstatus: " << rtcstatus << std::endl; - } - - void buzzerHandle(std::string buzzerstatus) - { - std::cout << "[TestCB] buzzerstatus: " << buzzerstatus << std::endl; - } - - void frequencyHandle(std::string frequencystatus) - { - std::cout << "[TestCB] frequency: " << frequencystatus << std::endl; - } - - void hardwareHandle(std::string hardware) - { - std::cout << "[TestCB] hardware value: " << hardware << std::endl; - } - - void ticHandle(std::string tic) - { - std::cout << "[TestCB] tic value: " << tic << std::endl; - } - - void turnOffHandle() - { - std::cout << "[TestCB] Received turn off" << std::endl; - turnOffHandle_ = true; - } - - void sdfunctionHandle(std::string sdfunctionstatus) - { - std::cout << "[TestCB] sd function: " << sdfunctionstatus << std::endl; - } - - void sdunameHandle(std::string sdunamestatus) - { - std::cout << "[TestCB] sd user name: " << sdunamestatus << std::endl; - } - -}; - -int main(int argc, char * argv[]) -{ - Class class_device; - std::cout << "[TestCB] Connect" << std::endl; - int response = class_device.connect(); - if (response !=0 ) - { - return -1; - } - - std::cout << "[TestCB] Configuring CLASS" << std::endl; - class_device.initCommunication(); - - std::cout << "[TestCB] Set battery callback"<< std::endl; - ClassCallback *callback; - TestCallback *test_callback = new TestCallback(); - callback = (ClassCallback *) test_callback; - class_device.setCallback(callback, "cpp"); - - std::cout << "[TestCB] Get battery. Wait 20 seconds"<< std::endl; - int loop_number = 0; - while (loop_number < 20 && !turnOffHandle_) - { - class_device.getBattery(); - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - loop_number += 1; - std::cout << "[TestCB] Waiting... " << loop_number << " second(s)"<< std::endl; - } - - std::cout << "[TestCB] Stop getting battery"<< std::endl; - - std::cout << "[TestCB] Disconnect" << std::endl; - response = class_device.disconnect(); - if (response !=0 ) - { - return -1; - } - - return 0; -} \ No newline at end of file diff --git a/src/tests/test_cpp/test_cpp_stim.bat b/src/tests/test_cpp/test_cpp_stim.bat deleted file mode 100644 index 0626518..0000000 --- a/src/tests/test_cpp/test_cpp_stim.bat +++ /dev/null @@ -1,3 +0,0 @@ -set CLASS_LIB_PATH=..\..\..\lib\cpp -echo %PATH%|find /i "%CLASS_LIB_PATH:"=%">nul || set PATH=%CLASS_LIB_PATH%;%PATH% -test_cpp_stim.exe \ No newline at end of file diff --git a/src/tests/test_cpp/test_cpp_stim.cpp b/src/tests/test_cpp/test_cpp_stim.cpp deleted file mode 100644 index c6f3e2a..0000000 --- a/src/tests/test_cpp/test_cpp_stim.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @file test_cpp.hpp - * @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> - * @date 2020 - * - * Copyright 2020 Tecnalia Research & Innovation. - * - * @brief Example of use of CLASS_API in CPP for stimulation - */ - -#include "class.hpp" -#include <iostream> -#include <sstream> -#include <chrono> -#include <thread> - -int main(int argc, char * argv[]) -{ - Class class_device; - std::cout << "[TestStim] Connect" << std::endl; - int response = class_device.connect(); - if (response !=0 ) - { - return -1; - } - - std::cout << "[TestStim] Configuring CLASS" << std::endl; - class_device.initCommunication(); - - std::cout << "[TestStim] Create electrode 1. Set number of pads in electrode 1 to 32"<< std::endl; - class_device.setElecPads(1, 32); - std::cout << "[TestStim] Set frequency of stimulation to 35Hz"<< std::endl; - class_device.setStimFreq(35.0); - std::cout << "[TestStim] Create virtual electrode for thumb. Its ID is 10. One cathode in pad 7. One anode in pad 4. Amp set to 2,5. Width to 400. Selected. Not sync"<< std::endl; - std::vector<int> cathodes; - cathodes.push_back(4); - std::vector<int> anodes; - anodes.push_back(7); - std::vector<double> amp; - amp.push_back(2.5); - std::vector<int> width; - width.push_back(400); - class_device.setVelec(10, "thumb", 1, cathodes, anodes, amp, width, true, false); - std::cout << "[TestStim] Start stimulation"<< std::endl; - class_device.startStim(); - std::cout << "[TestStim] Stimulate 20 seconds"<< std::endl; - int loop_number = 0; - while (loop_number < 20) - { - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - loop_number += 1; - std::cout << "[TestStim] Stimulating... " << loop_number << " second(s)"<< std::endl; - } - std::cout << "[TestStim] Stop stimulation"<< std::endl; - class_device.stopStim(); - - - std::cout << "[TestStim] Disconnect" << std::endl; - response = class_device.disconnect(); - if (response !=0 ) - { - return -1; - } - - return 0; -} \ No newline at end of file diff --git a/src/tests/test_wrappers/.gitkeep b/src/tests/test_wrappers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/tests/test_wrappers/CMakeLists.txt b/src/tests/test_wrappers/CMakeLists.txt deleted file mode 100644 index 9381fe1..0000000 --- a/src/tests/test_wrappers/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) -project (tests_wrappers) - -#add_subdirectory(python) - -#if(BUILD_DOTNET) -# add_subdirectory(csharp) -#endif() \ No newline at end of file diff --git a/src/tests/test_wrappers/csharp/.gitkeep b/src/tests/test_wrappers/csharp/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/tests/test_wrappers/csharp/CMakeLists.txt b/src/tests/test_wrappers/csharp/CMakeLists.txt deleted file mode 100644 index 6322784..0000000 --- a/src/tests/test_wrappers/csharp/CMakeLists.txt +++ /dev/null @@ -1,113 +0,0 @@ -cmake_minimum_required(VERSION 3.13) -project(test_csharp_wrapper CSharp) - -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/Class.cs - DEPENDS csharpClass -) - -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/csClassPINVOKE.cs - DEPENDS csharpClass -) - -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfDoubles.cs - DEPENDS csharpClass -) - -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfInts.cs - DEPENDS csharpClass -) - -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfAcqFrames.cs - DEPENDS csharpClass -) - -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfPatterns.cs - DEPENDS csharpClass -) - -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/AcqFrame.cs - DEPENDS csharpClass -) - -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/Pattern.cs - DEPENDS csharpClass -) - -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/ClassError.cs - DEPENDS csharpClass -) - -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/ClassCallback.cs - DEPENDS csharpClass -) - -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfBools.cs - DEPENDS csharpClass -) - -add_executable(TestCSharpWrapperAcq TestCSharpWrapperAcq.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/Class.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/csClassPINVOKE.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfDoubles.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfInts.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfAcqFrames.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfPatterns.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/AcqFrame.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/Pattern.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/ClassError.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/ClassCallback.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfBools.cs) - -add_executable(TestCSharpWrapperStim TestCSharpWrapperStim.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/Class.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/csClassPINVOKE.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfDoubles.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfInts.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfAcqFrames.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfPatterns.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/AcqFrame.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/Pattern.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/ClassError.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/ClassCallback.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfBools.cs) - -add_executable(TestCSharpWrapperCallback TestCSharpWrapperCallback.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/Class.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/csClassPINVOKE.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfDoubles.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfInts.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfAcqFrames.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfPatterns.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/AcqFrame.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/Pattern.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/ClassError.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/ClassCallback.cs - ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfBools.cs) - -# copy generated dll file to bin dir -add_custom_target( - copyCSharpClass ALL - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/Release/cSharpClass.dll ${CMAKE_CURRENT_BINARY_DIR}/Release/cSharpClass.dll -) -add_dependencies(copyCSharpClass csharpClass) - - -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/Class.cs ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/csClassPINVOKE.cs ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfDoubles.cs ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfInts.cs ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfAcqFrames.cs ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfPatterns.cs ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/AcqFrame.cs ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/Pattern.cs ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/ClassError.cs ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/ClassCallback.cs ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/c_sharp/VectorOfBools.cs DESTINATION class_api/lib/csharp) -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/TestCSharpWrapperAcq.cs ${CMAKE_CURRENT_SOURCE_DIR}/TestCSharpWrapperStim.cs ${CMAKE_CURRENT_SOURCE_DIR}/TestCSharpWrapperCallback.cs DESTINATION class_api/examples/src/csharp) -install(TARGETS TestCSharpWrapperAcq TestCSharpWrapperStim TestCSharpWrapperCallback - RUNTIME DESTINATION class_api/examples/bin/csharp) -install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_csharp_wrapper_acq.bat DESTINATION class_api/examples/bin/csharp) -install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_csharp_wrapper_stim.bat DESTINATION class_api/examples/bin/csharp) -install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_csharp_wrapper_callback.bat DESTINATION class_api/examples/bin/csharp) -install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists_install.txt DESTINATION class_api/examples/src/csharp RENAME CMakeLists.txt) \ No newline at end of file diff --git a/src/tests/test_wrappers/csharp/CMakeLists_install.txt b/src/tests/test_wrappers/csharp/CMakeLists_install.txt deleted file mode 100644 index 7d1cfb9..0000000 --- a/src/tests/test_wrappers/csharp/CMakeLists_install.txt +++ /dev/null @@ -1,43 +0,0 @@ -cmake_minimum_required(VERSION 3.13) -project(test_csharp_wrapper CSharp) - -if (WIN32) - add_executable(TestCSharpWrapperAcq TestCSharpWrapperAcq.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/Class.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/csClassPINVOKE.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/VectorOfDoubles.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/VectorOfInts.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/VectorOfAcqFrames.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/VectorOfPatterns.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/AcqFrame.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/Pattern.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/ClassError.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/ClassCallback.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/VectorOfBools.cs) - - add_executable(TestCSharpWrapperStim TestCSharpWrapperStim.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/Class.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/csClassPINVOKE.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/VectorOfDoubles.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/VectorOfInts.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/VectorOfAcqFrames.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/VectorOfPatterns.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/AcqFrame.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/Pattern.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/ClassError.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/ClassCallback.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/VectorOfBools.cs) - - add_executable(TestCSharpWrapperCallback TestCSharpWrapperCallback.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/Class.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/csClassPINVOKE.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/VectorOfDoubles.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/VectorOfInts.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/VectorOfAcqFrames.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/VectorOfPatterns.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/AcqFrame.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/Pattern.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/ClassError.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/ClassCallback.cs - ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/csharp/VectorOfBools.cs) -endif (WIN32) diff --git a/src/tests/test_wrappers/csharp/TestCSharpWrapperAcq.cs b/src/tests/test_wrappers/csharp/TestCSharpWrapperAcq.cs deleted file mode 100644 index a198deb..0000000 --- a/src/tests/test_wrappers/csharp/TestCSharpWrapperAcq.cs +++ /dev/null @@ -1,128 +0,0 @@ -/** - * @file TestCSharpWrapperAcq.cs - * @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> - * @date 2020 - * - * Copyright 2020 Tecnalia Research & Innovation. - * - * @brief Example of use of CLASS_API for acquisition in c# - */ - -using System; -using System.Threading; -using System.Runtime.InteropServices; - -class TestCSharpWrapperAcq -{ - static void Main(string[] args) - { - //'.' instead of ',' for string containing double - System.Globalization.CultureInfo customCulture = (System.Globalization.CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone(); - customCulture.NumberFormat.NumberDecimalSeparator = "."; - System.Threading.Thread.CurrentThread.CurrentCulture = customCulture; - - Console.WriteLine("[TestAcq] Connect"); - Class classDevice = new Class(); - ClassError.Error response = classDevice.connect(); - if(response != ClassError.Error.CLASS_NO_ERROR) - { - Environment.Exit(-1); - } - - int loop_number = 0; - - Console.WriteLine("[TestAcq] Configuring CLASS"); - - classDevice.initCommunication(); - VectorOfInts channels = new VectorOfInts(); - //get acq from channel 1 and 4 - channels.Add(1); - channels.Add(4); - classDevice.setAcqConfig(Class.AcqFreq.FREQ_250, channels, Class.AcqGain.GAIN_24, Class.AcqInput.NORMAL); - classDevice.startAcqStream(); - - Console.WriteLine("[TestAcq] Start acquisition"); - classDevice.startAcq(); - Console.WriteLine("[TestAcq] Wait 20 seconds"); - while (loop_number < 20) - { - try - { - var acqFrames = classDevice.getAcqFrames(); - int i = 0; - foreach (var frame in acqFrames) - { - string channelsString = ""; - AcqFrame acqFrame = frame; - int j = 1; - foreach (var channel in acqFrame.values) - { - channelsString += ", ch" + j + ": " + string.Format("{0:0.0000}", (double)channel) + "(uV)"; - j += 1; - } - Console.WriteLine("[TestAcq] Frame " + i + ": Timestamp: " + string.Format("{0:0.0000}", acqFrame.timestamp) + "(ms)" + channelsString) ; - i += 1; - } - } - catch (System.Exception ex) - { - Console.WriteLine("[TestAcq] Exception: " + ex.ToString()); - } - - Thread.Sleep(1000); - loop_number += 1; - Console.WriteLine(loop_number + " second(s)"); - } - Console.WriteLine("[TestAcq] Stop acquisition"); - classDevice.stopAcq(); - - Thread.Sleep(1000); - - classDevice.setAcqImpedanceConfig(Class.AcqImpedanceSign.TYPE_POSITIVE); - classDevice.setAcqImpedancePolarity(Class.AcqImpedancePolarity.TYPE_UNIPOLAR); - - Console.WriteLine("[TestAcq] Start impedance acquisition"); - classDevice.startAcqStream(); - classDevice.startImpedanceAcq(); - Console.WriteLine("[TestAcq] Wait 20 seconds"); - loop_number = 0; - while (loop_number < 20) - { - try - { - var acqFrames = classDevice.getAcqFrames(); - int i = 0; - foreach (var frame in acqFrames) - { - string channelsString = ""; - AcqFrame acqFrame = frame; - int j = 1; - foreach (var channel in acqFrame.values) - { - channelsString += ", ch" + j + ": " + string.Format("{0:0.0000}", channel) + "(ohm)"; - j += 1; - } - Console.WriteLine("[TestAcq] Frame " + i + ": Timestamp: " + string.Format("{0:0.0000}", acqFrame.timestamp) + "(ms)" + channelsString) ; - i += 1; - } - } - catch (System.Exception ex) - { - Console.WriteLine("[TestAcq] Exception: " + ex.ToString()); - } - - Thread.Sleep(1000); - loop_number += 1; - Console.WriteLine(loop_number + " second(s)"); - } - Console.WriteLine("[TestAcq] Stop impedance acquisition"); - classDevice.stopImpedanceAcq(); - - Console.WriteLine("[TestAcq] Disconnect"); - response = classDevice.disconnect(); - if(response != ClassError.Error.CLASS_NO_ERROR) - { - Environment.Exit(-1); - } - } -} \ No newline at end of file diff --git a/src/tests/test_wrappers/csharp/TestCSharpWrapperCallback.cs b/src/tests/test_wrappers/csharp/TestCSharpWrapperCallback.cs deleted file mode 100644 index 422622f..0000000 --- a/src/tests/test_wrappers/csharp/TestCSharpWrapperCallback.cs +++ /dev/null @@ -1,86 +0,0 @@ -/** - * @file TestCSharpWrapperCallback.cs - * @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> - * @date 2020 - * - * Copyright 2020 Tecnalia Research & Innovation. - * - * @brief Example of use of CLASS_API in c# - */ - -using System; -using System.Threading; -using System.Runtime.InteropServices; - -class TestCSharpWrapperCallback : ClassCallback -{ - static bool turnOff = false; - public override void batteryHandle(double battery) - { - System.Console.WriteLine("[TestCB] Battery received: " + battery); - } - - public override void firmwareHandle(string firmware) - { - System.Console.WriteLine("[TestCB] firmware received: " + firmware); - } - - public override void hardwareHandle(string hardware) - { - System.Console.WriteLine("[TestCB] hardware received: " + hardware); - } - - public override void ticHandle(string tic) - { - System.Console.WriteLine("[TestCB] tic received: " + tic); - } - - public override void turnOffHandle() - { - System.Console.WriteLine("[TestCB] Turn off received"); - turnOff = true; - } - - static void Main(string[] args) - { - TestCSharpWrapperCallback test = new TestCSharpWrapperCallback(); - Console.WriteLine("[TestCB] Connect"); - Class classDevice = new Class(); - ClassError.Error response = classDevice.connect(); - if(response != ClassError.Error.CLASS_NO_ERROR) - { - Environment.Exit(-1); - } - - int loop_number = 0; - - Console.WriteLine("[TestCB] Configuring CLASS"); - classDevice.initCommunication(); - - Console.WriteLine("[TestCB] Set callback"); - if(classDevice.setCallback(test, "csharp") == ClassError.Error.CLASS_NO_ERROR) - { - Console.WriteLine("[TestCB] Get Battery. Wait 20 secs"); - loop_number = 0; - while (loop_number < 20 && !turnOff) - { - classDevice.getBattery(); - Thread.Sleep(1000); - loop_number += 1; - Console.WriteLine("[TestCB] Waiting... " + loop_number + " second(s)"); - } - Console.WriteLine("[TestCB] Stop getting battery"); - } - else - { - Console.WriteLine("[TestCB] Error setting callback"); - } - - Console.WriteLine("[TestCB] Disconnect"); - response = classDevice.disconnect(); - if(response != ClassError.Error.CLASS_NO_ERROR) - { - Environment.Exit(-1); - } - } -} \ No newline at end of file diff --git a/src/tests/test_wrappers/csharp/TestCSharpWrapperStim.cs b/src/tests/test_wrappers/csharp/TestCSharpWrapperStim.cs deleted file mode 100644 index d823b41..0000000 --- a/src/tests/test_wrappers/csharp/TestCSharpWrapperStim.cs +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @file TestCSharpWrapperStim.cs - * @author Alfonso Dominguez <alfonso.dominguez@tecnalia.com> - * @date 2020 - * - * Copyright 2020 Tecnalia Research & Innovation. - * - * @brief Example of use of CLASS_API for Stimulation in c# - */ - -using System; -using System.Threading; -using System.Runtime.InteropServices; - -class TestCSharpWrapperStim -{ - static void Main(string[] args) - { - Console.WriteLine("[TestStim] Connect"); - Class classDevice = new Class(); - ClassError.Error response = classDevice.connect(); - if(response != ClassError.Error.CLASS_NO_ERROR) - { - Environment.Exit(-1); - } - - int loop_number = 0; - - Console.WriteLine("[TestStim] Configuring CLASS"); - classDevice.initCommunication(); - - Console.WriteLine("[TestStim] Create electrode 1. Set number of pads in electrode 1 to 32"); - classDevice.setElecPads(1, 32); - Console.WriteLine("[TestStim] Set frequency of stimulation to 35Hz"); - classDevice.setStimFreq(35.0); - Console.WriteLine("[TestStim] Create virtual electrode for thumb. Its ID is 10. One cathode in pad 7. One anode in pad 4. Amp set to 2,5. Width to 400. Selected. Not sync"); - VectorOfInts cathodes = new VectorOfInts(); - cathodes.Add(4); - VectorOfInts anodes = new VectorOfInts(); - anodes.Add(7); - VectorOfDoubles amp = new VectorOfDoubles(); - amp.Add(2.5); - VectorOfInts width = new VectorOfInts(); - width.Add(400); - classDevice.setVelec(10, "thumb", 1, cathodes, anodes, amp, width, true, false); - Console.WriteLine("[TestStim] Start stimulation"); - classDevice.startStim(); - Console.WriteLine("[TestStim] Stimulate 20 seconds"); - loop_number = 0; - while (loop_number < 20) - { - Thread.Sleep(1000); - loop_number += 1; - Console.WriteLine("[TestStim] Stimulating... " + loop_number + " second(s)"); - } - Console.WriteLine("[TestStim] Stop stimulation"); - classDevice.stopStim(); - - Console.WriteLine("[TestStim] Disconnect"); - response = classDevice.disconnect(); - if(response != ClassError.Error.CLASS_NO_ERROR) - { - Environment.Exit(-1); - } - } -} \ No newline at end of file diff --git a/src/tests/test_wrappers/csharp/test_csharp_wrapper_acq.bat b/src/tests/test_wrappers/csharp/test_csharp_wrapper_acq.bat deleted file mode 100644 index b2efba8..0000000 --- a/src/tests/test_wrappers/csharp/test_csharp_wrapper_acq.bat +++ /dev/null @@ -1,3 +0,0 @@ -set CLASS_LIB_PATH=..\..\..\lib\csharp;..\..\..\lib\cpp -echo %PATH%|find /i "%CLASS_LIB_PATH:"=%">nul || set PATH=%CLASS_LIB_PATH%;%PATH% -TestCSharpWrapperAcq.exe \ No newline at end of file diff --git a/src/tests/test_wrappers/csharp/test_csharp_wrapper_callback.bat b/src/tests/test_wrappers/csharp/test_csharp_wrapper_callback.bat deleted file mode 100644 index 491eac8..0000000 --- a/src/tests/test_wrappers/csharp/test_csharp_wrapper_callback.bat +++ /dev/null @@ -1,3 +0,0 @@ -set CLASS_LIB_PATH=..\..\..\lib\csharp;..\..\..\lib\cpp -echo %PATH%|find /i "%CLASS_LIB_PATH:"=%">nul || set PATH=%CLASS_LIB_PATH%;%PATH% -TestCSharpWrapperCallback.exe \ No newline at end of file diff --git a/src/tests/test_wrappers/csharp/test_csharp_wrapper_stim.bat b/src/tests/test_wrappers/csharp/test_csharp_wrapper_stim.bat deleted file mode 100644 index 7c8c24e..0000000 --- a/src/tests/test_wrappers/csharp/test_csharp_wrapper_stim.bat +++ /dev/null @@ -1,3 +0,0 @@ -set CLASS_LIB_PATH=..\..\..\lib\csharp;..\..\..\lib\cpp -echo %PATH%|find /i "%CLASS_LIB_PATH:"=%">nul || set PATH=%CLASS_LIB_PATH%;%PATH% -TestCSharpWrapperStim.exe \ No newline at end of file diff --git a/src/tests/test_wrappers/python/.gitkeep b/src/tests/test_wrappers/python/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/tests/test_wrappers/python/CMakeLists.txt b/src/tests/test_wrappers/python/CMakeLists.txt deleted file mode 100644 index ec0d555..0000000 --- a/src/tests/test_wrappers/python/CMakeLists.txt +++ /dev/null @@ -1,40 +0,0 @@ -cmake_minimum_required(VERSION 3.13) -project(test_python_wrapper) - -find_package(Python REQUIRED COMPONENTS Interpreter) - -# copy src script to bin dir -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test_python_wrapper_acq.py - ${CMAKE_CURRENT_BINARY_DIR} - COPYONLY) - -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test_python_wrapper_stim.py - ${CMAKE_CURRENT_BINARY_DIR} - COPYONLY) - -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test_python_wrapper_callback.py - ${CMAKE_CURRENT_BINARY_DIR} - COPYONLY) - -# copy generated file pyClass.py to bin dir -add_custom_target( - copyPyClass ALL - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/python/pyClass.py ${CMAKE_CURRENT_BINARY_DIR} -) -add_dependencies(copyPyClass pyClass) - -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/../../../wrappers/python/pyClass.py DESTINATION class_api/lib/python) -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_python_wrapper_acq.py DESTINATION class_api/examples/src/python) -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_python_wrapper_stim.py DESTINATION class_api/examples/src/python) -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_python_wrapper_callback.py DESTINATION class_api/examples/src/python) -if (WIN32) - install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_python_wrapper_acq.bat DESTINATION class_api/examples/bin/python) - install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_python_wrapper_stim.bat DESTINATION class_api/examples/bin/python) - install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_python_wrapper_callback.bat DESTINATION class_api/examples/bin/python) -endif (WIN32) - -if (UNIX) - install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_python_wrapper_acq.sh DESTINATION class_api/examples/bin/python) - install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_python_wrapper_stim.sh DESTINATION class_api/examples/bin/python) - install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_python_wrapper_callback.sh DESTINATION class_api/examples/bin/python) -endif (UNIX) \ No newline at end of file diff --git a/src/tests/test_wrappers/python/test_python_wrapper_acq.bat b/src/tests/test_wrappers/python/test_python_wrapper_acq.bat deleted file mode 100644 index 7d836b5..0000000 --- a/src/tests/test_wrappers/python/test_python_wrapper_acq.bat +++ /dev/null @@ -1,7 +0,0 @@ -set CLASS_PYTHON_LIB_PATH=..\..\..\lib\python -set CLASS_CPP_LIB_PATH=..\..\..\lib\cpp - -echo %PYTHONPATH%|find /i "%CLASS_PYTHON_LIB_PATH:"=%">nul || set PYTHONPATH=%PYTHONPATH%;%CLASS_PYTHON_LIB_PATH% -echo %PATH%|find /i "%CLASS_CPP_LIB_PATH:"=%">nul || set PATH=%CLASS_CPP_LIB_PATH%;%PATH% - -python ..\..\src\python\test_python_wrapper_acq.py \ No newline at end of file diff --git a/src/tests/test_wrappers/python/test_python_wrapper_acq.py b/src/tests/test_wrappers/python/test_python_wrapper_acq.py deleted file mode 100644 index 9ea316f..0000000 --- a/src/tests/test_wrappers/python/test_python_wrapper_acq.py +++ /dev/null @@ -1,68 +0,0 @@ -"""Example of use of CLASS_API in Python for acquisition""" -__author__ = "Alfonso Dominguez" -__email__ = "alfonso.dominguez@tecnalia.com" -__copyright__ = "Copyright (C) 2020 Tecnalia Research and Innovation" - - -import sys -import time - -from pyClass import Class, VectorOfInts, VectorOfDoubles - -def test(): - """ - Run tests - :return: - """ - class_device = Class() - print("[TestAcq] Connect") - class_device.connect() - - print("[TestAcq] Configuring CLASS") - class_device.initCommunication() - channels = VectorOfInts() - channels.append(1) - channels.append(4) - class_device.setAcqConfig(Class.FREQ_250, channels, Class.GAIN_24, Class.NORMAL) - class_device.startAcqStream() - - print("[TestAcq] Start acquisition") - class_device.startAcq() - print("[TestAcq] Wait 20 seconds") - for loop_number in range (20): - acq_frames = class_device.getAcqFrames() - for i in range(acq_frames.size()): - channelsString = '' - for j in range(acq_frames[i].values.size()): - channelsString += ", ch" + str(j+1) + ": " + "{:.4f}".format(acq_frames[i].values[j]).replace(',', '.') + "(uV)" - print('Frame ' + str(i) + ': Timestamp: ' + "{:.4f}".format(acq_frames[i].timestamp).replace(',', '.') + "(ms)" + channelsString) - time.sleep(1.0) - print(str(loop_number+1) + " second(s)") - print("[TestAcq] Stop acquisition") - class_device.stopAcq() - - time.sleep(1.0) - - class_device.setAcqImpedanceConfig(Class.TYPE_POSITIVE) - class_device.startAcqStream() - print("[TestAcq] Start impedance acquisition") - class_device.startImpedanceAcq() - print("[TestAcq] Wait 20 seconds") - for loop_number in range (20): - acq_frames = class_device.getAcqFrames() - for i in range(acq_frames.size()): - channelsString = '' - for j in range(acq_frames[i].values.size()): - channelsString += ", ch" + str(j+1) + ": " + "{:.4f}".format(acq_frames[i].values[j]).replace(',', '.') + "(ohm)" - print('Frame ' + str(i) + ': Timestamp: ' + "{:.4f}".format(acq_frames[i].timestamp).replace(',', '.') + "(ms)" + channelsString) - time.sleep(1.0) - print(str(loop_number+1) + " second(s)") - print("[TestAcq] Stop impedance acquisition") - class_device.stopAcq() - - print("[TestAcq] Disconnect") - class_device.disconnect() - - -if __name__ == '__main__': - test() \ No newline at end of file diff --git a/src/tests/test_wrappers/python/test_python_wrapper_acq.sh b/src/tests/test_wrappers/python/test_python_wrapper_acq.sh deleted file mode 100644 index 4ca214c..0000000 --- a/src/tests/test_wrappers/python/test_python_wrapper_acq.sh +++ /dev/null @@ -1,2 +0,0 @@ -export PYTHONPATH=$PYTHONPATH:../../../lib/python -python ../../src/python/test_python_wrapper_acq.py \ No newline at end of file diff --git a/src/tests/test_wrappers/python/test_python_wrapper_callback.bat b/src/tests/test_wrappers/python/test_python_wrapper_callback.bat deleted file mode 100644 index 031e200..0000000 --- a/src/tests/test_wrappers/python/test_python_wrapper_callback.bat +++ /dev/null @@ -1,7 +0,0 @@ -set CLASS_PYTHON_LIB_PATH=..\..\..\lib\python -set CLASS_CPP_LIB_PATH=..\..\..\lib\cpp - -echo %PYTHONPATH%|find /i "%CLASS_PYTHON_LIB_PATH:"=%">nul || set PYTHONPATH=%PYTHONPATH%;%CLASS_PYTHON_LIB_PATH% -echo %PATH%|find /i "%CLASS_CPP_LIB_PATH:"=%">nul || set PATH=%CLASS_CPP_LIB_PATH%;%PATH% - -python ..\..\src\python\test_python_wrapper_callback.py \ No newline at end of file diff --git a/src/tests/test_wrappers/python/test_python_wrapper_callback.py b/src/tests/test_wrappers/python/test_python_wrapper_callback.py deleted file mode 100644 index ab1e470..0000000 --- a/src/tests/test_wrappers/python/test_python_wrapper_callback.py +++ /dev/null @@ -1,57 +0,0 @@ -"""Example of use of CLASS_API in Python for callback""" -__author__ = "Alfonso Dominguez" -__email__ = "alfonso.dominguez@tecnalia.com" -__copyright__ = "Copyright (C) 2020 Tecnalia Research and Innovation" - - -import sys -import time -import types -import threading - -from pyClass import Class, VectorOfInts, VectorOfDoubles, ClassCallback - -class Test(ClassCallback): - turn_off = False - - def batteryHandle(self, battery): - print("[TestCB] Battery received: " + str(battery)) - - def ticHandle(self, tic): - print("[TestCB] tic received: " + tic) - - def turnOffHandle(self): - print("[TestCB] TurnOff received") - self.turn_off = True - - def test(self): - """ - Run tests - :return: - """ - class_device = Class() - print("[TestCB] Connect") - class_device.connect() - print("[TestCB] Configuring CLASS") - class_device.initCommunication() - - print("[TestCB] Set callback") - self.__disown__ = 0 - class_device.setCallback(self, "python") - - print("[TestCB] Get battery. Wait 20 seconds") - loop_number = 0 - while loop_number < 20 and not self.turn_off: - class_device.getBattery() - time.sleep(1.0) - loop_number +=1 - print("[TestCB] Waiting..." + str(loop_number+1) + " second(s)") - print("[TestCB] Stop getting battery") - - print("[TestCB] Disconnect") - class_device.disconnect() - - -if __name__ == '__main__': - t = Test() - t.test() \ No newline at end of file diff --git a/src/tests/test_wrappers/python/test_python_wrapper_callback.sh b/src/tests/test_wrappers/python/test_python_wrapper_callback.sh deleted file mode 100644 index e8f5e36..0000000 --- a/src/tests/test_wrappers/python/test_python_wrapper_callback.sh +++ /dev/null @@ -1,2 +0,0 @@ -export PYTHONPATH=$PYTHONPATH:../../../lib/python -python ../../src/python/test_python_wrapper_callback.py \ No newline at end of file diff --git a/src/tests/test_wrappers/python/test_python_wrapper_stim.bat b/src/tests/test_wrappers/python/test_python_wrapper_stim.bat deleted file mode 100644 index fe2e5db..0000000 --- a/src/tests/test_wrappers/python/test_python_wrapper_stim.bat +++ /dev/null @@ -1,7 +0,0 @@ -set CLASS_PYTHON_LIB_PATH=..\..\..\lib\python -set CLASS_CPP_LIB_PATH=..\..\..\lib\cpp - -echo %PYTHONPATH%|find /i "%CLASS_PYTHON_LIB_PATH:"=%">nul || set PYTHONPATH=%PYTHONPATH%;%CLASS_PYTHON_LIB_PATH% -echo %PATH%|find /i "%CLASS_CPP_LIB_PATH:"=%">nul || set PATH=%CLASS_CPP_LIB_PATH%;%PATH% - -python ..\..\src\python\test_python_wrapper_stim.py \ No newline at end of file diff --git a/src/tests/test_wrappers/python/test_python_wrapper_stim.py b/src/tests/test_wrappers/python/test_python_wrapper_stim.py deleted file mode 100644 index ff1b27c..0000000 --- a/src/tests/test_wrappers/python/test_python_wrapper_stim.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Example of use of CLASS_API in Python for stimulation""" -__author__ = "Alfonso Dominguez" -__email__ = "alfonso.dominguez@tecnalia.com" -__copyright__ = "Copyright (C) 2020 Tecnalia Research and Innovation" - - -import sys -import time - -from pyClass import Class, VectorOfInts, VectorOfDoubles - -def test(): - """ - Run tests - :return: - """ - class_device = Class() - print("[TestStim] Connect") - class_device.connect() - print("[TestStim] Configuring CLASS") - class_device.initCommunication() - - print("[TestStim] Create electrode 1. Set number of pads in electrode 1 to 32") - class_device.setElecPads(1, 32) - print("[TestStim] Set frequency of stimulation to 35Hz") - class_device.setStimFreq(35.0) - print("[TestStim] Create virtual electrode for thumb. Its ID is 10. One cathode in pad 7. One anode in pad 4. Amp set to 2,5. Width to 400. Selected. Not sync") - cathodes = VectorOfInts() - cathodes.append(4) - anodes = VectorOfInts() - anodes.append(7) - amp = VectorOfDoubles() - amp.append(2.5) - width = VectorOfInts() - width.append(400) - class_device.setVelec(10, "thumb", 1, cathodes, anodes, amp, width, True, False) - print("[TestStim] Start stimulation") - class_device.startStim() - print("[TestStim] Stimulate 20 seconds") - loop_number = 0 - while loop_number < 20: - time.sleep(1.0) - loop_number += 1 - print("[TestStim] Stimulating... " + str(loop_number) + " second(s)") - print("[TestStim] Stop stimulation") - class_device.stopStim() - - print("[TestStim] Disconnect") - - class_device.disconnect() - - -if __name__ == '__main__': - test() \ No newline at end of file diff --git a/src/tests/test_wrappers/python/test_python_wrapper_stim.sh b/src/tests/test_wrappers/python/test_python_wrapper_stim.sh deleted file mode 100644 index 757c6f5..0000000 --- a/src/tests/test_wrappers/python/test_python_wrapper_stim.sh +++ /dev/null @@ -1,2 +0,0 @@ -export PYTHONPATH=$PYTHONPATH:../../../lib/python -python ../../src/python/test_python_wrapper_stim.py \ No newline at end of file diff --git a/src/wrappers/.gitkeep b/src/wrappers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/wrappers/CMakeLists.txt b/src/wrappers/CMakeLists.txt deleted file mode 100644 index 25636ff..0000000 --- a/src/wrappers/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 3.14) -project (wrappers) - -find_package(SWIG) -include(${SWIG_USE_FILE}) - -message(STATUS "Build Python: ${BUILD_PYTHON}") -message(STATUS "Build .Net: ${BUILD_DOTNET}") - -#if(BUILD_PYTHON) -# add_subdirectory(python) -#endif() - -if(BUILD_DOTNET) - add_subdirectory(c_sharp) -endif() - -install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/sw_arch.png DESTINATION ${CMAKE_BINARY_DIR}) \ No newline at end of file diff --git a/src/wrappers/c_sharp/.gitkeep b/src/wrappers/c_sharp/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/wrappers/c_sharp/CMakeLists.txt b/src/wrappers/c_sharp/CMakeLists.txt deleted file mode 100644 index c82839a..0000000 --- a/src/wrappers/c_sharp/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -cmake_minimum_required(VERSION 3.14) -project(csharpClass) - -set_property(SOURCE class.i PROPERTY CPLUSPLUS ON) - -swig_add_library(${PROJECT_NAME} LANGUAGE csharp SOURCES class.i) - -swig_link_libraries(${PROJECT_NAME} class) -set_target_properties(${PROJECT_NAME} PROPERTIES SWIG_USE_TARGET_INCLUDE_DIRECTORIES TRUE) - -# 'make install' to the correct location -install(TARGETS ${PROJECT_NAME} - ARCHIVE DESTINATION class_api/lib/csharp - LIBRARY DESTINATION class_api/lib/csharp - RUNTIME DESTINATION class_api/bin/csharp) \ No newline at end of file diff --git a/src/wrappers/c_sharp/class.i b/src/wrappers/c_sharp/class.i deleted file mode 100644 index b63f176..0000000 --- a/src/wrappers/c_sharp/class.i +++ /dev/null @@ -1,27 +0,0 @@ -%module (directors="1") csClass - -%include "std_string.i" -%include "std_vector.i" - -%template(VectorOfDoubles) std::vector<double>; -%template(VectorOfInts) std::vector<int>; -%template(VectorOfBools) std::vector<bool>; - -%feature("director") ClassCallback; - -#define CLASS_EXPORT - -%{ -#include "../../api/class/include/class/class.hpp" -#include "../../api/class/include/class/class_structures.hpp" -#include "../../api/class/include/class/class_error.hpp" -#include "../../api/class/include/class/class_cb.hpp" -%} - -%include "../../api/class/include/class/class.hpp" -%include "../../api/class/include/class/class_structures.hpp" -%include "../../api/class/include/class/class_error.hpp" -%include "../../api/class/include/class/class_cb.hpp" - -%template(VectorOfAcqFrames) std::vector<AcqFrame>; -%template(VectorOfPatterns) std::vector<Pattern>; \ No newline at end of file diff --git a/src/wrappers/python/.gitkeep b/src/wrappers/python/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/wrappers/python/CMakeLists.txt b/src/wrappers/python/CMakeLists.txt deleted file mode 100644 index bd6b72f..0000000 --- a/src/wrappers/python/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -cmake_minimum_required(VERSION 3.14) -project(pyClass) - -find_package(PythonLibs REQUIRED) - -include_directories(${PYTHON_INCLUDE_DIRS}) - -set_property(SOURCE class.i PROPERTY CPLUSPLUS ON) - -swig_add_library(${PROJECT_NAME} LANGUAGE python SOURCES class.i) - -message(STATUS "PYTHON_LIBRARIES = ${PYTHON_LIBRARIES}") - -swig_link_libraries(${PROJECT_NAME} class ${PYTHON_LIBRARIES}) -set_target_properties(${PROJECT_NAME} PROPERTIES SWIG_USE_TARGET_INCLUDE_DIRECTORIES TRUE) - -# 'make install' to the correct location -install(TARGETS ${PROJECT_NAME} - ARCHIVE DESTINATION class_api/lib/python - LIBRARY DESTINATION class_api/lib/python - RUNTIME DESTINATION class_api/bin/python) \ No newline at end of file diff --git a/src/wrappers/python/class.i b/src/wrappers/python/class.i deleted file mode 100644 index 913eeda..0000000 --- a/src/wrappers/python/class.i +++ /dev/null @@ -1,28 +0,0 @@ -%module (directors="1") pyClass - -%include "std_string.i" -%include "std_vector.i" - -%template(VectorOfDoubles) std::vector<double>; -%template(VectorOfInts) std::vector<int>; - -%template(VectorOfBools) std::vector<bool>; - -%feature("director") ClassCallback; - -#define CLASS_EXPORT - -%{ -#include "../../api/class/include/class/class.hpp" -#include "../../api/class/include/class/class_structures.hpp" -#include "../../api/class/include/class/class_error.hpp" -#include "../../api/class/include/class/class_cb.hpp" -%} - -%include "../../api/class/include/class/class.hpp" -%include "../../api/class/include/class/class_structures.hpp" -%include "../../api/class/include/class/class_error.hpp" -%include "../../api/class/include/class/class_cb.hpp" - -%template(VectorOfAcqFrames) std::vector<AcqFrame>; -%template(VectorOfPatterns) std::vector<Pattern>; \ No newline at end of file diff --git a/src/yaml_tools/.gitkeep b/src/yaml_tools/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/yaml_tools/CMakeLists.txt b/src/yaml_tools/CMakeLists.txt deleted file mode 100644 index b38f788..0000000 --- a/src/yaml_tools/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) -project(yaml_tools) -message("cmake ${PROJECT_NAME}") -if (DEBUG_CMAKE) - set(CMAKE_VERBOSE_MAKEFILE yes) -endif (DEBUG_CMAKE) - -set(yaml_tools_files - ${CMAKE_CURRENT_SOURCE_DIR}/src/yaml_tools.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/yaml_tools/yaml_tools.hpp - ) - -set(yaml_tools_files ${yaml_tools_files} PARENT_SCOPE) - -include_directories( - include - ${Boost_INCLUDE_DIR} - ${YAML_INCLUDE_DIR} - ) - -add_library(yaml_tools ${yaml_tools_files}) -add_dependencies(yaml_tools yaml-cpp) -target_link_libraries(yaml_tools yaml-cpp) - -set_target_properties(yaml_tools PROPERTIES FOLDER yaml_tools) - -# Define headers for this library. PUBLIC headers are used for -# compiling the library, and will be added to consumers' build -# paths. -target_include_directories(yaml_tools PUBLIC - $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> - $<INSTALL_INTERFACE:include> - PRIVATE src) - -# This makes the project importable from the build directory -export(TARGETS yaml_tools yaml-cpp FILE yaml_toolsConfig.cmake) \ No newline at end of file diff --git a/src/yaml_tools/include/.gitkeep b/src/yaml_tools/include/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/yaml_tools/include/yaml_tools/.gitkeep b/src/yaml_tools/include/yaml_tools/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/yaml_tools/include/yaml_tools/yaml_tools.hpp b/src/yaml_tools/include/yaml_tools/yaml_tools.hpp deleted file mode 100644 index 332373f..0000000 --- a/src/yaml_tools/include/yaml_tools/yaml_tools.hpp +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @file yaml_utils.hpp - * @author Anthony Remazeilles <anthony.remazeilles@tecnalia.com> - * @date 2019 - * - * Copyright 2019 Tecnalia Research & Innovation. - * Distributed under the GNU GPL v3. For full terms see https://www.gnu.org/licenses/gpl.txt - * - * @brief helper functions for yaml-cpp package. - */ - -#ifndef YAML_UTILS_HPP -#define YAML_UTILS_HPP - -#include <yaml-cpp/yaml.h> -#include <string> -#include <vector> - -/** - * @brief check if a tag is defined in a yaml structure - * - * @param[in] tag list of strings bringing to the desired tag - * @param[in] n YAML node from where the tag is to be searched - * - * @return true if the tag is present in the yaml structure. - */ -bool isTagDefined(const std::vector < std::string> &tag, YAML::Node n); -/** - * @brief check if a list of tags are defined in a yaml structure - * - * @param[in] ltags list of strings bringing to the desired tags - * @param[in] n YAML node from where the tag is to be searched - * - * @return true if the tag is present in the yaml structure. - */ -bool areTagsDefined(const std::vector <std::vector < std::string> > <ags, - const YAML::Node & n); -#endif // YAML_UTILS_HPP diff --git a/src/yaml_tools/src/.gitkeep b/src/yaml_tools/src/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/yaml_tools/src/yaml_tools.cpp b/src/yaml_tools/src/yaml_tools.cpp deleted file mode 100644 index 12b3c9a..0000000 --- a/src/yaml_tools/src/yaml_tools.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @file yaml_utils.cpp - * @author Anthony Remazeilles <anthony.remazeilles@tecnalia.com> - * @date 2019 - * - * Copyright 2019 Tecnalia Research & Innovation. - * Distributed under the GNU GPL v3. For full terms see https://www.gnu.org/licenses/gpl.txt - * - * @brief helper functions for yaml-cpp package. - */ - -#include <yaml_tools/yaml_tools.hpp> -#include <iostream> - -bool isTagDefined(const std::vector < std::string> &tag, YAML::Node n) -{ - YAML::Node node = YAML::Clone(n); - for (auto item : tag) - { - // std::cout << "Checking tag " << item << std::endl; - if (node[item]) - { - node = node[item]; - } - else - { - std::cerr << "Prb while searching for tag " << item << std::endl; - return false; - } - } - return true; -} -bool areTagsDefined(const std::vector <std::vector < std::string> > <ags, - const YAML::Node & n) -{ - for (auto item : ltags) - { - std::ostringstream oss; - for (auto tag : item) - { - oss << tag << "."; - } - std::string msg = oss.str(); - // remove last - msg.pop_back(); - YAML::Node node = n; - if (!isTagDefined(item, node)) - { - std::cerr << "Could not find the tag " << msg << std::endl; - return false; - } - } - return true; -} -- GitLab