Device

A Device in the openDAQ™ SDK is a Folder that acts as a standardized container of other openDAQ™ Components such as Signals, Function blocks, Channels, and other Devices. Within the openDAQ™ tree-structure architecture, the root node of the structure is always a Device - below it, all data processing structures visible to that Device are organized.

For example, a Device can be an abstraction/representation of a physical hardware unit with a set of Properties and other Components such as Channels that map to parts of the Device (eg. analog/digital inputs or outputs). Such a Device will perform physical measurements and translate it to the openDAQ™ ecosystem, while sometimes also performing signal processing such as filtering and scaling.

The above Devices will most often host servers that allow for configuration of the Device, as well as signal data transfer. To collect and analyze the data, a client-side Device is often created. Such a Device does not represent physical hardware but instead acts as a virtual client Device that connects to openDAQ™ devices, collecting and processing their data via Function blocks.

device_simple
Figure 1. Simple architecture consisting of a Physical device hosting a server and a Virtual device with a client

This example of Physical hardware hosting a server, and a virtual client that collects data is the most common example of a data acquisition system in openDAQ™. However, the SDK allows for much more than that. Any Device can act as a server, client, or both and any Device can have a set of Function blocks that act as signal processing units. This allows for an arbitrary organization of Device clusters, onboard processing, and distributed data processing on multiple clients. The image below illustrates a sample layout made possible by openDAQ™.

device_advanced
Figure 2. Device layout with 5 Physical DAQ Devices with Analog Inputs (AI), 2 loggers with AIs, distributed statistics calculations on two Calculation Devices, and a final Client Device performing FFT and Power Analysis

Having explored some examples of what a Device represents, the remainder of this section aims to provide an understanding of how a Device is used, and how to configure systems similar to the ones shown above.

Device Components

A Device on its own is a simple container of Signals, Channels, Function Blocks, and other Devices. It mandates a specific organization of the above Components into pre-created folders:

  • io: Inputs-Outputs Folder that can contain other IO Folders or Channnels

  • sig: Contains global Device Signals that are not part of its other Components (Function Blocks, Channels, etc.)

  • fb: Contains the Device’s Function blocks

  • dev: Contains sub-devices of the Device

Devices often provide a set of pre-configured Components and some additional configurable parts such as dynamically added Function blocks. The section on [Configuring a Device] contains more information about customizing device Components.

Additionally, a Device can have other Sub-Components, that are not standardized in the aforementioned folders. Those are accessible through the standard Folder navigation methods or the dedicated Device custom Component getter function.

The example below illustrates how to access different Components of a Device.

  • Cpp

  • Python

// Assumes a DevicePtr object is available in the `device` variable

// Gets the Device's function blocks
ListPtr<IFunctionBlock> functionBlocks = device.getFunctionBlocks();
// Gets the Device's Signals
ListPtr<ISignal> signals = device.getSignals();
// Gets all Sub-Devices
ListPtr<IDevice> devices = device.getDevices();

// Gets the `io` folder.
FolderPtr inputsOutputsFolder = device.getInputsOutputsFolder();
// Shortcut to obtaining a list of channels that are the leaf Components of the `io` Folder
ListPtr<IChannel> channels =  device.getChannels();

// Gets all custom Components of the Device
ListPtr<IComponent> customComponents = device.getCustomComponents();
def access_device_components(device: opendaq.IDevice):
    functionBlocks = device.function_blocks
    signals = device.signals
    sub_devices = device.devices
    io_folder = device.inputs_outputs_folder
    channels = device.channels
    custom_components = device.custom_components

Device Properties

Similar to other openDAQ components, Devices can have an arbitrary amount of custom Properties. Any Device integration can specify its own set of Properties that allow users to customize the behaviour of a device.

By default, each Device has two Properties: "UserName" and "Location".

Sub-Devices

Among the Device Components, Sub-Devices warrant additional explanation. In this section we make the distinction between local and remote devices:

  • Local: Device is added via a Device Module that does not connect to another instance of openDAQ™. It does not use a client to mirror an existing Device as a copy of a Device as a sub-device, but instead interacts directly with physical hardware (or a Device simulator created in the local openDAQ™ instance).

  • Remote: Device that is running its own openDAQ™ instance with a structure (eg. OPC UA) or streaming (eg. WebSocket) server. Connecting to a remote Device creates a mirror of the Device as a Sub-Device with often limited capabilities.

Local

