Actions
Each action has this basic model. An action receives an input message which depend on the action hook and the previous action in case of an action pipeline. After execution of the action it will output a message which could be altered depending on the action type.
{
"type": "", // Type of the action.
"message": { // (Optional) To be merged with the input message.
"topic": "", // (Optional) Can be a string or null.
"payload": {} // (Optional) Can be any number, string, object or array.
}
}
Pipeline can consist of actions with type
:
-
action: Refers to another action to be executed.
-
collect: Collect data from a widget.
-
consoleLog: Write to the browser’s console log.
-
convert: Converts data to and from JSON, Base64.
-
copy: Copy to clipboard.
-
delegate: delegate the execution context of an action pipeline.
-
dismiss: Dismiss an active floating tab or the last shown prompt
-
gettime: Converts relative, ISO UTC and milliseconds since Epoch timestamps.
-
function: Advanced Endpoint call to the system.
-
modify: Change the model of a widget.
-
notify: Display a notification.
-
openLink: Opens a URL in the browser.
-
passthrough: Passes the input message to the next action with the option to merge a message and/or payload.
-
prompt: Show a dialog.
-
read: Reads a value of an object.
-
read-write: Used for data sources, supports
read
andwrite
. -
refresh: Refresh a widget.
-
send: Send data to another widget.
-
subscribe: Subscribe to data changes in the system.
-
switch: Execute different actions based on conditions.
-
tpm-oee: Read configuration table models from the backend relating to OEE monitoring.
-
transform: Transform the data using MongoDB’s Aggregation Pipeline logic.
-
wait: Adds a delay before executing the next action.
-
write: Writes a value to an object.
Note: Features marked with (*) are not supported yet.
Action
Invoke a named action defined in the widget’s own actions
collection or in the actions
collection at compilation
level. Actions defined at the widget level take precedence over those at compilation level. If a widget refers to a
named action which exists in his own model and at compilation level, the one in the widget collection will be executed.
{
"type": "action",
"name": "NAME OF THE ACTION"
}
Refer to the write-example-01 compilation to see how named actions are defined and used.
Collect
Collect data from a widget. The specific data retrieved dependents on the source widget. In general, collect returns the
same payload data provided to action pipelines defined directly on the referenced widget. Refer to the widget specific
documentation for more details. The collected information is assigned to the provided key
name of the message
payload
. If a key
is not specified the whole payload
will be overwritten with the collected data.
{
"type": "collect",
"from": "Place the ID of the widget here",
"key": "collectedData"
}
The from
field can be set to one of the following:
-
Widget ID: ID of a widget at the same level in the compilations. Note: Dot-notation cannot be used to collect data from widgets nested inside tabs.
-
Route: The route notation is typically used to access widgets contained in nested tab compilations.
-
"self": This allows the pipeline to fetch data from the widget that initiated the actions. This might seem like an odd thing to do since the message at the beginning of a pipeline will be initialized by the source widget, but as the execution progresses this information may be overwritten. Using the
collect
action on "self" provides a way to get back to the original content.
Note: Collect is mainly intended to grab the "data" content of a widget. If you need to get access to any other fields of a widget during pipeline execution, the modify action can be used together with
set
Console Log
Writes the input message payload
to the browser’s console log.
{
"type": "consoleLog"
}
Add context by providing a tag
.
{
"type": "consoleLog",
"tag": "fetch response"
}
Example to set a fixed text message on the payload
:
{
"type": "consoleLog",
"tag": "fetch response",
"message": {
"payload": "This is just a message"
}
}
Example to merge the input message payload
with other key-value data.
{
"type": "consoleLog",
"tag": "fetch response",
"message": {
"payload": {
"otherText": "This is just some other text message"
}
}
}
Convert
Converts the payload
to a specified format. Supported formats are:
-
json
-
base64
Encode the payload:
{
"type": "convert",
"encode": "json"
}
Decode the payload:
{
"type": "convert",
"decode": "json"
}
Delegate
Using tabs
widgets, compilations can be created that contain sub-compilations in each tab. Widgets defined within
these cannot directly interact with other widgets in peer level compilations.
The delegate
action provides a mechanism to address this constraint.
{
"type": "delegate",
"action": []
}
This action is probably easiest to understand by considering an example.
Suppose we have a tabs
widget with two tab instances, each containing their own text
widget, text01 and text02. We want to change the text of text02 when clicking on text01. As a first attempt, we might try something like this:
{
"type": "text",
"text": "Click Me",
"id": "text01",
"actions": {
"onClick": {
"type": "send",
"to": {
"route": [
"tabs01",
"tab02",
"text02"
]
},
"message": {
"payload": "Text on tab 1 was clicked"
}
}
}
}
This fails to update text02 since, from within the tab01 compilation, there is no widget that resolves to the provided route. The route only makes sense when it is traversed starting at the root compilation.
Root (compilation) | +- tabs01 (widget) | +- tab01 (compilation) | | | +- text01 (widget) <-- Action starts here | +- tab02 (compilation) | +- text02 (widget)
To get the correct execution context we need to define the action at either the root compilation level, or in the actions section of the tabs01 widget.
Note: The execution context refers to the compilation model from which routes and widget ids are resolved.
A named action can be be defined in the root compilation and invoked from text01. This works since named actions are resolved by searching upwards in the containment hierarchy. In this case the onClick
could be defined like so:
{
"type": "text",
"text": "Click Me",
"id": "text01",
"actions": {
"onClick": {
"type": "action",
"name": "modifyWidgetOnSecondTab"
}
}
}
with the named action modifyWidgetOnSecondTab
in the root compilation:
{
"actions": {
"modifyWidgetOnSecondTab": {
"type": "send",
"to": {
"route": [
"tabs01",
"tab02",
"text02"
]
},
"message": {
"payload": "Text on tab 1 was clicked"
}
}
}
}
Unfortunately, just declaring the named action in the root compilation is not enough. The execution context is not affected by where the action is declared, unless delegate
is used.
{
"actions": {
"modifyWidgetOnSecondTab": {
"type": "delegate",
"action": [
{
"type": "send",
"to": {
"route": [
"tabs01",
"tab02",
"text02"
]
},
"message": {
"payload": "Text on tab 1 was clicked"
}
}
]
}
}
}
In other words, delegate
changes the context of the execution pipeline to be at the level where it is defined in the compilation. Using the new context, the pipeline defined in the action
property is executed.
When the delegate
action returns, the context is restored and any subsequent actions will be executed in the context that was there before.
Note: The context also includes the widget that initiated the pipeline, and is referred to as
"self"
. If thedelegate
action is declared at compilation level, theself
widget will not be set.
Dismiss
This action is used to close a modal dialog shown using the prompt action or to close a floating tab from within the tab compilation.
{
"type" : "dismiss"
}
Note: The dismiss action closes the last shown prompt. Prompts can be "stacked" by invoking a
prompt
action from within an active popup dialog. Whendismiss
is called, only the top most instance is closed.
The example below shows the use of the dismiss action to close a tab.
{
"type": "tabs",
"appearance": {
"type": "floating"
},
"tabs": [
{
"id": "tab01",
"name": "Tab01",
"indicator": {
"title": "Tab 01"
},
"compilation": {
"version": "1",
"widgets": [
{
"type": "text",
"text": "Dismiss",
"captionBar": false,
"actions": {
"onClick": {
"type": "dismiss"
}
},
"layout": { "x": 0, "y": 0, "w": 32, "h": 32,
"static": false
},
"id": "txt"
}
],
"options": {
"stacking": "vertical",
"numberOfColumns": 32,
"width": 200,
"height": 150,
"numberOfRows": {
"type": "count",
"value": 32
}
}
}
}
],
"options": {
"tabAlignment": "top"
},
"layout": { "x": 31, "y": 0, "w": 29, "h": 5,
"static": false
},
"id": "tabs"
}
GetTime
The gettime
action can perform two types of functions:
-
Convert a relative time expression to the equivalent ISO UTC time string or an Epoch timestamp. By default the output is an ISO string. If the optional
asEpoch
property is set to true, the output is returned as a number. -
Convert between ISO UTC string and epoch integer
A time relative expression consists of a , indicating *now, optionally followed by an offset expression subtracted
from, or if required, added to the current time.
The offset is stated as an integer number followed by a time scale unit. The supported time scale units are:
-
ms - millisecond
-
s - second
-
m - minute
-
h - hour
-
d - day
-
w - week
Here are some examples of relative time expressions:
Expression | Description |
---|---|
* |
Now |
*-5d |
5 days ago |
*+30m |
30 minutes from now |
The table below illustrates the conversions performed by the gettime
action.
type | value type | value example | results |
---|---|---|---|
Relative |
string |
*-1d |
Now minus one day as an ISO UTC string. Unless: |
ISO UTC |
string |
2021-04-28T09:44:35.668Z |
1619603075668 |
Milliseconds since Epoch |
number |
1619603075668 |
2021-04-28T09:44:35.668Z |
The JSON below shows an example of gettime
used to convert relative times to absolute ISO UTC strings:
{
"type": "gettime",
"set": [
{
"name": "starttime",
"value": "*-5d"
},
{
"name": "endtime",
"value": "*-1d"
}
]
}
Resulting message:
{
"payload": {
"starttime": "2021-04-24T10:31:04.091Z",
"endtime": "2021-04-28T10:31:04.092Z"
}
}
This example shows how to convert a relative time to its Epoch equivalent:
{
"type": "gettime",
"set": [
{
"name": "starttime",
"value": "*-5d",
"asEpoch" : true
}
]
}
yields this output message:
{
"payload": {
"starttime": 1619260264103
}
}
The action can also take the set
instruction from the message payload to do dynamic conversions.
Action message input:
{
"payload": {
"set": [
{
"name": "myTimestamp",
"value": "*-2d"
}
]
}
}
Action:
{
"type": "gettime" // Notice it does not contain a set field.
}
Result is the message output:
{
"set": [], // Since the action result is merged with the message input the set is still present.
"myTimestamp": 1630497600000
}
Function
Invoke an Advanced Endpoint.
{
"type": "function",
"lib": "LIBRARY NAME",
"func": "FUNCTION NAME", // (Optional) Function name in case library is Lua table.
"farg": {}, // (Optional) Function argument.
"ctx": "" // (Optional) System object path.
}
Modify
With modify
the model of a widget can be changed. Rather than directly altering the underlying widget, this action applies changes by performing the following steps:
-
Copy the work model of the widget being modified into the
model
field of the message received by the action. -
Merge operator fields from the
message.payload
into the action definition. (Have a look at this example to see the process in action). -
Apply one or more modification operator. These operators typically change values in the
message.model
. -
Update the the work-model of the widget with the content of
message.model
. This triggers update and possibly refresh lifecycle hooks depending on what was changed and the action settings. (Also see Using modify to collect model information)
If no modification operator is specified a mergeObjects
will be performed. This will merge / overwrite the properties from the message.payload
into the model rather than applying the message.model
. (See example below)
The id
field is used to point to the widget which needs to be modified. The value is normally a string with the ID of
the relevant widget.
{
"type": "modify",
"id": "TextWidget" // Pointing to a Text widget which has an ID of 'TextWidget`.
}
Should you need to reference a nested widget, which is to say a tab instance of a Tabs widget or a widget which resides within a tab, you need to use a route
expression.
{
"type": "modify",
"id": {
"route" : [
"MyTabs", // ID of the Tabs widget.
"Tab01", // ID of the single tab.
"TextWidget" // Pointing to a Text widget which has an ID of 'TextWidget` which.
]
}
}
The id field may be set to "self" resulting in the action being applied to the currently scoped widget.
|
Modification Operators
The following modification operators are supported, multiples of which can be specified in the same action. They are
evaluated in the order shown. In other words, if multiple modification operators are used, the set
modification is
done first, followed by unset
and so on.
-
set
: Add field to model or update field. -
unset
: Remove field from model. -
addToArray
: Adds an item to an array field. -
removeFromArray
: Removes one or more items from an array that matches the provided fields. -
filter
: Removes items from an array field based on a condition.
A transform
action will be performed under the hood with completeMsgObject
set to true. As stated earlier, the model
field is added to the input message by the modify action before the transform
actions starts. It is read from the model of the widget being modified (designated by the id
field).
The message payload
, which is passed down from the originating widget or previous action in the pipeline is typically
used as the source for setting model fields.
Main signature of the modify
action is:
{
"type": "modify",
"id": "TextWidget",
"refresh": true, // Default is true. Set it to false to prevent a widget refresh.
"debug": false // If true, writes the model to the console log, after the modification.
}
Examples
A number of examples are presented to illustrate how modify actions are used and what the underlying transform logic looks like
Merge the widget model with the message payload
{
"type": "modify",
"id": "TextWidget"
}
Will result in a transform
action:
{
"type": "transform",
"completeMsgObject": true,
"aggregateOne": [
{
"$project": {
"model": {
"$mergeObjects": [
"$model",
"$payload"
]
},
"payload": "$payload"
}
}
]
}
To modify a specific sub document of the model, make use of the supported modification operators.
Example to update the font size:
{
"type": "modify",
"id": "TextWidget",
"set": [
{
"name": "model.options.style.fontSize",
"value": "50px"
},
{
"name": "model.options.style.fontFamily",
"value": "Courier New"
}
]
}
Will result in a transform
action:
{
"type": "transform",
"aggregateOne": [
{
"$set": {
"model.options.style.fontSize": "50px",
"model.options.style.fontFamily": "Courier New"
}
}
]
}
Example to update the style:
With this example the custom style will only include fontSize
.
{
"type": "modify",
"id": "TextWidget",
"set": [
{
"name": "model.options.style",
"value": {
"fontSize": "50px"
}
}
]
}
Will result in a transform
action:
{
"type": "transform",
"aggregateOne": [
{
"$set": {
"model.options.style": {
"fontSize": "50px"
}
}
}
]
}
Example to remove the style example:
This way the widget uses the default style.
{
"type": "modify",
"id": "TextWidget",
"unset": ["model.options.style"]
}
Will result in a transform
action:
{
"type": "transform",
"aggregateOne": [
{
"$project": {
"model.options.style": 0
}
}
]
}
Example to update an item by index:
This example changes the aggregate
of the first pen in a chart. Since the field notation is one-on-one used in the
Aggregation Pipeline, the index is zero based.
{
"type": "modify",
"id": "chartWidget",
"set": [
{
"name": "model.chart.pens.0.aggregate",
"value": "AGG_TYPE_AVERAGE"
}
]
}
Will result in a transform
action:
{
"type": "transform",
"completeMsgObject": true,
"aggregateOne": [
{
"$set": {
"model.chart.pens.0.aggregate": "AGG_TYPE_AVERAGE"
}
}
]
}
Example to add an item to the data
array of a table widget:
{
"type": "modify",
"id": "fixedTableWidget",
"addToArray": [
{
"name": "model.data",
"value": {
"column1": 1,
"column2": 2
}
}
]
}
Will result in a transform
action:
{
"type": "transform",
"completeMsgObject": true,
"aggregateOne": [
{
"$set": {
"model.data": {
"$concatArrays": [
{
"$cond": [
{
"$isArray": [
"$model.data"
]
},
"$model.data",
[]
]
},
[
{
"column1": 1,
"column2": 2
}
]
]
}
}
}
]
}
Example to remove an item from an array by index:
Remove the third row from a fixed data table.
{
"type": "modify",
"id": "fixedTableWidget",
"removeFromArray": [
{
"name": "model.data",
"idx": 2
}
]
}
Will result in a transform
action:
{
"type": "transform",
"completeMsgObject": true,
"aggregateOne": [
{
"$project": {
"model.data.1": 0
}
}
]
}
The index provided in the removeFromArray action is zero based, and must be a static number. This begs the question "How do I delete a row based on user input?". The answer is to do the modify in two steps.* Set the removeFromArray property in the message payload passed to the modify action* Then do the 'modify', leaving out the removeFromArray property, which will be taken from the message payload.The following example should make this clearer. |
Example to remove an item from an array based on a match expression:
Remove the rows from a fixed data table for which the name
field equals Inside Temperature
and the value
field is equal to 26.
{
"type": "modify",
"id": "fixedTableWidget",
"removeFromArray": [
{
"name": "model.data",
"item": {
"name": "Inside Temperature",
"value": 26
}
}
]
}
Alternative structure:
{
"type": "modify",
"id": "fixedTableWidget",
"removeFromArray": [
{
"name": "model.data",
"item": [
{
"name": "name",
"value": "Inside Temperature"
},
{
"name": "value",
"value": 26
}
]
}
]
}
Will result in a transform
action:
{
"type": "transform",
"completeMsgObject": true,
"aggregateOne": [
{
"$set": {
"model.data": {
"$filter": {
"input": "$model.data",
"as": "item",
"cond": {
"$or": [
{
"$ne": [
"$$item.name",
"Inside Temperature"
]
},
{
"$ne": [
"$$item.value",
26
]
}
]
}
}
}
}
}
]
}
Make sure the fields referenced in the item property exist in the array to be modified. Referring to model fields which are not present while deciding which rows to remove from an array, can yield unexpected results.
|
To illustrate the point, consider the following:
Suppose we have a table with name
and value
fields as before and want to remove all rows where the value
is null. We create the modify
action with a typo in the item
property (referring to valueX rather than value):
{
"type": "modify",
"id": "fixedTableWidget",
"removeFromArray": [
{
"name": "model.data",
"item": {
"valueX": null
}
}
]
}
When this action executes, the transform filter will look up model.data.valueX
in each row, which always yields null since the field is not there, and compare it to the target value of null, resulting in all rows being deleted.
Example to filter items from an array:
Filters the rows from a fixed data table of which the column value
is greater than or equal to 20. The condition
is
an Aggregation Pipeline filter condition. Within the condition $$item
is reserved for referencing an item in the
array.
{
"type": "modify",
"id": "fixedTableWidget",
"filter": [
{
"name": "model.data",
"condition": {
"$gte" : [
"$$item.value", 20
]
}
}
]
}
Will result in a transform
action:
{
"type": "transform",
"completeMsgObject": true,
"aggregateOne": [
{
"$set": {
"model.data": {
"$filter": {
"input": "$model.data",
"as": "item",
"cond": {
"$gte" : [
"$$item.value", 20
]
}
}
}
}
}
]
}
Using modify
to collect model information
The modify
action can also be used to copy properties from the widget’s work model into the message payload from where these are available to downstream actions. In doing so, the source widget is not changes at all. Consequently there is no need to invoke update and refresh lifecyle hooks which are not triggered in this scenario.
The table below summarizes the behavior of the modify
action based on the setting of the refresh
flag, and whether the model was modified.
refresh value | Model altered | Lifecycle hooks triggered |
---|---|---|
undefined |
false |
No refresh or update |
undefined |
true |
Refresh and update |
false |
false |
No refresh and no update |
false |
true |
Only update |
true |
false |
Refresh and update |
true |
true |
Refresh and update |
Example:
Suppose we want to toggle the background color of a text widget between say green and transparent each time
it is clicked. To achieve this a switch action can be used which "looks at" the current background color
and set the opposite one in onClick
. The tricky part is getting access to the current color. The json snippet below
shows how this might be achieved:
{
"actions": {
"onClick": [
{ // Start by reading the current background color.
"type": "modify",
"id": "self",
"set": [
{ // Note how the model and payload are referenced to
// read the background color and save it in the message payload
"name": "payload",
"value": "$model.options.style.backgroundColor"
}
]
},
{ // Swop the colors in the message payload.
"type": "switch",
"case": [
{
"match": { // if the current bg is transparent
"payload": "transparent"
},
"action": { // then set it to green
"type": "passthrough",
"message": {
"payload": "green"
}
}
},
{
"match": {}, // otherwise
"action": { // set to back to transparent
"type": "passthrough",
"message": {
"payload": "transparent"
}
}
}
]
},
{ // Apply the payload color to the widget background.
"type": "modify",
"id": "self",
"set": [
{
"name": "model.options.style.backgroundColor",
"value": "$payload"
}
]
}
]
},
}
Notify
Displays a notification on the top right of WebStudio.
{
"type": "notify",
"title": "Copied",
"text": "📋 Copied to Clipboard",
"duration": 2500,
"transition": "slide"
}
title
is optional and is by default set to the title
defined in the widget captionBar
. In case the title
is not
defined in the caption bar the name
or id
of the widget will be shown.
duration
is optional and by default 3000
.
transition
is optional with a default value of slide
.
-
slide
Smooth sliding of notification -
bounce
Bounce in of the notification -
zoom
Zoom in and out -
flip
Flips the notification
Open Link
Opens a hypermedia link.
{
"type": "openLink",
"url": "https://www.lipsum.com",
"target": "_blank"
}
target
is optional and by default _blank
.
-
_self
Opens the document in the same window/tab as it was clicked. -
_blank
Opens the document in a new window or tab.
Passthrough
The input message will be passed through to the next action with the option to merge with a defined message.
{
"type": "passthrough",
"message": {
"someField": "This field will be merged besides input message topic and payload",
"payload": {
"attrib": "This field will be merged within the input message payload"
}
}
}
Prompt (Dialog)
This action causes a popup dialog (prompt) to be shown.
-
Content: The content/model of the prompt is declared in a message payload and must be a single widget. It is possible to indirectly show a complete compilation by using a tabs widget containing one or more tab instances. The appearance of a single compilation is achieved by defining one tab and hiding the indicator.
-
Dialog title: Text to display in the dialog title bar can be defined using the
captionBar.title
element of the widget.{ "type": "prompt", "width": "500px", "height": "500px", "message": { "payload": { "type": "text", "text": "Hello world", "captionBar": { "title": "Prompt Title" }, "actions": { "onClick": { "type": "action", "name": "some-action" } } } } }
Since the prompt repurposes the widget’s
captionBar
, any of thecaptionBar
properties can be set. If the widget-edit button {} is visible (see showDevTools), the work model of the prompt can be inspected, albeit not edited. -
Closing the prompt: The most direct way to close the prompt dialog is to click on the "X" icon in the title bar. Pressing the ESC key on the keyboard will also hide the prompt.
If you choose not to show the title bar, then users of your compilations may be left wondering how to close the prompt, since there is no visual indication that the ESC key can be used. In this situation you can invoke the
dismiss
action from anonClick
handler as shown below.{ "type": "prompt", "message": { "payload": { "type": "text", "text": "Click or press ESC to Hide prompt", "captionBar": { "hidden": true }, "options": { "style": { "textAlign": "center", "fontSize": "20px", "fontWeight": "bold" } }, "actions": { "onClick": { "type": "dismiss" } } } } }
-
Using Named Actions: Actions implemented inside the
prompt
can be made to manipulate widgets outside its scope by using named actions.When invoking named
actions
declared at root compilation or source widget level, the delegate action is usually required to ensure that the changes are applied in the appropriate context and directed at the right widget.To make sense of this statement it might be helpful to refer to the prompt-02 example. It shows how to load a prompt from a click in the main compilation, containing a tabs widget with its indicator hidden. The text widgets in the prompt are clickable, resulting in a further popup and changes to other text widgets.
Named actions declared on the widget from which the prompt was invoked take precedence over named actions at compilation level.
Read
Reads the dynamic value of an object or the value of an object property by executing a Read endpoint request. The action model comes in three variants depending on how the path and options are specified:
Path and options provided at root level
This form of the read request returns only the value of the object or property referred to in the path, omitting the timestamp, id and quality.
{
"type": "read",
"path": "/System/Core/Examples/Demo Data/Process Data/FC4711",
"opt" : {
"q": "COREPATH"
}
}
Name | Description |
---|---|
|
Path to the object or property. |
|
Optional query details field, used to request information other than the object value. Supported arguments are described in the Read endpoint section in the Web API documentation. |
Example read object’s dynamic value.
{
"type": "read",
"path": "/System/Core/Examples/Demo Data/Process Data/FC4711"
}
The result is returned as a single value assigned to the message payload. For example:
{
"payload": 49.87
}
Example read object’s property value.
{
"type": "read",
"path": "/System/Core/Examples/Demo Data/Process Data/FC4711.OPCEngUnit"
}
Example to read a Lua KPI table object.
{
"type": "read",
"path": "/System/Core/Examples/Demo Data/Process Data/FC4711",
"opt": {
"STARTTIME": "2020-10-13T00:00:00.000Z",
"ENDTIME": "2020-10-14T00:00:00.000Z",
"TIMESTAMP": "1602633600"
}
}
Example of the query mechanism support by the Read Web API endpoint.
{
"type": "read",
"path": "/System/Core/Examples/Demo Data/Process Data/DC4711",
"opt": {
"q": "COREPATH"
}
}
Path and options provided in the item property
Read all VQT properties for a single entity.
{
"type": "read",
"item": {
"p": "/System/Core/Examples/Demo Data/Process Data/FC4711",
"opt": {} // options
}
}
Name | Description | |
---|---|---|
|
Single item for which to read the VQT values. |
|
|
Path to the object or property. |
|
|
Optional query details field, used to request information other than the object value. Supported arguments are described in the Read endpoint section in the Web API documentation. |
The output from the read looks something like this:
{
"i": 281474980511744,
"p": "/System/Core/Examples/Demo Data/Process Data/FC4711",
"q": 0,
"t": "2022-04-27T14:07:06.781Z",
"v": 36.58641815185547
}
Path and options provided in the items property
Read all VQT properties for a list of entities.
{
"type": "read",
"items": [
{
"p": "/System/Core/Examples/Demo Data/Process Data/FC4711",
"opt": {} // options
},
{
"p": "/System/Core/Examples/Demo Data/Process Data/DC4711",
"opt": {} // options
}
]
}
Name | Description | |
---|---|---|
|
Array of items for which to read the VQT values. |
|
|
Path to the object or property. |
|
|
Optional query details field, used to request information other than the object value. Supported arguments are described in the Read endpoint section in the Web API documentation. |
The output from the read looks something like this:
[
{
"i": 281474980511744,
"p": "/System/Core/Examples/Demo Data/Process Data/FC4711",
"q": 0,
"t": "2022-04-27T14:12:45.781Z",
"v": 43.22888946533203
},
{
"i": 281474980118528,
"p": "/System/Core/Examples/Demo Data/Process Data/DC4711",
"q": 0,
"t": "2022-04-27T14:12:24.781Z",
"v": 10.687927246093746
}
]
Read-write
Can be used for widgets which support reading and writing.
{
"dataSource": {
"type": "read-write",
"path": "/System/Core/Examples/Tables/Example01"
}
}
Refresh
Refresh a widget. No message will be send to the target widget. In case you want to send a message to a widget, use a
send
action with the message topic
set to refresh
.
{
"type": "refresh",
"id": "Place the ID of the widget here"
}
The id
field can have the value self
should the action pipeline need to refresh its own widget. It can also be a
route expression allowing widgets embedded in nested tabs compilations to be refreshed.
Send
Widgets data exchange
Sending message from one widget to another can be done using the send
action. This action does not change the output
message. The output message is the same as the input message.
{
"type": "send",
"to": "Place the ID of the widget here"
}
The value of the to
field can be set to self
in case the pipeline needs to send data to its own widget. The update
behavior differs per widget.
Using a route
to point to a widget within a tab
{
"type": "send",
"to": {
"route" : [
"MyTabs", // ID of the Tabs widget.
"Tab01", // ID of the single tab.
"TextWidget" // Pointing to a Text widget which has an ID of 'TextWidget` which.
]
}
}
Supported topics:
-
refresh
: the recipient widget will perform a refresh. (default) -
update
: the receiving widget will perform an update. This will bypass the data source action.
Refresh Topic
The recipient widget will perform a refresh. It will execute the data source action (pipeline) with the provided message
payload
. The refresh life cycle will be performed including a
fetch if a data source is present.
Example to send a message to another component to refresh itself:
{
"type" : "send",
"to" : "Place the ID of the widget here",
"message": {
"topic": "refresh" // Can be omitted because it is default.
"payload": {} // Can be any type of value. Typically an object is used.
}
}
Update Topic
The recipient widget will perform an update. It only updates its known properties with the provided message payload
.
The update life cycle will be performed.
{
"type" : "send",
"to" : "Place the ID of the widget here",
"message": {
"topic": "update",
"payload": {} // Can be any type of value. Typically an object is used.
}
}
Subscribe
Subscribe to object data changes in the system. Typically used in dataSource
configurations. If necessary, use the
willUpdate
action hook to transform the data.
{
"type": "subscribe",
"path": "/System/Core/Examples/Variable"
}
Switch
Execute actions based on rules. A rule will be checked by performing a queryOne
transformation. If
the result of the queryOne transformation is something other than null
the action(s) defined in the case
statement
will be executed. If one rule matches, its action
will be executed and further testing of the subsequent rules will be
stopped.
When checkAll
is set to true
, the 'initial' input message of the switch will be passed to each action pipeline of
the matched rules. The output message of the last executed action pipeline, will be the output message of this switch
action. When no rule matches, the output of the switch action is the same as the input.
To declare a default action which applies when none of the rules match, add a extra rule at the end of the case
array with an empty match
condition.
{
"type": "switch",
"checkAll": false,
"case": [
{
"match" : { // test if payload.temp == 10
"temp": 10
},
"action" : { // Can be a single action or action pipeline.
"type": "action",
"name": "doSomething"
}
},
{
"match" : { // test if payload.temp >= 20
"temp": {
"$gte": 20
}
},
"action" : [ // Can be a single action or action pipeline.
{
"type": "action",
"name": "doSomethingFirst"
},
{
"type": "action",
"name": "doSomethingExtra"
}
]
},
{
"match" : {}, // Default action
"action" : [] // Can be a single action or action pipeline.
}
]
}
In its simplest form, a match expressions tests if the named payload field equals the provided value. An explicit comparison operator can also be used as illustrated above.
Even more complex match conditions can be formulated by using the mongoDB $expr
operator. For example:
{
"match" : {
"$expr": {
"$lt": [ // check if payload.value < payload.maxValue
"$value",
"$maxValue"
]
}
}, // ...
}
TPM-OEE
This action reads table models relating to OEE monitoring from the back end. It is typically used in the dataSource
of either a timeperiodtable
or a table
widget. Depending on the value of the subject
property, the action returns widget schema
, actions
and data
elements for specific OEE tables, which can then be edited in WebStudio.
{
"type": "tpm-oee",
"subject": "big-table_stops",
"path": "/Enterprise/Site/Area-S1-A2/Cell-S1-A2-C1/OEE/Production/Equipment Stops"
}
Name | Description |
---|---|
|
Path to an OEE object. The specific one referred to depends on the selected |
|
The selected
|
Transform
Transformation of data can be performed by means of the MongoDB Aggregation framework. The input data is normally the
value of the payload
field of the input message. In case the whole message object needs to be available to the
transform logic you can set completeMsgObject
to true
.
The aggregation pipeline often returns an array of one or more elements. In most cases, pipeline actions which ingest
the output of the transformation are however looking for a single object. As a convenience the WebStudio specific
aggregateOne
options can be used instead of aggregate
. It return the first element from the resulting
transformation.
Besides aggregate
and aggregateOne
, query
and queryOne
can also be used in scenarios where filtering is
required. As expected queryOne
returns the first matching object.
MongoDB Documentation:
Transform Example 01
This example is to transform the data to an object with a different key.
-
Input message payload:
{
"name": "Company A",
"location": "Eindhoven"
}
-
Transform logic:
{
"type": "transform",
"aggregateOne": [
{
"$project": {
"company": "$name"
}
}
]
}
Output message payload:
{
"company": "Company A"
}
Example to search for the object with 'location' value 'Cologne':
-
Input message payload:
[
{
"name": "Company A",
"location": "Cologne"
},
{
"name": "Company B",
"location": "Eindhoven"
}
]
-
Transform logic:
{
"type": "transform",
"aggregate": [
{
"$match": {
"location": "Cologne"
}
}
]
}
-
Output message payload:
[
{
"name": "Company A",
"location": "Cologne"
}
]
Note: Even though only one element was matched, the returned massage payload is still an array. If aggregateOne
were
used instead of aggregate
, the output would look like this:
{
"name": "Company A",
"location": "Cologne"
}
Transform Example 02
This example uses a query
to search for the object with 'location' value of 'Cologne'.
-
Input message payload:
[
{
"name": "Company A",
"location": "Cologne"
},
{
"name": "Company B",
"location": "Eindhoven"
},
{
"name": "Company C",
"location": "Cologne"
}
]
-
Transform logic:
{
"type": "transform",
"query": {
"location": "Cologne"
}
}
-
Output message payload:
[
{
"name": "Company A",
"location": "Cologne"
},
{
"name": "Company C",
"location": "Cologne"
}
]
Transform Example 03
This example uses queryOne
to search for the object with 'location' value 'Cologne'. Like aggregateOne
, it returns
only the first match.
-
Input message payload:
[
{
"name": "Company A",
"location": "Cologne"
},
{
"name": "Company B",
"location": "Eindhoven"
},
{
"name": "Company C",
"location": "Cologne"
}
]
-
Transform logic:
{
"type": "transform",
"queryOne": {
"location": "Cologne"
}
}
-
Output message payload:
{
"name": "Company A",
"location": "Cologne"
}
Wait
Wait before the next actions will be executed. duration
is in milliseconds.
{
"type": "wait",
"duration": 1000
}
Write
Writes a value to an object in the system. The write action inspects the message payload passed to it, looking for
variables v
, item
or items
which it uses to update the object identified by the path
or p
property, depending on the variant of the action used.
Fixed path with "v" read from message payload
{
"type": "write",
"path": "/System/Core/Examples/Variable"
}
Name | Description |
---|---|
|
Path to the object or property to update. |
|
Value to write. The value is read from the payload of the incoming message. It can be a property of the payload.
The value parameter can also be assign directly to the payload itself.
The write action assigns the |
Write parameters are provided in the "item" property
This version of the write
action can be used to update any of the VQT properties of an object. Only the action type is mandatory. The item
can be statically set or read from the message payload.
{
"type": "write",
"item": {
"p": "/System/Core/Examples/Variable",
"v": 215.24,
"t": "2022-04-27T15:25:10.331Z",
"q": 0 // Quality is GOOD
}
}
Name | Description | |
---|---|---|
|
Details of item to be written to. The property is usually not explicitly configured, be read from the |
|
|
Path to the object or property to update. |
|
|
Value to write. |
|
|
If the timestamp field |
|
|
The OPC quality value can be omitted in most cases in which case 0 (GOOD) is assumed. |
if the write was successful, the message is updated with the values written.
{
"payload": {
"i": 281474983198720,
"p": "/System/Core/Examples/Variable",
"v": 215.24,
"q": 0,
"t": "2022-04-27T15:25:10.331Z",
}
}
Write parameters are provided in the "items" property
This variant of the write action is very similar to the previous one, except that instead of a single item
, an array of items
is provided. The items
can be statically configured or read from the message payload.
{
"type": "write",
"items": [
{
"p": "/System/Core/Examples/Variable",
"v": 215.24,
"t": "2022-04-27T15:25:10.331Z",
"q": 0 // Quality is GOOD
},
{
"p": "/System/Core/Examples/Variable",
"v": 215.24,
"t": "2022-04-27T15:26:13.331Z",
"q": 0 // Quality is GOOD
}
]
}