Initial commit

This commit is contained in:
Jaka Mohorko
2025-01-22 13:32:50 +01:00
commit 60daee2a73
24 changed files with 1084 additions and 0 deletions

62
.clang-format Normal file
View File

@@ -0,0 +1,62 @@
---
BasedOnStyle: Chromium
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
#AllowAllArgumentsOnNextLine: 'false'
#AlignConsecutiveAssignments: None
#AlignConsecutiveDeclarations: None
AlignEscapedNewlines: Left
AlignOperands: true
AllowAllParametersOfDeclarationOnNextLine: true
#AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
#AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Allman
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: true
ColumnLimit: 140
CommentPragmas: '^ IWYU pragma:'
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
IndentCaseLabels: true
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: false
Language: Cpp
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
ObjCBlockIndentWidth: 4
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
#SortIncludes: Never
SpaceAfterCStyleCast: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpacesBeforeTrailingComments: 2
Standard: Auto
TabWidth: 4
UseTab: Never
#NamespaceMacros:
# - DECLARE_TEMPLATED_OPENDAQ_INTERFACE_T
# - DECLARE_TEMPLATED_OPENDAQ_INTERFACE_T_U
# - DECLARE_OPENDAQ_INTERFACE_EX
# - DECLARE_OPENDAQ_INTERFACE
FixNamespaceComments: false
#MacroBlockBegin: "^BEGIN_NAMESPACE_OPENDAQ$"
#MacroBlockEnd: "^END_NAMESPACE_OPENDAQ"
#IndentPPDirectives: BeforeHash
#SeparateDefinitionBlocks: Always
...

15
.editorconfig Normal file
View File

@@ -0,0 +1,15 @@
root = true
[{*.{cpp,h},CMakeLists.txt,*.rtclass,*.cmake,*.json}]
indent_style = space
indent_size = 4
tab_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
[*.yml]
ident_style = space
ident_size = 2
tab_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
* text=auto
*.[tT][xX][tT] text
*.[sS][hH] text eol=lf

46
.gitignore vendored Normal file
View File

@@ -0,0 +1,46 @@
# file types
*.bin
*.bak
*.cache
*.check_cache
*.db
*.dcu
*.depend
*.exp
*.filters
*.idb
*.ilk
*.list
*.log
*.obj
*.orig
*.pdb
*.pyc
*.rule
*.rsm
*.stamp
*.stat
*.suo
*.tlog
*.user
# IDE files
.idea/
.idea_/
.vs/
.vscode/
__history/
__recovery/
__pycache__/
# build and backup folders
/_build*
/build*
/build_win
/cmake-build*
/out*
bckp
# cmake
CMakeUserPresets.json
CMakeSettings.json

60
CMakeLists.txt Normal file
View File

@@ -0,0 +1,60 @@
cmake_minimum_required(VERSION 3.25)
set(REPO_NAME example_fb_module)
set(REPO_OPTION_PREFIX EXAMPLE_MODULE)
project(${REPO_NAME} VERSION 1.0.0)
if (POLICY CMP0135)
cmake_policy(SET CMP0135 NEW)
endif()
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_FB_EXAMPLE_ENABLE_APP "Enable building example function block application" OFF)
option(EXAMPLE_MODULE_ENABLE_TESTS "Enable building of test suite for the example function block module" OFF)
include(CommonUtils)
setup_repo(${REPO_OPTION_PREFIX})
if(OPENDAQ_FB_EXAMPLE_ENABLE_APP)
set(DAQMODULES_REF_FB_MODULE ON CACHE BOOL "" FORCE)
set(DAQMODULES_REF_DEVICE_MODULE ON CACHE BOOL "" FORCE)
endif()
add_subdirectory(external)
add_subdirectory(example_module)
if(OPENDAQ_FB_EXAMPLE_ENABLE_APP)
message(STATUS "Enabled example function block application")
add_subdirectory(example_application)
endif()
# Set CPack variables
set(CPACK_COMPONENTS_ALL RUNTIME)
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
set(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_BINARY_DIR}/package")
# Set the CPack generator based on the platform
if (WIN32)
set(CPACK_GENERATOR "ZIP")
elseif (UNIX AND NOT APPLE)
cmake_host_system_information(RESULT DISTRO_ID QUERY DISTRIB_ID)
cmake_host_system_information(RESULT DISTRO_VERSION_ID QUERY DISTRIB_VERSION_ID)
set(CPACK_SYSTEM_NAME "${DISTRO_ID}${DISTRO_VERSION_ID}")
set(CPACK_GENERATOR "TGZ")
endif()
# Include CPack for packaging
include(CPack)

