Components
Components represent all data acquisition objects that define the tree structure of an openDAQ™ system. Generally, they represent objects that represent physical device components (eg. Device, Channel), or data processing objects that form the Data path (eg. Function blocks, Signals, or Input Ports).
Each Component inherits all capabilities of a Property Object and uses its features to allow for the definition of user-customizable parameters such as a Device’s sample rate, or the number of averaged samples in an Averaging Function block.
The Component differentiates itself from base Property Objects in that it has a Local and Global ID in an openDAQ™ system and thus is a building block of the system’s structure. When viewed from the perspective of a user interface, Components would show up as a tree-view that represents the structure, while Properties (including Object-type properties that represent base Property Objects), appear as customizable properties to the user in a separate part of the user interface.
Component types
The following components are currently available in the openDAQ™ SDK. The table below outlines their purpose in a brief explanation, and provides links to articles describing each component in detail.
Component |
Description |
Component |
A base Component that describes any custom Component of a Device. |
A base Component that can have 0 or more child Components. |
|
A base Component that can have 0 or more IO (input-output) Components. IO Folders and Channels are IO Components. |
|
Representation of a physical, or virtual data acquisition Device that acts as a container for Channels, Function Blocks, Signals, and other Devices |
|
Representation of physical hardware input, or output Channel. Examples of Channels are analog or digital inputs or outputs of DAQ hardware. |
|
Signals carry data streams in the form of packets through openDAQ™ systems. They are used to form data paths from data producers (eg. Channels) to consumers (eg. Function Blocks with Input Ports). |
|
Data processing Component of the SDK. Function blocks can have 0 or more Input Ports through which they acquire data and 0 or more Output Signals through which they send data to any data consumer. Note that a Function Block can have multiple modes of operation that do not require any Input Ports or Output Signals (for example, a File writer that writes acquired data from an Input Port to a file, but does not produce Output Signals). |
|
Port object to which a Signal is connected, forming a Connection between them. When a Signal is connected to an Input Port the Input Port acts as a consumer and will receive Packets sent by the signal. |
Component IDs
Each Component in the openDAQ™ tree has a unique Global ID. The Global ID is formed using the hierarchical structure of Components, with it being formed with the Local IDs of the Component’s ancestors separated by forward slashes ("/"). A Device with a Local ID "DAQDevice" that has a Channel named "CH1" with Output Signals "Scaled" and "Raw" would have the following components:
Local ID |
Global ID |
DAQDevice |
DAQDevice |
CH1 |
DAQDevice/IO/CH1 |
Scaled |
DAQDevice/IO/CH1/Scaled |
Raw |
DAQDevice/IO/CH1/Raw |
Of note are the IO
components that separate the Device and Channel Components. These represent the Device’s IO Folder
that is automatically created by the openDAQ™ Device in addition to the FB
(Function Blocks) and Sig
(Signals)
folders.
Component statuses
Each component within the openDAQ™ tree may have one or more implementation-specific predefined statuses. Each status is uniquely named within the component’s scope and is assigned an openDAQ™ enumeration type value. An example of such a status is the "InputStatus" of the Scaling Function Block:
Status value |
Description |
"Disconnected" |
No Signal is connected to the Input Port of the Function block |
"Invalid" |
A Signal is connected to the Input Port of Function block, but scaling is not possible because its DataDescriptor does not fulfill the scaling conditions, or the Domain Signal is not assigned |
"Connected" |
The connected signal is valid and can be scaled |
This example code snippet showcases the addition of the Scaling Function Block and the printing of all its statuses names along with the current value of "InputStatus":
-
Cpp
const auto scalingFb = instance.addFunctionBlock("ref_fb_module_scaling");
std::cout << "The Scaling Function Block available statuses: ";
for (const auto& [name, _] : scalingFb.getStatusContainer().getStatuses())
std::cout << "\"" << name << "\"; ";
std::cout << std::endl
<< "The Scaling Function Block \"InputStatus\" is "
<< scalingFb.getStatusContainer().getStatus("InputStatus")
<< std::endl;
Folders
Folders are standard Components that can also have child Components. A Device for example, is a Folder,
as is a Function Block. Folders are used to define the structure of an openDAQ™ system.
Organization of standardized Folders such as Function Blocks and Devices, as well as standard Device folders (IO
, Sig
, Fb
, Dev
)
is done automatically when such Components are added in an openDAQ™ system.
Non-standardized Folder structures can be designed by Devices with custom Components/Folders, as well as Function Blocks with nested Function Block structures. These are explored in detail in their respective articles.
Traversing Folders
Folders such as Devices and Function Blocks offer methods to simply navigate through their child Components. Devices provide getters for their Channel/Function Block/Signal/Device child Components, while Function Block provide similar getter methods for their Output Signals and nested Function Blocks.
When working with a custom Folder implementation, however, a generic getter method is available that returns all child Components. Folders, unless specialized (eg. IO Folders), should only have base Folders or base Components as children. A base Folder should not have Devices, Function Blocks, Signals, or other DAQ Components as child Components.
Each specialized, or generic child get
call has an optional filter parameter, allowing for customizing your search query.
These search filters allow for filtering out unwanted components from the results list, as well as enabling recursion.
By default, the getters will not search recursively, and will only return children with visible
set to true
.
Example code snippet that recursively finds all signals in a given device and prints their global IDs:
-
Cpp
-
Python
using namespace daq::search;
void findSignals(const DevicePtr& device)
{
for (const auto& signal : device.getSignals(Recursive(Any)))
std::cout << signal.getGlobalId() << std::endl;
}
def find_signals(device: opendaq.IDevice):
search_filter = opendaq.RecursiveSearchFilter(opendaq.AnySearchFilter())
for signal in device.get_signals(search_filter):
print(signal.global_id)
IO Folders
Folders can have specializations where only specific child Components can be added as children. Currently openDAQ™ contains only the IO Folder specialization. IO Folders can only have other IO Folders or Channels as children. This allows Devices to organize their channels below the IO Folder in a tree structure but prevents them from placing non-Channel Components into the folder.
Each Device always has an IO folder as its child, and all its Channels should be children of the IO Folder.
|