Initial commit
This commit is contained in:
8
example_module/CMakeLists.txt
Normal file
8
example_module/CMakeLists.txt
Normal 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()
|
||||
24
example_module/include/example_module/common.h
Normal file
24
example_module/include/example_module/common.h
Normal 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
|
||||
56
example_module/include/example_module/dispatch.h
Normal file
56
example_module/include/example_module/dispatch.h
Normal 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); \
|
||||
}
|
||||
77
example_module/include/example_module/example_fb.h
Normal file
77
example_module/include/example_module/example_fb.h
Normal 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
|
||||
32
example_module/include/example_module/example_module.h
Normal file
32
example_module/include/example_module/example_module.h
Normal 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
|
||||
20
example_module/include/example_module/module_dll.h
Normal file
20
example_module/include/example_module/module_dll.h
Normal 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)
|
||||
45
example_module/src/CMakeLists.txt
Normal file
45
example_module/src/CMakeLists.txt
Normal 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})
|
||||
251
example_module/src/example_fb.cpp
Normal file
251
example_module/src/example_fb.cpp
Normal 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
|
||||
42
example_module/src/example_module.cpp
Normal file
42
example_module/src/example_module.cpp
Normal 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
|
||||
9
example_module/src/module_dll.cpp
Normal file
9
example_module/src/module_dll.cpp
Normal 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)
|
||||
|
||||
18
example_module/tests/CMakeLists.txt
Normal file
18
example_module/tests/CMakeLists.txt
Normal 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
|
||||
)
|
||||
21
example_module/tests/test_app.cpp
Normal file
21
example_module/tests/test_app.cpp
Normal 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;
|
||||
}
|
||||
77
example_module/tests/test_example_module.cpp
Normal file
77
example_module/tests/test_example_module.cpp
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user