Custom
Custom Conditions are designed to prepare raw input data for modeling. Some examples of this are converting XML to JSON, converting raw binary streams to data types, process counters, and more.
Custom Conditions expose an input for each source. If the source is
{{Connection.opc.tag}}
the conditioned input is {{Condition.myCondition.opc_tag}}
. Reading this value returns the value of the input run through the expression.
Custom Conditions maintain the state of the last read and last returned values per source, allowing flexibility in handling things like state changes.
Expression
The JavaScript expression that runs every time a source is read. The expression contains the following variables that are kept for each source in the condition. These values are persisted on disk and persist between condition reads.
Syntax | Description |
---|---|
{{this.currentValue}} |
The current value of the source being read. For example, this would be the current value of the OPC tag or MQTT topic. |
{{this.lastValue}} |
The previous value for the source that was read the last time the expression was executed. For example, if the currentValue on the first read is 100, the lastValue on the next read is 100. This value is null when reading a source for the first time. This value is only cached if {{this.lastValue}} is used in the expression. |
{{this.lastReturnValue}} |
The last value returned by the expression for the source. This is null on the first read of the source. This value is only cached if {{this.lastReturnValue}} is used in the expression. |
In addition to the above variables, other sources can be directly included in the expression by dragging/dropping the reference from the reference panel.
Custom Condition Examples
Below are common examples of Expressions. Note Expressions use JavaScript (ECMAScript/ECMA-262 specification).
Convert Named Object to Array
Assume the input payload looks like this.
{
"tag1":{
"value": 100,
"timestamp": "2023-05-31T12:30:56Z",
"quality": "good"
},
"tag2": {
"value": 60,
"timestamp": "2023-05-31T12:30:56Z",
"quality": "good"
}
}
We want to convert the payload to this.
[
{
"name": "tag1",
"value": 100,
"timestamp": "2023-05-31T12:30:56Z",
"quality": "good"
},
{
"name": "tag2",
"value": 60,
"timestamp": "2023-05-31T12:30:56Z",
"quality": "good"
}
]
The following expression for the condition accomplishes this.
var rawObject = {{this.currentValue}}
var arrayOut = []
// Iterate each tag in the payload
for (const tagName in rawObject){
// Create new object and add name
let tagObject = {...rawObject[tagName]}
tagObject["name"] = tagName
// Add to the array
arrayOut.push(tagObject)
}
// Return the array
arrayOut
Convert Array to Named Objects
Assume we want the reverse of the above example. In this case the raw data comes in as an array, and we want to convert it to named objects.
var rawArray = {{this.currentValue}}
var objectOut = {}
// Iterate each object in the array
for (const tag of rawArray){
// Get the tag name
let tagName = tag["name"]
// Add the tag to the object, using the name as the key
objectOut[tagName] = tag
// Delete the name field
delete objectOut[tagName]["name"]
}
// Return the object
objectOut
Return New Rows Only
In cases where an input returns all data, and you’re only concerned about new data, a condition can be used to filter out data that has already been read. Ideally this would be done by the input, for example using indexing in SQL, but in cases where it can’t a condition can perform the filtering.
Assume the raw input returns the following data.
[
{
"id": 1,
"name": "tag1",
"value": 100,
"timestamp": "2023-05-31T12:30:56Z",
"quality": "good"
},
{
"id": 2,
"name": "tag2",
"value": 60,
"timestamp": "2023-05-31T12:30:56Z",
"quality": "good"
}
]
On the first read this condition returns the two rows. On the second read the condition returns an empty array. If a new row is added with a new id, the third read returns the new row.
var currentReadRows = {{this.currentValue}}
var lastReadRows = {{this.lastValue}}
var retRows = []
if (lastReadRows == null){
// First read, there is no last read
currentReadRows
}
else{
// Iterate each row from the latest read
for (const currentRow of currentReadRows){
let foundRow = false;
// Iterate each row from the last read and see if there is a match by the id property
for (const lastRow of lastReadRows){
if (lastRow._id == currentRow._id){
foundRow = true
break;
}
}
// If no match is found, return the new row
if (!foundRow)
retRows.push(currentRow)
}
retRows
}