Examples¶
Example: Sync File Replay¶
This example channel reads floating point values from the first column of a CSV file and writes them to a synchronous output channel. Output frequency can be configured using the Sample rate setting. Playback loops when the end of the file is reached.
Features¶
Register a new Software Channel Type in Oxygen
Plugin channel provides editable filename (string) and sample rate (scalar) config items
Custom config item provides an english display name
Validate input file and update channel state accordingly
Set maximum range of output channel sample values
Write samples to a synchronous scalar output channel
UI extension to configure replay file
Custom request to demonstrate communication between UI and plugin
Location: examples/replay_sync_scalar
Main File: odkex_replay_sync_scalar.cpp
Plugin Name: ODK_REPLAY_SYNC_SCALAR
Plugin UUID: E70860DB-1A21-4C1E-8329-55A1871ACC7A
Source Code of Main File¶
// Copyright DEWETRON GmbH 2019
#include "odkapi_config_item_keys.h"
#include "odkfw_custom_request_handler.h"
#include "odkfw_properties.h"
#include "odkfw_software_channel_plugin.h"
#include "odkbase_message_return_value_holder.h"
#include "odkapi_utils.h"
#include "qml.rcc.h"
#include "sdk_csv_utils.h"
#include <fstream>
#include <functional>
#include <string.h>
// Manifest constains necessary metadata for oxygen plugins
// OxygenPlugin.name: unique plugin identifier; please use your (company) name to avoid name conflicts. This name is also used as a prefix in all custom config item keys.
// OxygenPlugin.uuid: unique number (generated by a GUID/UUID generator tool) that stored in configuration files to match channels etc. to the correct plugin
static const char* PLUGIN_MANIFEST =
R"XML(<?xml version="1.0"?>
<OxygenPlugin name="ODK_REPLAY_SYNC_SCALAR" version="1.1" uuid="E70860DB-1A21-4C1E-8329-55A1871ACC7A">
<Info name="Example Plugin: Simple file replay">
<Vendor name="DEWETRON GmbH"/>
<Description>SDK Example plugin implementing file replay into a synchronous scalar channel.</Description>
</Info>
<Host minimum_version="5.3.2045"/>
<UsesUIExtensions/>
</OxygenPlugin>
)XML";
// A minimal translation file that maps the internal ConfigItem key to a nicer text for the user
static const char* TRANSLATION_EN =
R"XML(<?xml version="1.0"?>
<TS version="2.1" language="en" sourcelanguage="en">
<context><name>ConfigKeys</name>
<message><source>ODK_REPLAY_SYNC_SCALAR/InputFile</source><translation>Input File</translation></message>
</context>
<!-- Translations for custom UI item -->
<context><name>ODK_REPLAY_SYNC_SCALAR/AddChannel</name>
<message><source>Not a valid CSV file</source><translation>No valid CSV file selected</translation></message>
</context>
</TS>
)XML";
// Keys for ConfigItems that are used to store channel settings
// Custom key (prefixed by plugin name) to store path to the input file
static const char* KEY_INPUT_FILE = "ODK_REPLAY_SYNC_SCALAR/InputFile";
using namespace odk::framework;
class ReplayChannel : public SoftwareChannelInstance
{
public:
ReplayChannel()
: m_input_file(new EditableStringProperty(""))
, m_next_tick(std::numeric_limits<uint64_t>::max())
{
}
// Describe how the software channel should be shown in the "Add Channel" dialog
static odk::RegisterSoftwareChannel getSoftwareChannelInfo()
{
odk::RegisterSoftwareChannel telegram;
telegram.m_display_name = "Example Plugin: Simple file replay";
telegram.m_service_name = "CreateChannel";
telegram.m_display_group = "Data Sources";
telegram.m_description = "Adds a synchronous channel that delivers samples read from a CSV file.";
telegram.m_ui_item_add = "AddChannel";
telegram.m_analysis_capable = false;
return telegram;
}
InitResult init(const InitParams& params) override
{
odk::PropertyList props(params.m_properties);
auto csv_file = props.getString("ODK_REPLAY_SYNC_SCALAR/CSVFile");
if (!csv_file.empty())
{
m_input_file->setValue(csv_file);
update();
}
InitResult r(true);
r.showChannelDetails(getRootChannel()->getLocalId());
return r;
}
void updatePropertyTypes(const PluginChannelPtr& output_channel) override
{
ODK_UNUSED(output_channel);
}
void updateStaticPropertyConstraints(const PluginChannelPtr& channel) override
{
ODK_UNUSED(channel);
}
bool update() override
{
// channel is only valid if we can properly parse the specified csv file
// in production code the parsing should not be done on every config change, but only if the filename was updated
CSVNumberReader csv;
std::ifstream input_stream(m_input_file->getValue());
m_next_tick = std::numeric_limits<uint64_t>::max();
m_values.clear();
double range_min = std::numeric_limits<double>::max();
double range_max = std::numeric_limits<double>::lowest();
if (input_stream && csv.parse(input_stream) && !csv.m_values.empty())
{
m_values.reserve(csv.m_values.size());
size_t column_index = 0;
for (const auto& row_values : csv.m_values)
{
if (row_values.size() > column_index)
{
range_min = std::min(range_min, row_values[column_index]);
range_max = std::max(range_max, row_values[column_index]);
m_values.push_back(row_values[column_index]);
}
else
{
m_values.push_back(std::numeric_limits<double>::quiet_NaN());
}
}
}
bool is_valid = range_min <= range_max;
getRootChannel()->setRange({range_min, range_max, "", ""});
getRootChannel()->setValid(is_valid);
getRootChannel()->getRangeProperty()->setLive(is_valid);
getRootChannel()->setSimpleTimebase(m_sample_rate->getValue().m_val);
return is_valid;
}
void create(odk::IfHost* host) override
{
ODK_UNUSED(host);
getRootChannel()->setSamplerate({1000.0, "Hz"});
m_sample_rate = getRootChannel()->getSamplerateProperty();
m_sample_rate->setMinMaxConstraint(0.01, 10000000.0);
// add some proposed sample rates
m_sample_rate->addOption(1);
m_sample_rate->addOption(100);
m_sample_rate->addOption(1000);
getRootChannel()->setDefaultName("Replay channel")
.setSampleFormat(
odk::ChannelDataformat::SampleOccurrence::SYNC,
odk::ChannelDataformat::SampleFormat::DOUBLE,
1)
.setSimpleTimebase(m_sample_rate->getValue().m_val)
.setDeletable(true)
.addProperty(KEY_INPUT_FILE, m_input_file);
}
bool configure(
const odk::UpdateChannelsTelegram& request,
std::map<std::uint32_t, std::uint32_t>& channel_id_map) override
{
configureFromTelegram(request, channel_id_map);
return true;
}
void prepareProcessing(odk::IfHost* host) override
{
const auto ts = getMasterTimestamp(host);
const auto rate_factor = m_sample_rate->getValue().m_val / ts.m_frequency;
m_next_tick = static_cast<std::uint64_t>(ts.m_ticks* rate_factor);
}
void process(ProcessingContext& context, odk::IfHost *host) override
{
ODK_UNUSED(context);
std::uint32_t channel_id = getRootChannel()->getLocalId();
auto ts = getMasterTimestamp(host);
auto tick = m_next_tick;
const auto rate_factor = m_sample_rate->getValue().m_val / ts.m_frequency;
std::uint64_t target_tick = static_cast<std::uint64_t>(ts.m_ticks * rate_factor);
while (tick < target_tick)
{
auto idx = tick % m_values.size();
auto sz = std::min(m_values.size() - idx, target_tick - tick);
addSamples(host, channel_id, tick, &m_values[idx], sizeof(double) * sz);
tick += sz;
}
m_next_tick = tick;
}
private:
std::shared_ptr<EditableStringProperty> m_input_file;
std::shared_ptr<EditableScalarProperty> m_sample_rate;
std::vector<double> m_values;
std::uint64_t m_next_tick; // timestamp of the next sample that will be generated in doProcess()
};
class ReplaySyncScalarPlugin : public SoftwareChannelPlugin<ReplayChannel>
{
public:
ReplaySyncScalarPlugin()
: m_custom_requests(std::make_shared<odk::framework::CustomRequestHandler>())
{
addMessageHandler(m_custom_requests);
namespace arg = std::placeholders;
m_custom_requests->registerFunction(1, "checkCSVFile", std::bind(&ReplaySyncScalarPlugin::checkCSVFile, this, arg::_1, arg::_2));
}
void registerResources() final
{
addTranslation(TRANSLATION_EN);
addQtResources(plugin_resources::QML_RCC_data, plugin_resources::QML_RCC_size);
}
std::uint64_t checkCSVFile(const odk::PropertyList& params, odk::PropertyList& returns)
{
const auto filename = params.getString("filename");
CSVNumberReader csv;
std::ifstream input_stream(filename);
const bool valid = input_stream && csv.parse(input_stream) && !csv.m_values.empty();
returns.setBool("valid", valid);
return odk::error_codes::OK;
}
private:
std::shared_ptr<odk::framework::CustomRequestHandler> m_custom_requests;
};
OXY_REGISTER_PLUGIN1("ODK_REPLAY_SYNC_SCALAR", PLUGIN_MANIFEST, ReplaySyncScalarPlugin);
Example: Sync+Async Channel¶
This example demonstrates how to sum up sample values of two scalar input channels and write the result to an output channel.
Features¶
Register a new Software Channel Type in Oxygen
Plugin instance provides a Channel Id List Config Item to configure the two input channels
Config Item name is translated to English and German
Validate input channels and their type
Read samples from synchronous and/or asynchronous channels
Write samples to a synchronous and an asynchronous output channel
Location: examples/sync_plus_async_channel
Main File: odkex_sync_plus_async_channel.cpp
Plugin Name: ODK_SUM_CHANNELS
Plugin UUID: D9C295C0-CBB9-4412-9B4A-0C5B1ACF3EB6
Example: Bin Detector¶
This example plugin detects the minimum and/or maximum values with the corresponding bins out of an Oxygen vector channel (e.g. a fft channel). All values and bins are written to separate asynchronous output channels. Detecting min/max values and bins can be configured through the plugin group channel setup page.
Features¶
Register a new Software Channel Type in Oxygen
Plugin group channel provides selectable config items for detecting min and/or max values and their corresponding bins.
Creates output channels for values and bins dynamically. The name of the selected input channel affects the name of the created output channel names
Read samples from a vector channel
Write values and bins to corresponding asynchronous scalar output channels
Dynamic plugin config is stored and can be reloaded through oxygen save/load config use case
Location: examples/bin_detector
Main File: odkex_bin_detector.cpp
Plugin Name: ODK_BIN_DETECTOR
Plugin UUID: DCDF634A-377B-4E9F-89BD-09D54C9DFCD3
Example: WAV Export¶
This example plugin exports channel data as WAV file
Features¶
Register custom exporter in Oxygen
Simple WAV file writer
Write channel samples to WAV file
UI extension to change format settings
Location: examples/wav_export
Main File: odkex_wav_export.cpp
Plugin Name: ODK_WAV_EXPORT
Plugin UUID: 60e4627b-8964-4259-a9e0-54d38847ae68