Advanced: Translators and ValueMaps
Translators
Some IOServers need to know a method how to translate data between data stream and actual CDP channels (signal- or propertychannels). For that, a Translator element can be added to IOServer element.
Several pre-made Translators are available in CDP:
Translator name | Description |
---|---|
TextTranslator | Converts channel values to/from the stream line by line as text. |
CSVTranslator | Converts channel values to/from the stream in CSV format. |
BinaryTranslator | Converts channel values to/from the stream in binary format. |
JSONTranslator | Converts channel values to/from the stream in JSON format. |
FormatTranslator | Converts channel values to the stream using Format pattern. |
RegexTranslator | Converts channel values from the stream using Regex pattern. |
CustomTranslator | You can create your own translator in C++ and use this. |
Note: One and only one Translator must be configured for a stream. In case none or multiple Translators configured, error message will be printed at runtime and TextTranslator is chosen as default.
ValueMaps
ValueMap elements can be used by Translators in order to send or receive textual values to/from the stream based on CDP channel values. Every ValueMap element will then be used to transform between one CDP value and corresponding stream text value. Unlimited number of ValueMap elements can be added.
The ValueMap element has the following properties:
Property | Description |
---|---|
ChannelValue | CDP channel value (of any CDP channel type). |
StreamValue | Corresponding stream value (string). |
For example you can use SignalChannel<bool> and send or receive strings OFF and ON (instead of CDP bool values 0 and 1) with two ValueMaps like this:
- ChannelValue 0 <=> StreamValue OFF
- ChannelValue 1 <=> StreamValue ON
Note: ValueMaps are type-sensitive - i.e defining ValueMap<int> for 0 does not apply to channels other than type <int> (for example value 0 for channel <short> is not mapped by this ValueMap).
TextTranslator
The TextTranslator is a stream translator that converts channel values to/from the stream, line by line, as printable text.
The TextTranslator has the following properties:
Property | Description |
---|---|
Prefix | Prefix text to be added to output or ignored from input |
Suffix | Suffix text to be added to output or ignored from input |
CSVTranslator
The CSVTranslator is a stream translator that converts channel values to/from the stream in CSV format.
The CSVTranslator has the following properties:
Property | Description |
---|---|
Separator | Separator for CSV values. Can be set to Semicolon (default), Comma or TabSpace. |
Decimal | Decimal separator for values. Can be set to Point (default) or Comma. |
Quotation | Quotation style to use. Can be set to None (no quoatation, default), MappedValues (only ValueMap replaced values are quoted) or All - all values are quoted. This option is available only for the output CSVTranslator. |
BinaryTranslator
The BinaryTranslator converts channel values to/from the stream in binary format.
Every channel value is converted as they are represented in memory, corresponding to the channel type:
Channel type | Number of bytes translated to/from stream |
---|---|
bool, char, unsigned char | 1 byte |
short, unsigned short | 2 bytes |
int, unsigned int, float | 4 bytes |
int64_t, uint64_t, double | 8 bytes |
string | number of bytes in string |
JSONTranslator
The JSONTranslator is a stream Translator that can be used to easily send or receive JSON-formatted streams.
The JSONTranslator has the following properties:
Property | Description |
---|---|
TabSpace | Number of spaces to use for indentation of JSON output. This option is available only for the output JSONTranslator. |
Corresponding channel and group names will be used as JSON object names. Corresponding channel values will be used as JSON object values.
For more complex JSON objects, nested ChannelGroups can be added. Using them recursively, Channel and ChannelGroups hierarchy can be created that match any JSON object hierarchy.
To match JSON arrays, ChannelGroup with array element order number as ChannelGroup name has to be created.
For example, let's assume you have to send or receive the following JSON stream:
{ "Status": 1, "SensorData" : [ { "Value": 1, "Battery": 100 }, { "Value": 2, "Battery": 80 }, { "Value": 4, "Battery": 90 } ] }
For that you have to create channel-tree with Channels and ChannelGroups like in the figure below
Note: Channel in a ChannelGroup can be addressed (for CDP routing) using its parent group names. In the example above, the third sensor battery signal routing address in CDP will be
App.Component.Sensors.SensorData.2.Battery
CDP reserved names and symbols
When configuring Channel and ChannelGroup names, note that there are some reserved words (like "Name" etc) in CDP that you can not use as Channel or ChannelGroup name. If you have to use these names for JSON, place space at the end of Channel and ChannelGroup name (as spaces are trimmed out of name by JSONTranslator). For example, to send or receive JSON object named "Name" configure channel or group to be named "Name ".
Also, there are some reserved symbols (like . or /) that CDP also does not allow to use in object names. You can insert these special symbols using notation \xFF, that will be replaced by byte with hexadecimal value FF. For example, a dot can be inserted in name with secuence \x2E
FormatTranslator
The FormatTranslator can be used to convert channel values to output stream using format pattern.
The FormatTranslator has the following properties:
Property | Description |
---|---|
Pattern | Pattern to form stream from channel value(s) in C++ library Boost Format() or C language printf() like format. |
Pattern is a string consisting of ordinary characters that will be copied to output unmodified. Special character entities will be replaced with current channel values from the corresponding channel list.
Let's assume you have to send out payloads like this:
Temperature: 25.1 C; Humidity: 55 %
Assuming you have two channels for temperature and humidity added to channel list, then you can use their values and FormatTranslator to form the output with:
Pattern="Temperature: %.1f C;\nHumidity: %02d %%".
Format patterns allow to compose very complex and specific payloads from channel values.
Below is a table with the most useful format patterns:
%d | Outputs current value of next channel from channel list as number (of any type, even bool, float or double). |
%N% | Outputs current value of N-th channel from channel list as number (of any type). |
%0Nd | Outputs current value of next channel from channel list as number (of any type), using at least N symbols (padding zeros if needed). |
%.Nd | Outputs current value of next channel from channel list as number (of any type), using maximum N symbols (rounding decimal part). |
%.Nf | Outputs current value of next float or double channel from channel list rounded, using maximum N digits after decimal mark |
%% | Outputs % (percentage mark) |
\n | Outputs newline |
\xFF | Outputs byte with hexadecimal value FF |
For more information about possible format characters, please see Boost format syntax.
Note: Format pattern allows to use ValueMap. I.e. numeric value formatter (for example %d) can be used, but value can still be mapped to string - then mapped string is actually outputted (at the %d placeholder).
RegexTranslator
The RegexTranslator converts channel values from the stream using Regex match pattern.
The RegexTranslator has the following properties:
Property | Description |
---|---|
Pattern | Pattern for converting channel values from input stream in POSIX Regex match pattern format. |
Regex match pattern is a string that describes what will expected to be in the stream with special character entities that will indicate blocks, that will be read as values for channels in corresponding channel list.
Let's assume you have to receive and parse stream like this:
Temperature: 25.1 C; Humidity: 55 %
Assuming you have corresponding temperature and humidity channels added for input , then you can use RegexTranslator with following Pattern to get values to these channels:
Pattern="Temperature: (\S+) C;\s+Humidity: (\S+) %"
Regex match patterns can detect and grab channel values from very complex payload structures.
Below is table with the most useful special characters in patterns:
(...) | Indicates a match block. Anything that is matched inside parenthesis is used as a value for the next channel in the corresponding channel list. |
. | Matches any character |
\. | Matches . character (dot) |
\s | Matches any whitespace character (space, tab or newline) |
\S | Matches any non-whitespace character (any character except space, tab and newline) |
\d | Matches any digit character |
+ | Indicates one or more occurrences of the preceding elements. For example, S+ causes to match one or more sequential non-whitespace characters. |
* | Indicates zero or more occurrences of the preceding elements. For example, S* causes to match any number (or zero) sequential non-whitespace characters. |
You can learn more about Regex possibilities, at:
Note: Regex match patterns also consider ValueMaps. I.e. after a pattern is matched, ValueMaps are also scanned to find any match. If matched StreamValue is found, corresponding ChannelValue is used instead to set the value of a channel.
CustomTranslator
If none of the existing Translators suits the need, a custom Translator can be created and used by programming it's behavior in C++ code.
To create a custom Translator:
- Create new library in CDP Studio
- Select Code mode
- By right-clicking on library name, choose "Add New..." wizard.
- Choose add "CDP Node Model" element, and follow the wizard, giving Translator a suitable name f.e MyTranslator.
- Change model file (MyLibrary.MyTranslator.xml) to be derived from CDPCore.Translator (instead of CPNode), to look like this:
<?xml version="1.0" encoding="utf-8"?> <Model Name="MyLibrary.MyTranslator<T>"> <ModelTypeClass>C++</ModelTypeClass> <BaseModel>TranslatorBase<T></BaseModel> <Template Name="T" Value="ostream; istream"/> <Attributes> <Attribute Name="Name" Type="string" Required="1" ReadOnly="1"/> <Attribute Name="Model" Value="MyLibrary.MyTranslator<T>" Type="string" Description="Implementation model used." Required="1" ReadOnly="1"/> <Attribute Name="Description" Type="string" Value="Simple CustomTranslator example." ReadOnly="1" Required="1"/> </Attributes> </Model>
- Check library builder function .h and .cpp files to have function CreateNewCDPNode(const std::string& type) overriden to create MyTranslator instance. MyLibraryBuilder.h to be like:
#ifndef MYLIBRARY_MYLIBRARYBUILDER_H #define MYLIBRARY_MYLIBRARYBUILDER_H #include <CDPSystem/Application/CDPBuilder.h> namespace MyLibrary { class MyLibraryBuilder : public CDPBuilder { public: MyLibraryBuilder(const char* libName,const char* timeStamp); CDP::StudioAPI::CDPNode* CreateNewCDPNode(const std::string& type) override; }; } #endif
MyLibraryBuilder.cpp to be like:
#include "MyTranslator.h" #include "MyLibraryBuilder.h" using namespace MyLibrary; MyLibraryBuilder::MyLibraryBuilder(const char* libName, const char* timeStamp) : CDPBuilder(libName, timeStamp) { } CDP::StudioAPI::CDPNode *MyLibraryBuilder::CreateNewCDPNode(const std::string &type) { if (type == "MyLibrary.MyTranslator<ostream>") return new MyTranslator<ostream>; if (type == "MyLibrary.MyTranslator<istream>") return new MyTranslator<istream>; return CDPBuilder::CreateNewCDPNode(type); }
- Change MyTranslator.h so that MyTranslator class is derived from ServerIO::Translator::TranslatorBase (instead of CDPNode), to look like this:
#ifndef MYLIBRARY_MYTRANSLATOR_H #define MYLIBRARY_MYTRANSLATOR_H #include <CDPSystem/Base/CDPObject.h> #include <IO/ServerIO/Translator/TranslatorBase.h> namespace MyLibrary { template <typename STREAMTYPE> class MyTranslator : public ServerIO::Translator::TranslatorBase<STREAMTYPE> { public: void Translate(STREAMTYPE& stream, const ServerIO::Translator::TranslatorChannelGroup& channelTree) const override; std::string GetNodeTypeName() const override; }; } #endif
- Write member bodies for actual worker functions for MyTranslator<ostream>::Translate() and MyTranslator<ostream>::Translate()(). These functions will do the actual formatting and parsing. Below is a simple translator example that just composes and parses channel and channel group member values line-by-line.
#include "MyTranslator.h" #include <StudioAPI/NodeStream.h> #include <Generic/CDPUtils.h> using namespace std; using namespace ServerIO::Translator; namespace MyLibrary { template <> void MyTranslator::Translate<ostream>(ostream& stream, const TranslatorChannelGroup& channelTree) const { for (auto channel : channelTree.ChildChannels()) { if (channel->HasMappedValue()) stream << channel->MappedValue(); else stream << channel->StringValue(); stream << endl; } for (auto group : channelTree.ChildGroups()) { stream << endl; Translate(stream, *group); } } template <> void MyTranslator::Translate<istream>(istream& stream, const TranslatorChannelGroup& channelTree) const { string intoken; getline(stream, intoken); for (auto channel : channelTree.ChildChannels()) { if (!channel->SetMappedValue(intoken)) channel->SetValue(intoken); getline(stream, intoken); } for (auto group : channelTree.ChildGroups()) Translate(stream, *group); } template <> string MyTranslator<ostream>::GetNodeTypeName() const { return "MyLibrary.MyTranslator<ostream>"; } template <> string MyTranslator<istream>::GetNodeTypeName() const { return "MyLibrary.MyTranslator<istream>"; } }
Note: For simplicity, one of the member bodies (ComposePayload() or ParsePayload()) can be left empty, in cases where your translator is one-directional, i.e. compose- or parseonly.
- In case the translator needs some configurable properties, also override member-function Configure() and set up properties in there.
Build the library. After successful build, the newly created MyTranslator becomes available for use in any IOServer element that accepts Translators (like MQTTIO Topics or ExternalControlIO Requests).
Get started with CDP Studio today
Let us help you take your great ideas and turn them into the products your customer will love.