9
README.md Normal file
View File

@@ -0,0 +1,9 @@
# Example function block module
Simple example that builds an openDAQ module giving access to an example function block. Said function block scales an input signal with a provided scale, and offsets it by a provided offset.
## Testing the module
To test the module, enable the `OPENDAQ_FB_EXAMPLE_ENABLE_APP` cmake flag. Doing so will add an the openDAQ reference device and function block modules to your project. Those are used to create a simulator device via the "daqref://device0" connection string, as well as a renderer via the "RefFBModuleRenderer" function block ID. The main application connects a reference device signal into both the example scaler and renderer. Additionally, it connects the scaler output into the renderer.
To add additional tests, enable the `EXAMPLE_MODULE_ENABLE_TESTS` cmake flag. Doing so will create a new test target configured for use with the GTest framework.

151
cmake/CommonUtils.cmake Normal file
View File

@@ -0,0 +1,151 @@
macro(setup_repo REPO_OPTION_PREFIX)
if (NOT DEFINED PROJECT_SOURCE_DIR)
message(FATAL_ERROR "Must be run inside a project()")
endif()
# Additional build options
option(${REPO_OPTION_PREFIX}_DISABLE_DEBUG_POSTFIX "Disable debug ('-debug') postfix" OFF)
option(${REPO_OPTION_PREFIX}_DEBUG_WARNINGS_AS_ERRORS "Treat debug warnings as errors" OFF)
option(${REPO_OPTION_PREFIX}_ENABLE_TESTS "Enable unit-tests for ${REPO_OPTION_PREFIX}" ON)
get_filename_component(ROOT_DIR ${CMAKE_SOURCE_DIR} REALPATH)
if (NOT ${PROJECT_SOURCE_DIR} STREQUAL ${ROOT_DIR})
set(BUILDING_AS_SUBMODULE ON PARENT_SCOPE)
message(STATUS "Building as submodule")
else()
message(STATUS "Building standalone")
set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER ".CMakePredefinedTargets")
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
message(STATUS "Platform: ${CMAKE_SYSTEM_PROCESSOR} | ${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION}")
message(STATUS "Generator: ${CMAKE_GENERATOR} | ${CMAKE_GENERATOR_PLATFORM}")
if (IS_MULTICONFIG)
message(STATUS "Configuration types:")
block()
list(APPEND CMAKE_MESSAGE_INDENT "\t")
foreach(CONFIG_TYPE ${CMAKE_CONFIGURATION_TYPES})
message(STATUS ${CONFIG_TYPE})
endforeach()
endblock()
else()
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
endif()
string(TIMESTAMP CONFIGURE_DATE)
string(TIMESTAMP CURRENT_YEAR "%Y")
endif()
set(CMAKE_CXX_STANDARD 17)
if (WIN32)
add_compile_definitions(NOMINMAX
_WIN32_WINNT=0x0601 # Windows 7 Compat
)
add_compile_definitions(UNICODE _UNICODE)
endif()
if(NOT CMAKE_DEBUG_POSTFIX AND NOT ${REPO_OPTION_PREFIX}_DISABLE_DEBUG_POSTFIX)
set(CMAKE_DEBUG_POSTFIX -debug)
endif()
if (MSVC)
# As above CMAKE_CXX_STANDARD but for VS
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:/std:c++17>)
foreach (flag IN ITEMS
# Set source and execution character sets to UTF-8
# https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8
/utf-8
# Display level 1, level 2, and level 3 warnings, and all level 4 (informational) warnings that aren't off by default.
# https://learn.microsoft.com/en-us/cpp/build/reference/compiler-option-warning-level
/W4
# data member 'member1' will be initialized after data member 'member2'
# https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/c5038
#/w15038
# Supress warnings
# https://learn.microsoft.com/en-us/cpp/build/reference/compiler-option-warning-level
#
# 'class1' : inherits 'class2::member' via dominance
# https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4250
#/wd4250
# Your code uses a function, class member, variable, or typedef that's marked deprecated.
# https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-3-c4996
/wd4996
# declaration of 'identifier' hides class member
# https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4458
/wd4458
# nonstandard extension used : nameless struct/union
# https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4201
#/wd4201
# unreachable code
# https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4702
#/wd4702
# declaration of 'identifier' hides global declaration
# https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4459
#/wd4459
# 'function' : unreferenced local function has been removed
# https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4505
#/wd4505
# conditional expression is constant
# https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4127
#/wd4127
# assignment within conditional expression
# https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4706
#/wd4706
# loss of data / precision, unsigned <--> signed
#
# 'argument' : conversion from 'type1' to 'type2', possible loss of data
# https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4244
/wd4244
# 'var' : conversion from 'size_t' to 'type', possible loss of data
# https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-3-c4267
#/wd4267
# 'identifier' : unreferenced formal parameter
# https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4100
/wd4100
)
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:${flag}>)
endforeach()
if (NOT OPENDAQ_MSVC_SINGLE_PROCESS_BUILD)
# Build with multiple processes
# https://learn.microsoft.com/en-us/cpp/build/reference/mp-build-with-multiple-processes
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:/MP>)
endif()
# Treat warnings as errors if not Debug or OPENDAQ_DEBUG_WARNINGS_AS_ERRORS is ON
add_compile_options($<$<OR:$<NOT:$<CONFIG:Debug>>,$<BOOL:${${REPO_OPTION_PREFIX}_DEBUG_WARNINGS_AS_ERRORS}>>:/WX>)
add_compile_definitions($<$<CONFIG:Debug>:_DEBUG>)
if (MSVC_VERSION GREATER_EQUAL 1910)
# /Zc:__cplusplus forces MSVC to use the correct value of __cplusplus macro (otherwise always C++98)
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:/Zc:__cplusplus>)
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
# /Zf (Faster PDB generation) is not supported by ClangCL
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Zf")
endif()
# Produce diagnostic messages with exact location
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:/diagnostics:caret>)
endif()
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /ignore:4221")
# set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /ignore:4221")
# set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} /ignore:4221")
endif()
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
if (${REPO_OPTION_PREFIX}_ENABLE_TESTS)
set(OPENDAQ_ENABLE_TEST_UTILS ON CACHE BOOL "Enable testing utils library")
endif()
endmacro()