As mentioned above, local Devices are usually physical hardware running openDAQ™ with a Device Module that translates the hardware Components/data into the openDAQ™ ecosystem. Such Devices provide a set of Properties and Function blocks that can be added dynamically. When done so, the Properties are written to the device/its Components, and the Function blocks perform all calculations locally, using the local processing capabilities of the device.

Remote

Remote Devices are those that are running their own openDAQ™ instance with a structure (eg. OPC UA) or streaming (eg. WebSocket) server. When we connect to such devices from another instance, they are considered remote.

openDAQ™ compatible devices can be integrated at the protocol level, where they do not contain an instance of the SDK, but instead only adhere to the OPC UA and Streaming protocols that openDAQ™ is compliant with.

When connecting to a remote Device its structure is mirrored in the Sub-Device visible in the dev Folder. All Components of the remote Device are dummy versions that allow for the configuration of their Properties but do not do actual data processing. A Function block of a remote Device will be a simple container of Properties, which, when modified, will also be modified on the remote Device as well.

An exception to the above are Signals, which receive data from the remote Device via a selected streaming protocol. The data sent to the signal is also sent to all connected clients.

In general, a remote Device will appear to behave in the exact same manner as a local device when inspected/modified. Its Properties can be inspected/changed, Function blocks can be added/removed, and signals connected into its Input Ports.

There are currently, however, limitations in the SDK, where not all of the above features are available on remote Devices.

Current limitations

  • Any function calls that modify the Device’s structure are not available. This includes but is not limited to

    • Adding/Removing Function blocks

    • Adding/Removing Sub-Devices

    • Connecting signals to Input Ports

    • Adding new Properties to Components

  • Calls that require the remote Device to obtain information about its clients

    • Eg. Connecting a signal of the client into an input port of the remote Device

  • Remote Device servers do not notify clients of changes. The clients need to re-read all Properties be able to see their current state.

Device Information

Each openDAQ™ device has a set of standardized information fields. While not mandatory, those fields are recommended to be made available by each Device in addition to any custom Properties the users can inspect/configure. These fields include information such as the Device’s Serial number, Name, Model, and others.

The standardized information is available through the Device Info object directly on the Device itself, or through Device discovery when listing Devices that are available (Devices that the SDK is able to connect to/add).

  • Cpp

  • Python

// Gets the Device information and prints its serial number
DeviceInfoPtr info = device.getInfo();
std::cout << info.getSerialNumber() << std::endl;

// Prints the name of the first available device
DeviceInfoPtr availableDeviceInfo = device.getAvailableDevices().getValueList()[0];
std::cout << availableDeviceInfo.getName() << std::endl;
def device_info(device: opendaq.IDevice):
    # Gets the Device information and prints its serial number
    info = device.info
    print(info.serial_number)
# Prints the name of the first available device
    available_device_info = device.available_devices.values[0]
    print(available_device_info.name)
The Device Info objects can contain additional, non-standardized fields. The objects themselves are Property Objects and can be parsed to obtain a full list of Properties defined by the Device Info.

Instance and the Root Device

We’ve previously mentioned that Devices always form the root node of the openDAQ™ tree of Components. Additionally, we often use the term "openDAQ™ Instance" to indicate that the openDAQ™ SDK is running on a physical/virtual device. The terms Device and Instance are often mixed up/misused, and can be misleading when used in an incorrect context.

Whenever we create a new openDAQ™ application, we first create an Instance. The Instance is our entry point into the SDK. It creates back-end objects responsible for logging, scheduling tasks, loading Modules, and other features that are required for the functioning of the SDK. Additionally, it creates the root node of the tree of Components.

As mentioned before - the root node is a Device. By default, the Instance creates a virtual Device that simply acts as a way of accessing Modules, allowing for connecting to Devices and processing their data through the use of Function blocks. This root node Device is also called the Root Device. The Instance object itself has access to all functions that a Device has, but it forwards all Device function calls to the Root device instead.

  • Cpp

  • Python

InstancePtr instance = Instance();

// The below two lines are equivalent.
auto availableDevices1 = instance.getAvailableDevices();
auto availableDevices2 = instance.getRootDevice().getAvailableDevices();
def instance_and_root():
    instance = opendaq.Instance()

# The below two lines are equivalent.
    available_devices1 = instance.available_devices
    available_devices2 = instance.root_device.available_devices

When we want to avoid having the default virtual Device as the Root node, we can set a different Device as the root, resulting in said Device appearing at the top of the openDAQ™ Component tree.

Working with Devices

For information on connecting to Devices, see the How To Connect to a Device guide.