diff --git a/.github/actions/module-build-test/action.yml b/.github/actions/module-build-test/action.yml new file mode 100644 index 0000000..f5e0fb4 --- /dev/null +++ b/.github/actions/module-build-test/action.yml @@ -0,0 +1,29 @@ +name: Build and test simple device module +description: Configure, build and test simple device module with CMake + +runs: + using: composite + steps: + - name: Set CMake generator + shell: bash + run: | + if [[ "${{ runner.os }}" == "Windows" ]]; then + echo "CMAKE_GENERATOR=Visual Studio 17 2022" >> $GITHUB_ENV + else + echo "CMAKE_GENERATOR=Ninja" >> $GITHUB_ENV + fi + + - name: Configure simple device module with CMake + shell: bash + run: | + cmake -B build/output -S . -G "$CMAKE_GENERATOR" -DEXAMPLE_MODULE_ENABLE_TESTS=ON -DCMAKE_BUILD_TYPE=Release + + - name: Build simple device module with CMake + shell: bash + run: | + cmake --build build/output --config Release + + - name: Run simple device module tests with CMake + shell: bash + run: | + ctest --test-dir build/output --output-on-failure -C Release -V diff --git a/.github/workflows/ci-framework.yml b/.github/workflows/ci-framework.yml new file mode 100644 index 0000000..e63c830 --- /dev/null +++ b/.github/workflows/ci-framework.yml @@ -0,0 +1,82 @@ +name: Build simple device module with openDAQ framework and run tests + +on: + workflow_dispatch: + inputs: + runner: + description: "Runner label" + required: true + type: string + + branch: + description: "Branch to checkout" + required: false + type: string + default: "" + + artifact-run-id: + required: true + type: string + + artifact-name: + required: true + type: string + + file-name: + required: true + type: string + + workflow_call: + inputs: + runner: + description: "Runner label" + required: true + type: string + + branch: + description: "Branch to checkout" + required: false + type: string + default: "" + + artifact-run-id: + required: true + type: string + + artifact-name: + required: true + type: string + + file-name: + required: true + type: string + +env: + GH_TOKEN: ${{ github.token }} + +jobs: + test-artifact: + runs-on: ${{ inputs.runner }} + + steps: + - name: Checkout openDAQ module repository + uses: actions/checkout@v4 + with: + repository: openDAQ/SimpleDeviceModule + ref: ${{ inputs.branch }} + + - name: Download openDAQ framework artifact + id: download + uses: openDAQ/actions/framework-download-artifact@jira/TBBAS-2680-opendaq-gh-actions-github-api-wrapper-unit-tests + with: + run-id: ${{ inputs.artifact-run-id }} + artifact-name: ${{ inputs.artifact-name }} + artifact-filename: ${{ inputs.file-name }} + + - name: Install openDAQ framework artifact + uses: openDAQ/actions/framework-install@jira/TBBAS-2680-opendaq-gh-actions-github-api-wrapper-unit-tests + with: + framework-filename: ${{ steps.download.outputs.artifact }} + + - name: Build and test simple device module + uses: ./.github/actions/module-build-test diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..72bb675 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,60 @@ +name: Build and Test simple device module with latest openDAQ release + +on: + push: + branches: [main] + pull_request: + +env: + GH_TOKEN: ${{ github.token }} + +jobs: + test-asset: + permissions: + contents: read + actions: read + + env: + GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} + + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + platform-alias: "ubuntu20.04-x86_64" + - os: windows-latest + platform-alias: "win64" + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout simple device module repo + uses: actions/checkout@v4 + + - name: Download openDAQ framework asset + id: download-framework-filename + uses: openDAQ/actions/framework-download-release@jira/TBBAS-2680-opendaq-gh-actions-github-api-wrapper-unit-tests + with: + platform: ${{ matrix.platform-alias }} + github-token: ${{ secrets.PAT_TOKEN }} + + - name: Normalize downloaded asset path + id: normalize-framework-filename + shell: bash + run: | + asset=${{ steps.download-framework-filename.outputs.asset }} + # Normalize output-dir path for cross-platform compatibility + if command -v cygpath >/dev/null 2>&1; then + asset="$(cygpath -w "$asset")" + echo "Normalized path (Windows): $asset" + fi + echo "asset=$asset" >> $GITHUB_OUTPUT + + - name: Install openDAQ framework asset + uses: openDAQ/actions/framework-install@jira/TBBAS-2680-opendaq-gh-actions-github-api-wrapper-unit-tests + with: + framework-filename: ${{ steps.normalize-framework-filename.outputs.asset }} + + - name: Build and test simple device module + uses: ./.github/actions/module-build-test diff --git a/.gitignore b/.gitignore index 295355a..72017e9 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,9 @@ bckp # cmake CMakeUserPresets.json -CMakeSettings.json \ No newline at end of file +CMakeSettings.json + +# Secrets +.secrets +# Local configs +.actrc diff --git a/CMakeLists.txt b/CMakeLists.txt index c046522..a631fc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,23 @@ cmake_minimum_required(VERSION 3.25) -set(REPO_NAME example_device_module) +project(SimpleDeviceModule VERSION 1.0.0) + +set(SIMPLE_DEVICE_MODULE_OPENDAQ_SDK_VER "" CACHE STRING "Version of openDAQ SDK to build SimpleDeviceModule with") + set(REPO_OPTION_PREFIX EXAMPLE_MODULE) +set(SDK_TARGET_NAMESPACE daq) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_MESSAGE_CONTEXT_SHOW ON CACHE BOOL "Show CMake message context") + +option(EXAMPLE_MODULE_ENABLE_TESTS "Enable building tests for example_module" ON) +option(SIMPLE_DEVICE_MODULE_ENABLE_SERVER_APP "Enable building example server application" OFF) +if(SIMPLE_DEVICE_MODULE_ENABLE_SERVER_APP) + set(DAQMODULES_OPENDAQ_SERVER_MODULE ON CACHE BOOL "" FORCE) + set(OPENDAQ_ENABLE_NATIVE_STREAMING ON CACHE BOOL "" FORCE) +endif() -project(${REPO_NAME} VERSION 1.0.0) +list(APPEND CMAKE_MESSAGE_CONTEXT ${PROJECT_NAME}) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") if (POLICY CMP0135) cmake_policy(SET CMP0135 NEW) @@ -12,29 +27,21 @@ if (POLICY CMP0077) cmake_policy(SET CMP0077 NEW) endif() -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -list(APPEND CMAKE_MESSAGE_CONTEXT ${REPO_NAME}) -set(CMAKE_MESSAGE_CONTEXT_SHOW ON CACHE BOOL "Show CMake message context") - -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") - add_definitions(-DFMT_HEADER_ONLY) -option(OPENDAQ_DEVICE_EXAMPLE_ENABLE_SERVER_APP "Enable building example server application" OFF) - include(CommonUtils) setup_repo(${REPO_OPTION_PREFIX}) -if(OPENDAQ_DEVICE_EXAMPLE_ENABLE_SERVER_APP) - set(DAQMODULES_OPENDAQ_SERVER_MODULE ON CACHE BOOL "" FORCE) - set(OPENDAQ_ENABLE_NATIVE_STREAMING ON CACHE BOOL "" FORCE) +if (EXAMPLE_MODULE_ENABLE_TESTS) + message(STATUS "Unit tests are ENABLED") + enable_testing() endif() add_subdirectory(external) add_subdirectory(example_module) -if(OPENDAQ_DEVICE_EXAMPLE_ENABLE_SERVER_APP) - message(STATUS "Enbled example server application") +if(SIMPLE_DEVICE_MODULE_ENABLE_SERVER_APP) + message(STATUS "Enbled example server application") add_subdirectory(server_application) endif() @@ -56,4 +63,4 @@ elseif (UNIX AND NOT APPLE) endif() # Include CPack for packaging -include(CPack) \ No newline at end of file +include(CPack) diff --git a/README.md b/README.md index d982f87..41b9d65 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,63 @@ -# Example device module +# SimpleDeviceModule -Simple example that builds an openDAQ module giving access to an example device. Said device contains 2 channels, each with a value and time signal. The value signal outputs a counter signal, increasing at a rate of 1 per second. +**SimpleDeviceModule** is an example module demonstrating how to build and test a simple device module using the **openDAQ framework**. +This repository can serve as a template for building other modules integrated with openDAQ. -## Testing the module +--- -To test the module, enable the `OPENDAQ_DEVICE_EXAMPLE_ENABLE_SERVER_APP` cmake flag. Doing so will add an openDAQ native server module to your build targets, and add an additional "server_application" executable. Running the executable will create a device, add an openDAQ server, and enable discovery. +## Prerequisites -To connect to the device, the openDAQ Python GUI application can be used (Latest Python version is recommended): +- **CMake** (>= 3.25) +- **Git** +- **C++ compiler** (Visual Studio on Windows, GCC/Clang on Linux/macOS) +- Optional: **openDAQ framework** installed locally or available via versioned checkout +--- + +## Building the Project + +There are two main ways to provide the openDAQ framework: + +1. **Using a local openDAQ installation** + - Install the openDAQ package on your machine. + - Set the environment variable `OPENDAQ_ROOT` to the path containing the binaries. + - Then CMake will automatically detect the binaries during configuration. + +```bash +export OPENDAQ_ROOT=/path/to/opendaq + +cmake -S . -B build/output \ + -G "Ninja" \ + -DEXAMPLE_MODULE_ENABLE_TESTS=ON \ + -DSIMPLE_DEVICE_MODULE_OPENDAQ_SDK_VER=3.20.4 +``` + +2. **Using a specific openDAQ version via CMake** + - Pass the desired version using the `SIMPLE_DEVICE_MODULE_OPENDAQ_SDK_VER`. + - CMake will perform a checkout of the openDAQ repository at the specified tag and build the minimal set of binaries needed to build the module and run tests. + +```bash +cmake -S . -B build/output \ + -G "Ninja" \ + -DEXAMPLE_MODULE_ENABLE_TESTS=ON \ + -DSIMPLE_DEVICE_MODULE_OPENDAQ_SDK_VER=3.20.4 +``` + +--- + +### Example: Build Module + +```bash +cmake --build build/output --config Release +``` + +#### Note: +- The flag `EXAMPLE_MODULE_ENABLE_TESTS=ON` is required if you want to build the module tests for subsequent execution. +- Building the module without `EXAMPLE_MODULE_ENABLE_TESTS=ON` will skip test compilation. +- Providing either `OPENDAQ_ROOT` or `SIMPLE_DEVICE_MODULE_OPENDAQ_SDK_VER` is mandatory for the module to find the required binaries. + +### Running Tests +Once the build is complete: +```bash +ctest --test-dir build/output --output-on-failure -C Release -V ``` -py -m pip install openDAQ -py -m openDAQ -``` \ No newline at end of file diff --git a/example_module/CMakeLists.txt b/example_module/CMakeLists.txt index 6338c6d..a5f2808 100644 --- a/example_module/CMakeLists.txt +++ b/example_module/CMakeLists.txt @@ -3,6 +3,6 @@ project(ExampleModule VERSION 1.0.0 LANGUAGES CXX) add_subdirectory(src) -if (OPENDAQ_ENABLE_TESTS) +if (EXAMPLE_MODULE_ENABLE_TESTS) add_subdirectory(tests) endif() diff --git a/example_module/src/CMakeLists.txt b/example_module/src/CMakeLists.txt index 0726437..b642274 100644 --- a/example_module/src/CMakeLists.txt +++ b/example_module/src/CMakeLists.txt @@ -1,5 +1,6 @@ set(LIB_NAME example_module) set(MODULE_HEADERS_DIR ../include/${TARGET_FOLDER_NAME}) +list(APPEND CMAKE_MESSAGE_CONTEXT ${LIB_NAME}) set(SRC_Include common.h module_dll.h @@ -37,6 +38,7 @@ if (MSVC) target_compile_options(${LIB_NAME} PRIVATE /bigobj) endif() +find_package(openDAQ ${SIMPLE_DEVICE_MODULE_OPENDAQ_SDK_VER} REQUIRED) target_link_libraries(${LIB_NAME} PUBLIC daq::opendaq ) diff --git a/example_module/tests/CMakeLists.txt b/example_module/tests/CMakeLists.txt index 7265fbc..38fce64 100644 --- a/example_module/tests/CMakeLists.txt +++ b/example_module/tests/CMakeLists.txt @@ -1,5 +1,6 @@ set(MODULE_NAME example_module) set(TEST_APP test_${MODULE_NAME}) +list(APPEND CMAKE_MESSAGE_CONTEXT ${TEST_APP}) set(TEST_SOURCES test_example_module.cpp test_app.cpp @@ -8,11 +9,33 @@ set(TEST_SOURCES test_example_module.cpp add_executable(${TEST_APP} ${TEST_SOURCES} ) -target_link_libraries(${TEST_APP} PRIVATE daq::test_utils +find_package(openDAQ ${SIMPLE_DEVICE_MODULE_OPENDAQ_SDK_VER} REQUIRED) +target_link_libraries(${TEST_APP} PRIVATE daq::opendaq ${SDK_TARGET_NAMESPACE}::${MODULE_NAME} ) +if (NOT TARGET gtest) + include(FetchContent) + FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip + ) + set(BUILD_GMOCK ON CACHE BOOL "" FORCE) + set(BUILD_GTEST ON CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(googletest) +endif() + +target_link_libraries(${TEST_APP} + PRIVATE + GTest::gtest + GTest::gtest_main + GTest::gmock + GTest::gmock_main +) + add_test(NAME ${TEST_APP} COMMAND $ - WORKING_DIRECTORY bin + WORKING_DIRECTORY $ ) + +set_target_properties(${TEST_APP} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY $) diff --git a/example_module/tests/test_app.cpp b/example_module/tests/test_app.cpp index 4f6ea7d..37ad2b1 100644 --- a/example_module/tests/test_app.cpp +++ b/example_module/tests/test_app.cpp @@ -1,20 +1,10 @@ -#include -#include +#include #include -#include -#include - int main(int argc, char** args) { - daq::daqInitializeCoreObjectsTesting(); - daqInitModuleManagerLibrary(); - testing::InitGoogleTest(&argc, args); - testing::TestEventListeners& listeners = testing::UnitTest::GetInstance()->listeners(); - listeners.Append(new DaqMemCheckListener()); - auto res = RUN_ALL_TESTS(); return res; diff --git a/example_module/tests/test_example_module.cpp b/example_module/tests/test_example_module.cpp index 2847362..751e5dd 100644 --- a/example_module/tests/test_example_module.cpp +++ b/example_module/tests/test_example_module.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include @@ -19,8 +18,6 @@ using testing::SizeIs; using namespace daq; using namespace std::chrono_literals; -using ExampleChannel = modules::example_module::ExampleChannel; -using ExampleDevice = modules::example_module::ExampleDevice; using ExampleModuleTest = testing::Test; static ModulePtr CreateModule() @@ -153,10 +150,9 @@ TEST_F(ExampleModuleTest, GetDeviceType) DictPtr deviceTypes; ASSERT_NO_THROW(deviceTypes = moduleLib.getAvailableDeviceTypes()); - DeviceTypePtr expected = ExampleDevice::CreateType(); - StringPtr key = expected.getId(); + StringPtr key = "example_dev"; ASSERT_TRUE(deviceTypes.hasKey(key)); - ASSERT_EQ(deviceTypes.get(key).getId(), expected.getId()); + ASSERT_EQ(deviceTypes.get(key).getId(), key); } TEST_F(ExampleModuleTest, GetValueSignals) @@ -250,9 +246,9 @@ TEST_F(ExampleModuleTest, GetDomainSignal) RatioPtr tickResolution; ASSERT_NO_THROW(tickResolution = domainDescriptor.getTickResolution()) << id << " get domain signal tick resolution failed"; - ASSERT_TRUE(tickResolution.assigned()) << "[" + ASSERT_TRUE(tickResolution.assigned()) << id << " domain signal tick resolution is not assigned"; - ASSERT_EQ(tickResolution, ExampleChannel::getResolution()) + ASSERT_EQ(tickResolution, Ratio(1, 1000000)) << id << " domain signal tick resolution mismatches"; DataRulePtr dataRule; @@ -269,9 +265,6 @@ TEST_F(ExampleModuleTest, GetDomainSignal) StringPtr keyStart = "start"; ASSERT_EQ(dataRule.getParameters().get(keyStart), 0) << id << " domain signal data rule \"" << keyStart << "\" parameter mismatches"; - - ASSERT_EQ(domainDescriptor.getOrigin(), ExampleChannel::getEpoch()) - << id << " domain signal origin mismatches"; } } diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index c6ccc9d..1418c82 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -5,6 +5,7 @@ if (${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) message(FATAL_ERROR "In-source build is not supported!") endif() -include(FetchContent) - -add_subdirectory(openDAQ) +find_package(openDAQ ${SIMPLE_DEVICE_MODULE_OPENDAQ_SDK_VER}) +if (NOT openDAQ_FOUND) + add_subdirectory(openDAQ) +endif() diff --git a/external/openDAQ/CMakeLists.txt b/external/openDAQ/CMakeLists.txt index 85ee031..4f8c4ff 100644 --- a/external/openDAQ/CMakeLists.txt +++ b/external/openDAQ/CMakeLists.txt @@ -1,12 +1,18 @@ set(OPENDAQ_ENABLE_TESTS false) +if(NOT SIMPLE_DEVICE_MODULE_OPENDAQ_SDK_VER) + message(FATAL_ERROR "SIMPLE_DEVICE_MODULE_OPENDAQ_SDK_VER is not set") +endif() + +include(FetchContent) + FetchContent_Declare( openDAQ GIT_REPOSITORY https://github.com/openDAQ/openDAQ.git - GIT_TAG v3.20.1 + GIT_TAG v${SIMPLE_DEVICE_MODULE_OPENDAQ_SDK_VER} GIT_PROGRESS ON - SYSTEM - FIND_PACKAGE_ARGS 3.20.1 GLOBAL + SYSTEM + FIND_PACKAGE_ARGS ${SIMPLE_DEVICE_MODULE_OPENDAQ_SDK_VER} GLOBAL ) FetchContent_MakeAvailable(openDAQ) diff --git a/server_application/CMakeLists.txt b/server_application/CMakeLists.txt index c9a7a15..41db6de 100644 --- a/server_application/CMakeLists.txt +++ b/server_application/CMakeLists.txt @@ -1,3 +1,6 @@ +set(SERVER_APP_NAME test_${MODULE_NAME}) +list(APPEND CMAKE_MESSAGE_CONTEXT ${SERVER_APP_NAME}) + add_executable(server_example main.cpp) target_link_libraries(server_example PRIVATE daq::opendaq) @@ -5,4 +8,4 @@ target_link_libraries(server_example PRIVATE daq::opendaq) add_dependencies( example_module daq::native_stream_srv_module -) \ No newline at end of file +)