View File

@@ -0,0 +1,10 @@
add_executable(fb_application_example main.cpp)
target_link_libraries(fb_application_example PRIVATE daq::opendaq)
add_dependencies(
fb_application_example
example_module
daq::ref_device_module
daq::ref_fb_module
)

View File

@@ -0,0 +1,26 @@
/**
* Server application
*/
#include <iostream>
#include <opendaq/opendaq.h>
using namespace daq;
int main(int /*argc*/, const char* /*argv*/[])
{
const auto instance = Instance();
auto referenceDevice = instance.addDevice("daqref://device0");
auto renderer = instance.addFunctionBlock("RefFBModuleRenderer");
auto exampleModule = instance.addFunctionBlock("ExampleScalingModule");
exampleModule.setPropertyValue("Scale", 3);
exampleModule.setPropertyValue("Offset", -2);
exampleModule.getInputPorts()[0].connect(referenceDevice.getSignalsRecursive()[0]);
renderer.getInputPorts()[0].connect(referenceDevice.getSignalsRecursive()[0]);
renderer.getInputPorts()[1].connect(exampleModule.getSignals()[0]);
std::cout << "Press \"enter\" to exit the application..." << std::endl;
std::cin.get();
return 0;
}

View File

@@ -0,0 +1,8 @@
get_current_folder_name(TARGET_FOLDER_NAME)
project(ExampleModule VERSION 1.0.0 LANGUAGES CXX)
add_subdirectory(src)
if (EXAMPLE_MODULE_ENABLE_TESTS)
add_subdirectory(tests)
endif()

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2022-2024 openDAQ d.o.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <coretypes/common.h>
#define BEGIN_NAMESPACE_EXAMPLE_MODULE BEGIN_NAMESPACE_OPENDAQ_MODULE(example_module)
static const std::string EXAMPLE_MODULE_NAME = "ExampleModule";
#define END_NAMESPACE_EXAMPLE_MODULE END_NAMESPACE_OPENDAQ_MODULE

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2022-2024 openDAQ d.o.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <opendaq/sample_type.h>
#define SAMPLE_TYPE_DISPATCH(ST, Func, ...) \
switch (ST) \
{ \
case SampleType::Int8: \
Func<SampleType::Int8>(__VA_ARGS__); \
break; \
case SampleType::Int16: \
Func<SampleType::Int16>(__VA_ARGS__); \
break; \
case SampleType::Int32: \
Func<SampleType::Int32>(__VA_ARGS__); \
break; \
case SampleType::Int64: \
Func<SampleType::Int64>(__VA_ARGS__); \
break; \
case SampleType::UInt8: \
Func<SampleType::UInt8>(__VA_ARGS__); \
break; \
case SampleType::UInt16: \
Func<SampleType::UInt16>(__VA_ARGS__); \
break; \
case SampleType::UInt32: \
Func<SampleType::UInt32>(__VA_ARGS__); \
break; \
case SampleType::UInt64: \
Func<SampleType::UInt64>(__VA_ARGS__); \
break; \
case SampleType::Float32: \
Func<SampleType::Float32>(__VA_ARGS__); \
break; \
case SampleType::Float64: \
Func<SampleType::Float64>(__VA_ARGS__); \
break; \
default: \
assert(false); \
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2022-2024 openDAQ d.o.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <example_module/common.h>
#include <opendaq/function_block_impl.h>
#include <opendaq/opendaq.h>
#include <opendaq/stream_reader_ptr.h>
BEGIN_NAMESPACE_EXAMPLE_MODULE
class ExampleFBImpl final : public FunctionBlock
{
public:
explicit ExampleFBImpl(const ContextPtr& ctx, const ComponentPtr& parent, const StringPtr& localId);
~ExampleFBImpl() override = default;
static FunctionBlockTypePtr CreateType();
private:
InputPortPtr inputPort;
DataDescriptorPtr inputDataDescriptor;
DataDescriptorPtr inputDomainDataDescriptor;
DataDescriptorPtr outputDataDescriptor;
DataDescriptorPtr outputDomainDataDescriptor;
SampleType inputSampleType;
SignalConfigPtr outputSignal;
SignalConfigPtr outputDomainSignal;
StreamReaderPtr reader;
SizeT sampleRate;
std::vector<float> inputData;
std::vector<uint64_t> inputDomainData;
bool configValid = false;
Float scale;
Float offset;
Float outputHighValue;
Float outputLowValue;
Bool useCustomOutputRange;
std::string outputUnit;
std::string outputName;
void createInputPorts();
void createSignals();
void calculate();
void processData(SizeT readAmount, SizeT packetOffset) const;
void processEventPacket(const EventPacketPtr& packet);
void processSignalDescriptorChanged(const DataDescriptorPtr& dataDescriptor,
const DataDescriptorPtr& domainDescriptor);
void configure();
void initProperties();
void propertyChanged(bool configure);
void readProperties();
};
END_NAMESPACE_EXAMPLE_MODULE

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2022-2024 openDAQ d.o.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <example_module/common.h>
#include <opendaq/module_impl.h>
BEGIN_NAMESPACE_EXAMPLE_MODULE
class ExampleModule final : public Module
{
public:
explicit ExampleModule(ContextPtr context);
DictPtr<IString, IFunctionBlockType> onGetAvailableFunctionBlockTypes() override;
FunctionBlockPtr onCreateFunctionBlock(const StringPtr& id, const ComponentPtr& parent, const StringPtr& localId, const PropertyObjectPtr& config) override;
};
END_NAMESPACE_EXAMPLE_MODULE

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2022-2024 openDAQ d.o.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <opendaq/module_exports.h>
DECLARE_MODULE_EXPORTS(ExampleModule)

View File

@@ -0,0 +1,45 @@
set(LIB_NAME example_module)
set(MODULE_HEADERS_DIR ../include/${TARGET_FOLDER_NAME})
set(SRC_Include common.h
module_dll.h
example_module.h
example_fb.h
)
set(SRC_Srcs module_dll.cpp
example_module.cpp
example_fb.cpp
)
prepend_include(${TARGET_FOLDER_NAME} SRC_Include)
source_group("module" FILES ${MODULE_HEADERS_DIR}/example_module.h
${MODULE_HEADERS_DIR}/example_fb.h
${MODULE_HEADERS_DIR}/module_dll.h
module_dll.cpp
example_module.cpp
example_fb.cpp
)
add_library(${LIB_NAME} SHARED ${SRC_Include}
${SRC_Srcs}
)
add_library(${SDK_TARGET_NAMESPACE}::${LIB_NAME} ALIAS ${LIB_NAME})
if (MSVC)
target_compile_options(${LIB_NAME} PRIVATE /bigobj)
endif()
target_link_libraries(${LIB_NAME} PUBLIC daq::opendaq
)
target_include_directories(${LIB_NAME} PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/../include>
$<INSTALL_INTERFACE:include>
)
opendaq_set_module_properties(${LIB_NAME} ${PROJECT_VERSION_MAJOR})
create_version_header(${LIB_NAME})

View File

@@ -0,0 +1,251 @@
#include <example_module/example_fb.h>
#include <example_module/dispatch.h>
#include <opendaq/event_packet_params.h>
BEGIN_NAMESPACE_EXAMPLE_MODULE
ExampleFBImpl::ExampleFBImpl(const ContextPtr& ctx, const ComponentPtr& parent, const StringPtr& localId)
: FunctionBlock(CreateType(), ctx, parent, localId)
{
initComponentStatus();
createInputPorts();
createSignals();
initProperties();
}
void ExampleFBImpl::initProperties()
{
const auto scaleProp = FloatProperty("Scale", 1.0);
objPtr.addProperty(scaleProp);
objPtr.getOnPropertyValueWrite("Scale") +=
[this](PropertyObjectPtr& obj, PropertyValueEventArgsPtr& args) { propertyChanged(true); };
const auto offsetProp = FloatProperty("Offset", 0.0);
objPtr.addProperty(offsetProp);
objPtr.getOnPropertyValueWrite("Offset") +=
[this](PropertyObjectPtr& obj, PropertyValueEventArgsPtr& args) { propertyChanged(true); };
const auto useCustomOutputRangeProp = BoolProperty("UseCustomOutputRange", False);
objPtr.addProperty(useCustomOutputRangeProp);
objPtr.getOnPropertyValueWrite("UseCustomOutputRange") +=
[this](PropertyObjectPtr& obj, PropertyValueEventArgsPtr& args) { propertyChanged(true); };
const auto customHighValueProp = FloatProperty("OutputHighValue", 10.0, EvalValue("$UseCustomOutputRange"));
objPtr.addProperty(customHighValueProp);
objPtr.getOnPropertyValueWrite("OutputHighValue") +=
[this](PropertyObjectPtr& obj, PropertyValueEventArgsPtr& args) { propertyChanged(true); };
const auto customLowValueProp = FloatProperty("OutputLowValue", -10.0, EvalValue("$UseCustomOutputRange"));
objPtr.addProperty(customLowValueProp);
objPtr.getOnPropertyValueWrite("OutputLowValue") +=
[this](PropertyObjectPtr& obj, PropertyValueEventArgsPtr& args) { propertyChanged(true); };
const auto outputNameProp = StringProperty("OutputName", "");
objPtr.addProperty(outputNameProp);
objPtr.getOnPropertyValueWrite("OutputName") +=
[this](PropertyObjectPtr& obj, PropertyValueEventArgsPtr& args) { propertyChanged(true); };
const auto outputUnitProp = StringProperty("OutputUnit", "");
objPtr.addProperty(outputUnitProp);
objPtr.getOnPropertyValueWrite("OutputUnit") +=
[this](PropertyObjectPtr& obj, PropertyValueEventArgsPtr& args) { propertyChanged(true); };
readProperties();
}
void ExampleFBImpl::propertyChanged(bool configure)
{
readProperties();
if (configure)
this->configure();
}
void ExampleFBImpl::readProperties()
{
scale = objPtr.getPropertyValue("Scale");
offset = objPtr.getPropertyValue("Offset");
useCustomOutputRange = objPtr.getPropertyValue("UseCustomOutputRange");
outputHighValue = objPtr.getPropertyValue("OutputHighValue");
outputLowValue = objPtr.getPropertyValue("OutputLowValue");
outputUnit = static_cast<std::string>(objPtr.getPropertyValue("OutputUnit"));
outputName = static_cast<std::string>(objPtr.getPropertyValue("OutputName"));
}
FunctionBlockTypePtr ExampleFBImpl::CreateType()
{
return FunctionBlockType("ExampleScalingModule", "Scaling", "Signal scaling");
}
void ExampleFBImpl::processSignalDescriptorChanged(const DataDescriptorPtr& dataDescriptor,
const DataDescriptorPtr& domainDescriptor)
{
if (dataDescriptor.assigned())
this->inputDataDescriptor = dataDescriptor;
if (domainDescriptor.assigned())
this->inputDomainDataDescriptor = domainDescriptor;
configure();
}
void ExampleFBImpl::configure()
{
try
{
if (!inputDomainDataDescriptor.assigned() || inputDomainDataDescriptor == NullDataDescriptor())
{
throw std::runtime_error("No domain input");
}
if (!inputDataDescriptor.assigned() || inputDataDescriptor == NullDataDescriptor())
{
throw std::runtime_error("No value input");
}
if (inputDataDescriptor.getDimensions().getCount() > 0)
{
throw std::runtime_error("Arrays not supported");
}
inputSampleType = inputDataDescriptor.getSampleType();
if (inputSampleType != SampleType::Float64 &&
inputSampleType != SampleType::Float32 &&
inputSampleType != SampleType::Int8 &&
inputSampleType != SampleType::Int16 &&
inputSampleType != SampleType::Int32 &&
inputSampleType != SampleType::Int64 &&
inputSampleType != SampleType::UInt8 &&
inputSampleType != SampleType::UInt16 &&
inputSampleType != SampleType::UInt32 &&
inputSampleType != SampleType::UInt64)
{
throw std::runtime_error("Invalid sample type");
}
// Accept only synchronous (linear implicit) domain signals
if (inputDomainDataDescriptor.getSampleType() != SampleType::Int64 && inputDomainDataDescriptor.getSampleType() != SampleType::UInt64)
{
throw std::runtime_error("Incompatible domain data sample type");
}
const auto domainUnit = inputDomainDataDescriptor.getUnit();
if (domainUnit.getSymbol() != "s" && domainUnit.getSymbol() != "seconds")
{
throw std::runtime_error("Domain unit expected in seconds");
}
const auto domainRule = inputDomainDataDescriptor.getRule();
if (inputDomainDataDescriptor.getRule().getType() != DataRuleType::Linear)
{
throw std::runtime_error("Domain rule must be linear");
}
RangePtr outputRange;
if (useCustomOutputRange)
{
outputRange = Range(outputLowValue, outputHighValue);
}
else
{
auto outputHigh = scale * static_cast<Float>(inputDataDescriptor.getValueRange().getLowValue()) + offset;
auto outputLow = scale * static_cast<Float>(inputDataDescriptor.getValueRange().getHighValue()) + offset;
if (outputLow > outputHigh)
std::swap(outputLow, outputHigh);
outputRange = Range(outputLow, outputHigh);
}
auto name = outputName.empty() ? inputPort.getSignal().getName().toStdString() + "/Scaled" : outputName;
auto unit = outputUnit.empty() ? inputDataDescriptor.getUnit() : Unit(outputUnit);
outputDataDescriptor = DataDescriptorBuilder()
.setSampleType(SampleType::Float64)
.setValueRange(outputRange)
.setUnit(unit)
.build();
outputDomainDataDescriptor = inputDomainDataDescriptor;
outputSignal.setDescriptor(outputDataDescriptor);
outputSignal.setName(name);
outputDomainSignal.setDescriptor(inputDomainDataDescriptor);
// Allocate 1s buffer
sampleRate = reader::getSampleRate(inputDomainDataDescriptor);
inputData.resize(sampleRate);
inputDomainData.resize(sampleRate);
setComponentStatus(ComponentStatus::Ok);
}
catch (const std::exception& e)
{
setComponentStatusWithMessage(ComponentStatus::Error, fmt::format("Failed to set descriptor for output signal: {}", e.what()));
outputSignal.setDescriptor(nullptr);
configValid = false;
}
configValid = true;
}
void ExampleFBImpl::calculate()
{
auto lock = this->getAcquisitionLock();
while (!reader.getEmpty())
{
SizeT readAmount = std::min(reader.getAvailableCount(), sampleRate);
const auto status = reader.readWithDomain(inputData.data(), inputDomainData.data(), &readAmount);
if (configValid)
{
processData(readAmount, status.getOffset());
}
if (status.getReadStatus() == ReadStatus::Event)
{
const auto eventPacket = status.getEventPacket();
if (eventPacket.assigned())
processEventPacket(eventPacket);
return;
}
}
}
void ExampleFBImpl::processData(SizeT readAmount, SizeT packetOffset) const
{
if (readAmount == 0)
return;
const auto outputDomainPacket = DataPacket(outputDomainDataDescriptor, readAmount, packetOffset);
const auto outputPacket = DataPacketWithDomain(outputDomainPacket, outputDataDescriptor, readAmount);
auto outputData = static_cast<Float*>(outputPacket.getRawData());
for (size_t i = 0; i < readAmount; i++)
*outputData++ = scale * static_cast<Float>(inputData[i]) + offset;
outputSignal.sendPacket(outputPacket);
outputDomainSignal.sendPacket(outputDomainPacket);
}
void ExampleFBImpl::processEventPacket(const EventPacketPtr& packet)
{
if (packet.getEventId() == event_packet_id::DATA_DESCRIPTOR_CHANGED)
{
DataDescriptorPtr dataDesc = packet.getParameters().get(event_packet_param::DATA_DESCRIPTOR);
DataDescriptorPtr domainDesc = packet.getParameters().get(event_packet_param::DOMAIN_DATA_DESCRIPTOR);
processSignalDescriptorChanged(dataDesc, domainDesc);
}
}
void ExampleFBImpl::createInputPorts()
{
inputPort = createAndAddInputPort("Input", PacketReadyNotification::Scheduler);
reader = StreamReaderFromPort(inputPort, SampleType::Float32, SampleType::UInt64);
reader.setOnDataAvailable([this] { calculate();});
}
void ExampleFBImpl::createSignals()
{
outputSignal = createAndAddSignal("Scaled");
outputDomainSignal = createAndAddSignal("ScaledTime", nullptr, false);
outputSignal.setDomainSignal(outputDomainSignal);
}
END_NAMESPACE_EXAMPLE_MODULE

View File

@@ -0,0 +1,42 @@
#include <coretypes/version_info_factory.h>
#include <opendaq/custom_log.h>
#include <example_module/example_fb.h>
#include <example_module/example_module.h>
#include <example_module/version.h>
BEGIN_NAMESPACE_EXAMPLE_MODULE
ExampleModule::ExampleModule(ContextPtr context)
: Module("ReferenceFunctionBlockModule",
VersionInfo(EXAMPLE_MODULE_MAJOR_VERSION, EXAMPLE_MODULE_MINOR_VERSION, EXAMPLE_MODULE_PATCH_VERSION),
std::move(context),
"ReferenceFunctionBlockModule")
{
}
DictPtr<IString, IFunctionBlockType> ExampleModule::onGetAvailableFunctionBlockTypes()
{
auto types = Dict<IString, IFunctionBlockType>();
const auto typeScaling = ExampleFBImpl::CreateType();
types.set(typeScaling.getId(), typeScaling);
return types;
}
FunctionBlockPtr ExampleModule::onCreateFunctionBlock(const StringPtr& id,
const ComponentPtr& parent,
const StringPtr& localId,
const PropertyObjectPtr& config)
{
if (id == ExampleFBImpl::CreateType().getId())
{
FunctionBlockPtr fb = createWithImplementation<IFunctionBlock, ExampleFBImpl>(context, parent, localId);
return fb;
}
LOG_W("Function block \"{}\" not found", id);
throw NotFoundException("Function block not found");
}
END_NAMESPACE_EXAMPLE_MODULE

View File

@@ -0,0 +1,9 @@
#include <example_module/module_dll.h>
#include <example_module/example_module.h>
#include <opendaq/module_factory.h>
using namespace daq::modules::example_module;
DEFINE_MODULE_EXPORTS(ExampleModule)

View File

@@ -0,0 +1,18 @@
set(MODULE_NAME example_module)
set(TEST_APP test_${MODULE_NAME})
set(TEST_SOURCES test_example_module.cpp
test_app.cpp
)
add_executable(${TEST_APP} ${TEST_SOURCES}
)
target_link_libraries(${TEST_APP} PRIVATE daq::test_utils
${SDK_TARGET_NAMESPACE}::${MODULE_NAME}
)
add_test(NAME ${TEST_APP}
COMMAND $<TARGET_FILE_NAME:${TEST_APP}>
WORKING_DIRECTORY bin
)

View File

@@ -0,0 +1,21 @@
#include <testutils/testutils.h>
#include <testutils/bb_memcheck_listener.h>
#include <coreobjects/util.h>
#include <opendaq/module_manager_init.h>
#include <coretypes/stringobject_factory.h>
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;
}

View File

@@ -0,0 +1,77 @@
#include <gmock/gmock.h>
#include <testutils/testutils.h>
#include <opendaq/opendaq.h>
#include <opendaq/data_descriptor_factory.h>
#include <thread>
using namespace daq;
using ExampleModuleTest = testing::Test;
TEST_F(ExampleModuleTest, TestAdd)
{
const auto instance = Instance();
ASSERT_TRUE(instance.addFunctionBlock("ExampleScalingModule").assigned());
}
TEST_F(ExampleModuleTest, TestPropCount)
{
const auto instance = Instance();
auto fb = instance.addFunctionBlock("ExampleScalingModule");
ASSERT_EQ(fb.getAllProperties().getCount(), 7);
}
TEST_F(ExampleModuleTest, TestDataScaling)
{
const auto instance = Instance();
auto fb = instance.addFunctionBlock("ExampleScalingModule");
fb.setPropertyValue("Scale", 2);
auto dataDescriptor = DataDescriptorBuilder().setSampleType(SampleType::Float32).setValueRange(Range(-10, 10)).build();
auto signal = SignalWithDescriptor(instance.getContext(), dataDescriptor, nullptr, "Data");
const auto domainDescriptor = DataDescriptorBuilder()
.setSampleType(SampleType::Int64)
.setUnit(Unit("s", -1, "seconds", "time"))
.setTickResolution(Ratio(1, 1000))
.setRule(LinearDataRule(1, 0))
.setOrigin("1970-01-01T01:00:00+00:00")
.build();
auto domainSignal = SignalWithDescriptor(instance.getContext(), domainDescriptor, nullptr, "DomainData");
signal.setDomainSignal(domainSignal);
fb.getInputPorts()[0].connect(signal);
auto streamReader = StreamReader(fb.getSignals()[0], SampleType::Float32, SampleType::Int64);
auto domainPacket = DataPacket(domainDescriptor, 10, 0);
auto packet = DataPacketWithDomain(domainPacket, dataDescriptor, 10);
float* data = static_cast<float*>(packet.getRawData());
for (auto i = 0; i < 10; i++)
data[i] = static_cast<float>(i);
signal.sendPacket(packet);
domainSignal.sendPacket(domainPacket);
SizeT count = 10;
std::vector<float> readData;
readData.resize(count);
auto status = streamReader.read(readData.data(), &count);
ASSERT_EQ(status.getReadStatus(), ReadStatus::Event);
ASSERT_EQ(count, 0);
while (count < 10)
{
using namespace std::chrono_literals;
count = streamReader.getAvailableCount();
std::this_thread::sleep_for(100ms);
}
ASSERT_EQ(count, 10);
status = streamReader.read(readData.data(), &count);
ASSERT_EQ(count, 10);
ASSERT_EQ(status.getReadStatus(), ReadStatus::Ok);
for (int i = 0; i < 10; i++)
ASSERT_EQ(static_cast<int>(readData[i]), i * 2);
}

10
external/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,10 @@
set(CMAKE_FOLDER external)
list(APPEND CMAKE_MESSAGE_CONTEXT external)
if (${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
message(FATAL_ERROR "In-source build is not supported!")
endif()
include(FetchContent)
add_subdirectory(openDAQ)

12
external/openDAQ/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,12 @@
set(OPENDAQ_ENABLE_TESTS false)
FetchContent_Declare(
openDAQ
GIT_REPOSITORY https://github.com/openDAQ/openDAQ.git
GIT_TAG origin/main
GIT_PROGRESS ON
SYSTEM
FIND_PACKAGE_ARGS 3.11.0 GLOBAL
)
FetchContent_MakeAvailable(openDAQ)