Merge pull request #8695 from ShvaykaD/feature/rule-nodes-versioning
added field templatization docs for enrichment rule nodes & added java doc for upgrade method in TbVersionedNode interface
@ -20,6 +20,16 @@ import org.thingsboard.server.common.data.util.TbPair;
|
|||||||
|
|
||||||
public interface TbVersionedNode extends TbNode {
|
public interface TbVersionedNode extends TbNode {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upgrades the configuration from a specific version to the current version specified in the
|
||||||
|
* {@link RuleNode} annotation for the instance of {@link TbVersionedNode}.
|
||||||
|
*
|
||||||
|
* @param fromVersion The version from which the configuration needs to be upgraded.
|
||||||
|
* @param oldConfiguration The old configuration to be upgraded.
|
||||||
|
* @return A pair consisting of a Boolean flag indicating the success of the upgrade
|
||||||
|
* and a JsonNode representing the upgraded configuration.
|
||||||
|
* @throws TbNodeException If an error occurs during the upgrade process.
|
||||||
|
*/
|
||||||
TbPair<Boolean, JsonNode> upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException;
|
TbPair<Boolean, JsonNode> upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,6 @@ import org.thingsboard.rule.engine.api.TbNode;
|
|||||||
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
|
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
|
||||||
import org.thingsboard.rule.engine.api.TbNodeException;
|
import org.thingsboard.rule.engine.api.TbNodeException;
|
||||||
import org.thingsboard.rule.engine.api.TbRelationTypes;
|
import org.thingsboard.rule.engine.api.TbRelationTypes;
|
||||||
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
|
|
||||||
import org.thingsboard.server.common.msg.TbMsg;
|
import org.thingsboard.server.common.msg.TbMsg;
|
||||||
import org.thingsboard.server.common.msg.queue.RuleEngineException;
|
import org.thingsboard.server.common.msg.queue.RuleEngineException;
|
||||||
import org.thingsboard.server.common.msg.queue.TbMsgCallback;
|
import org.thingsboard.server.common.msg.queue.TbMsgCallback;
|
||||||
@ -60,25 +59,23 @@ public abstract class TbAbstractTransformNode<C> implements TbNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void transformSuccess(TbContext ctx, TbMsg msg, List<TbMsg> msgs) {
|
protected void transformSuccess(TbContext ctx, TbMsg msg, List<TbMsg> msgs) {
|
||||||
if (msgs != null && !msgs.isEmpty()) {
|
if (msgs == null || msgs.isEmpty()) {
|
||||||
if (msgs.size() == 1) {
|
|
||||||
ctx.tellSuccess(msgs.get(0));
|
|
||||||
} else {
|
|
||||||
TbMsgCallbackWrapper wrapper = new MultipleTbMsgsCallbackWrapper(msgs.size(), new TbMsgCallback() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess() {
|
|
||||||
ctx.ack(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(RuleEngineException e) {
|
|
||||||
ctx.tellFailure(msg, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
msgs.forEach(newMsg -> ctx.enqueueForTellNext(newMsg, TbRelationTypes.SUCCESS, wrapper::onSuccess, wrapper::onFailure));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctx.tellFailure(msg, new RuntimeException("Message or messages list are empty!"));
|
ctx.tellFailure(msg, new RuntimeException("Message or messages list are empty!"));
|
||||||
|
} else if (msgs.size() == 1) {
|
||||||
|
ctx.tellSuccess(msgs.get(0));
|
||||||
|
} else {
|
||||||
|
TbMsgCallbackWrapper wrapper = new MultipleTbMsgsCallbackWrapper(msgs.size(), new TbMsgCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
ctx.ack(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(RuleEngineException e) {
|
||||||
|
ctx.tellFailure(msg, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
msgs.forEach(newMsg -> ctx.enqueueForTellNext(newMsg, TbRelationTypes.SUCCESS, wrapper::onSuccess, wrapper::onFailure));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
Fields templatization feature allows you to process the incoming messages with dynamic configuration by substitution templates specified in the configuration fields with values from message or message metadata.
|
||||||
|
|
||||||
|
There are two types of rule node configuration templates defined:
|
||||||
|
|
||||||
|
- `$[messageKey]` - templates with square brackets used to extract value from the message.
|
||||||
|
|
||||||
|
- `${metadataKey}` - templates with curly brackets used to extract value from the message metadata.
|
||||||
|
|
||||||
|
**Note:** `messageKey` and `metadataKey` are just samples of key names that might exist in the message or metadata.
|
||||||
@ -0,0 +1,107 @@
|
|||||||
|
#### Fields templatization
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
{% include rulenode/common_node_fields_templatization %}
|
||||||
|
|
||||||
|
##### Examples
|
||||||
|
|
||||||
|
Let's assume that we have a customer-based solution where customer manage two type of devices: `temperature` and `humidity` sensors.
|
||||||
|
Additionally, let's assume that customer configured the thresholds settings for each device type.
|
||||||
|
Threshold settings stored as an attributes on a customer level:
|
||||||
|
|
||||||
|
- *temperature_min_threshold* and *temperature_max_threshold* for temperature sensor with values set to *10* and *30* accordingly.
|
||||||
|
- *humidity_min_threshold* and *humidity_max_threshold* for humidity sensor with values set to *70* and *85* accordingly.
|
||||||
|
|
||||||
|
Each message received from device includes `deviceType` property in the message metadata
|
||||||
|
with either `temperature` or `humidity` value according to the sensor type.
|
||||||
|
|
||||||
|
In order to fetch the threshold value for the further message processing you can define next node configuration:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Imagine that you receive message defined below from the `temperature` sensor
|
||||||
|
and forwarded it to the **customer attributes** node with configuration added above.
|
||||||
|
|
||||||
|
- incoming message definition:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"temperature": 32
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "temperature",
|
||||||
|
"deviceName": "TH-001",
|
||||||
|
"ts": "1685379440000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
The same example for the `humidity` sensor:
|
||||||
|
|
||||||
|
- incoming message definition:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"humidity": 77
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "humidity",
|
||||||
|
"deviceName": "HM-001",
|
||||||
|
"ts": "1685379440000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Rule node configuration set to fetch data to the message metadata. In the following way:
|
||||||
|
|
||||||
|
- outgoing message for the `temperature` sensor would be updated to:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"temperature": 32
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "temperature",
|
||||||
|
"deviceName": "TH-001",
|
||||||
|
"ts": "1685379440000",
|
||||||
|
"min_threshold": "10",
|
||||||
|
"max_threshold": "30"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
- outgoing message for the `humidity` sensor would be updated to:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"humidity": 77
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "humidity",
|
||||||
|
"deviceName": "HM-001",
|
||||||
|
"ts": "1685379440000",
|
||||||
|
"min_threshold": "70",
|
||||||
|
"max_threshold": "85"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
These examples showcases using the **customer attributes** node with dynamic configuration based on the substitution of metadata fields.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
@ -1 +0,0 @@
|
|||||||
#### Coming soon!
|
|
||||||
@ -0,0 +1,132 @@
|
|||||||
|
#### Fields templatization
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
{% include rulenode/common_node_fields_templatization %}
|
||||||
|
|
||||||
|
##### Examples
|
||||||
|
|
||||||
|
Let's assume that we have a moisture meter device(message originator) that publishes a telemetry messages that includes the following data readings:
|
||||||
|
|
||||||
|
- `soilMoisture`
|
||||||
|
- `windSpeed`
|
||||||
|
- `windDirection`
|
||||||
|
- `temperature`
|
||||||
|
- `humidity`
|
||||||
|
|
||||||
|
Depending on certain conditions, we might need to fetch additional server-side attributes from the moisture meter device.
|
||||||
|
|
||||||
|
Specifically, if the soil moisture reading drops below a certain threshold, let's say 30%, this is considered critical as it directly impacts crop health and growth.
|
||||||
|
In this case, we need to fetch the `lastIrrigationTime` attribute.
|
||||||
|
This additional information can help us understand when the field was last watered and take necessary action, such as activating the irrigation system.
|
||||||
|
However, if the soil moisture is above this critical threshold, then we need to check another condition.
|
||||||
|
If the wind speed is above a certain level, say 8 m/s, we need to fetch the `lastWindSpeedAlarmTime` attribute.
|
||||||
|
This additional information can help us to understand when the last significant wind event occurred,
|
||||||
|
which might be indicative of an approaching storm or damaging winds.
|
||||||
|
|
||||||
|
Consider that you write a script that depending on a conditions written above will add to the message metadata additional key: `keyToFetch` with one of the next values:
|
||||||
|
|
||||||
|
- `lastIrrigationTime`
|
||||||
|
- `lastWindSpeedAlarmTime`
|
||||||
|
|
||||||
|
In order to fetch value of one of the following keys for the further message processing you can define next node configuration:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- message definition that match first condition after processing in the script node:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"temperature": 26.5,
|
||||||
|
"humidity": 75.2,
|
||||||
|
"soilMoisture": 28.9,
|
||||||
|
"windSpeed": 8.2,
|
||||||
|
"windDirection": "NNE"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "default",
|
||||||
|
"deviceName": "SN-001",
|
||||||
|
"ts": "1685379440000",
|
||||||
|
"keyToFetch": "lastIrrigationTime"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
- message definition that match second condition after processing in the script node:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"temperature": 26.5,
|
||||||
|
"humidity": 75.2,
|
||||||
|
"soilMoisture": 32.5,
|
||||||
|
"windSpeed": 10.4,
|
||||||
|
"windDirection": "NNE"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "default",
|
||||||
|
"deviceName": "SN-001",
|
||||||
|
"ts": "1685379440000",
|
||||||
|
"keyToFetch": "lastWindSpeedAlarmTime"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Rule node configuration set to fetch data to the message metadata. In the following way:
|
||||||
|
|
||||||
|
- outgoing message for the first condition would be updated to:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"temperature": 26.5,
|
||||||
|
"humidity": 75.2,
|
||||||
|
"soilMoisture": 28.9,
|
||||||
|
"windSpeed": 8.2,
|
||||||
|
"windDirection": "NNE"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "default",
|
||||||
|
"deviceName": "SN-001",
|
||||||
|
"ts": "1685379440000",
|
||||||
|
"keyToFetch": "lastIrrigationTime",
|
||||||
|
"ss_lastIrrigationTime": "1685369440000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
- outgoing message for the second condition would be updated to:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"temperature": 26.5,
|
||||||
|
"humidity": 75.2,
|
||||||
|
"soilMoisture": 32.5,
|
||||||
|
"windSpeed": 10.4,
|
||||||
|
"windDirection": "NNE"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "default",
|
||||||
|
"deviceName": "MM-001",
|
||||||
|
"ts": "1685379440000",
|
||||||
|
"keyToFetch": "lastWindSpeedAlarmTime",
|
||||||
|
"ss_lastWindSpeedAlarmTime": "1685359440000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
These examples showcases using the **originator attributes** node with dynamic configuration based on the substitution of metadata fields.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
@ -0,0 +1,106 @@
|
|||||||
|
#### Fields templatization
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
{% include rulenode/common_node_fields_templatization %}
|
||||||
|
|
||||||
|
##### Examples
|
||||||
|
|
||||||
|
Let's assume that we have two device types in our use case:
|
||||||
|
|
||||||
|
- `smart_door_lock`
|
||||||
|
- `motion_detector`
|
||||||
|
|
||||||
|
Let's assume that device of type `dock_lock_sensor` and name `SDL-001` publish next type of messages to the system:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"status": "locked"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceName": "SDL-001",
|
||||||
|
"deviceType": "smart_door_lock",
|
||||||
|
"ts": "1685379440000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
and device of type `motion_detector` and name `MD-001` publish next type of messages to the system:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"motionDetected": "true"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceName": "MD-001",
|
||||||
|
"deviceType": "motion_detector",
|
||||||
|
"ts": "1685379440000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Imagine that you send the messages received from the devices to the external systems
|
||||||
|
and depending on the device type you need add to the message the human-readable label
|
||||||
|
of the device with field name equal to the `deviceType` value from the message metadata.
|
||||||
|
|
||||||
|
Let's assume that devices have next labels:
|
||||||
|
|
||||||
|
- *Grocery warehouse door* for `SDL-001` device.
|
||||||
|
- *Grocery Warehouse motion detector* for `MD-001` device.
|
||||||
|
|
||||||
|
In order to fetch labels and add them to the message with the appropriate field name
|
||||||
|
you can define the next node configuration:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Rule node configuration set to fetch data to the message. In the following way:
|
||||||
|
|
||||||
|
- outgoing message for the `SDL-001` device would be updated to:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"status": "locked",
|
||||||
|
"smart_door_lock": "Grocery warehouse door"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceName": "SDL-001",
|
||||||
|
"deviceType": "smart_door_lock",
|
||||||
|
"ts": "1685379440000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
- outgoing message for the `MD-001` device would be updated to:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"motionDetected": "true",
|
||||||
|
"motion_detector": "Grocery Warehouse motion detector"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceName": "MD-001",
|
||||||
|
"deviceType": "motion_detector",
|
||||||
|
"ts": "1685379440000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
These examples showcases using the **originator fields** node with dynamic configuration based on the substitution of metadata fields.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
@ -0,0 +1,292 @@
|
|||||||
|
#### Fields templatization
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
{% include rulenode/common_node_fields_templatization %}
|
||||||
|
|
||||||
|
##### Examples
|
||||||
|
|
||||||
|
Originator telemetry node support templatization for multiple configuration fields. Namely, you can specify the template in the
|
||||||
|
*Timeseries keys* list, and also there is an option to use the templatization for the fetch interval start and end if you are using *dynamic interval*.
|
||||||
|
|
||||||
|
###### Example 1
|
||||||
|
|
||||||
|
Let's start with an example of using templatization for the *Timeseries keys* list.
|
||||||
|
Imagine that you have a GPS tracker device(message originator) that publishes a telemetry messages that includes the following data readings:
|
||||||
|
|
||||||
|
- `latitude` - current device latitude value.
|
||||||
|
- `longitude` - current device longitude value.
|
||||||
|
- `event` - parameter that specifies the current state of the device. The value might be *parked* or *motion*.
|
||||||
|
|
||||||
|
Additionally let's imagine that devices periodically publishes other telemetry messages that includes additional information such as:
|
||||||
|
|
||||||
|
- `speed` - current speed value.
|
||||||
|
- `direction` - compass direction in which the device is moving.
|
||||||
|
- `acceleration` - how quickly the speed of the device is changing.
|
||||||
|
- `fuel_level` - current fuel level.
|
||||||
|
- `battery_level` - current battery level.
|
||||||
|
- `parked_location` - precise location where the device is parked.
|
||||||
|
- `parked_duration` - current park duration value.
|
||||||
|
- `parked_time` - timestamp when the device was parked.
|
||||||
|
|
||||||
|
Let's imagine that we need to make some historical analysis by fetching 3 latest telemetry readings for the keys listed below if the `event` value is set to *motion*:
|
||||||
|
|
||||||
|
- `speed`
|
||||||
|
- `direction`
|
||||||
|
- `acceleration`
|
||||||
|
- `fuel_level`
|
||||||
|
- `battery_level`
|
||||||
|
|
||||||
|
Otherwise, if the `event` value is set to *parked* value we need to fetch 3 latest telemetry readings for the following data keys:
|
||||||
|
|
||||||
|
- `parked_location`
|
||||||
|
- `parked_duration`
|
||||||
|
- `parked_time`
|
||||||
|
- `fuel_level`
|
||||||
|
- `battery_level`
|
||||||
|
|
||||||
|
Imagine that you created a script node that depending on the `event` value adds to the message metadata appropriate keyToFetch fields.
|
||||||
|
|
||||||
|
- message definition that match condition when `event` is set to *motion* value after processing in the script node:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"latitude": "40.730610",
|
||||||
|
"longitude": "-73.935242",
|
||||||
|
"event": "motion"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceName": "GPS-001",
|
||||||
|
"deviceType": "GPS Tracker",
|
||||||
|
"ts": "1685479440000",
|
||||||
|
"keyToFetch1": "speed",
|
||||||
|
"keyToFetch2": "direction",
|
||||||
|
"keyToFetch3": "acceleration"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
- message definition that match condition when `event` is set to *parked* value after processing in the script node:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"latitude": "40.730610",
|
||||||
|
"longitude": "-73.935242",
|
||||||
|
"event": "parked"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceName": "GPS-001",
|
||||||
|
"deviceType": "GPS Tracker",
|
||||||
|
"ts": "1685379440000",
|
||||||
|
"keyToFetch1": "parked_location",
|
||||||
|
"keyToFetch2": "parked_duration",
|
||||||
|
"keyToFetch3": "parked_time"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
In order to fetch the additional telemetry key values to make some historical analysis of the tracker's state you can define the next node configuration:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Rule node configuration is set to retrieve the telemetry from the fetch interval with configurable query parameters that you can check above.
|
||||||
|
So let's imagine that 3 latest values for the keys that we are going to fetch are:
|
||||||
|
|
||||||
|
- `speed` - 5.2, 15.7, 30.2 (mph).
|
||||||
|
- `direction` - N(North), NE(North-East), E(East).
|
||||||
|
- `acceleration` - 2.2, 2.4, 2.5 (m/s²).
|
||||||
|
- `fuel_level` - 61.5, 57.4, 55.6 (%).
|
||||||
|
- `battery_level` - 88.1, 87.8, 87.2 (%).
|
||||||
|
- `parked_location` - dr5rtwceb (geohash). Same value for 3 latest data readings.
|
||||||
|
- `parked_duration` - 6300000, 7300000, 8300000 (ms).
|
||||||
|
- `parked_time` - 1685339240000 (ms). Same value for 3 latest data readings.
|
||||||
|
|
||||||
|
In the following way:
|
||||||
|
|
||||||
|
- outgoing message for the case when the `event` has value *motion* would be look like this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"latitude": "40.730610",
|
||||||
|
"longitude": "-73.935242",
|
||||||
|
"event": "motion"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceName": "GPS-001",
|
||||||
|
"deviceType": "GPS Tracker",
|
||||||
|
"ts": "1685479440000",
|
||||||
|
"keyToFetch1": "speed",
|
||||||
|
"keyToFetch2": "direction",
|
||||||
|
"keyToFetch3": "acceleration",
|
||||||
|
"speed": "[{\"ts\":1685476840000,\"value\":5.2},{\"ts\":1685477840000,\"value\":15.7},{\"ts\":1685478840000,\"value\":30.2}]",
|
||||||
|
"direction": "[{\"ts\":1685476840000,\"value\":\"N\"},{\"ts\":1685477840000,\"value\":\"NE\"},{\"ts\":1685478840000,\"value\":\"N\"}]",
|
||||||
|
"acceleration": "[{\"ts\":1685476840000,\"value\":2.2},{\"ts\":1685477840000,\"value\":2.4},{\"ts\":1685478840000,\"value\":2.5}]",
|
||||||
|
"fuel_level": "[{\"ts\":1685476840000,\"value\":61.5},{\"ts\":1685477840000,\"value\":57.4},{\"ts\":1685478840000,\"value\":55.6}]",
|
||||||
|
"battery_level": "[{\"ts\":1685476840000,\"value\":88.1},{\"ts\":1685477840000,\"value\":87.8},{\"ts\":1685478840000,\"value\":87.2}]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
- outgoing message for the case when the `event` has value *parked* would be look like this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"latitude": "40.730610",
|
||||||
|
"longitude": "-73.935242",
|
||||||
|
"event": "parked"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceName": "GPS-001",
|
||||||
|
"deviceType": "GPS Tracker",
|
||||||
|
"ts": "1685379440000",
|
||||||
|
"keyToFetch1": "parked_location",
|
||||||
|
"keyToFetch2": "parked_duration",
|
||||||
|
"keyToFetch3": "parked_time",
|
||||||
|
"parked_location": "[{\"ts\":1685376840000,\"value\":\"dr5rtwceb\"},{\"ts\":1685377840000,\"value\":\"dr5rtwceb\"},{\"ts\":1685378840000,\"value\":\"dr5rtwceb\"}]",
|
||||||
|
"parked_duration": "[{\"ts\":1685376840000,\"value\":6300000},{\"ts\":1685377840000,\"value\":7300000},{\"ts\":1685378840000,\"value\":8300000}]",
|
||||||
|
"parked_time": "[{\"ts\":1685376840000,\"value\":1685376840000},{\"ts\":1685377840000,\"value\":1685377840000},{\"ts\":1685378840000,\"value\":1685378840000}]",
|
||||||
|
"fuel_level": "[{\"ts\":1685376840000,\"value\":61.5},{\"ts\":1685377840000,\"value\":57.4},{\"ts\":1685378840000,\"value\":55.6}]",
|
||||||
|
"battery_level": "[{\"ts\":1685376840000,\"value\":88.1},{\"ts\":1685377840000,\"value\":87.8},{\"ts\":1685378840000,\"value\":87.2}]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Example 2
|
||||||
|
|
||||||
|
This example will extend the previous example with additional condition:
|
||||||
|
|
||||||
|
Imagine that you need to specify the fetch interval dynamically from the 1 hour ago till the current time.
|
||||||
|
Additionally let's assume that the current time can be extracted from `ts` field that we have in the message metadata on each message received.
|
||||||
|
While the value of (1 hour ago) can be calculated in the script node that we use for adding keyToFetch fields into metadata.
|
||||||
|
|
||||||
|
In the following way:
|
||||||
|
|
||||||
|
- message definition that match condition when `event` is set to *motion* value after processing in the script node:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"latitude": "40.730610",
|
||||||
|
"longitude": "-73.935242",
|
||||||
|
"event": "motion"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceName": "GPS-001",
|
||||||
|
"deviceType": "GPS Tracker",
|
||||||
|
"ts": "1685479440000",
|
||||||
|
"keyToFetch1": "speed",
|
||||||
|
"keyToFetch2": "direction",
|
||||||
|
"keyToFetch3": "acceleration",
|
||||||
|
"dynamicIntervalStart": "1685475840000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- message definition that match condition when `event` is set to *parked* value after processing in the script node:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"latitude": "40.730610",
|
||||||
|
"longitude": "-73.935242",
|
||||||
|
"event": "parked"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceName": "GPS-001",
|
||||||
|
"deviceType": "GPS Tracker",
|
||||||
|
"ts": "1685379440000",
|
||||||
|
"keyToFetch1": "parked_location",
|
||||||
|
"keyToFetch2": "parked_duration",
|
||||||
|
"keyToFetch3": "parked_time",
|
||||||
|
"dynamicIntervalStart": "1685375840000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
In order to fetch the data using dynamic interval we need enable *Use dynamic interval* option in the rule node configuration and specify the templates for the *Interval start* and *Interval end*:
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Other configuration wasn't change from our previous example.
|
||||||
|
In the following way:
|
||||||
|
|
||||||
|
- outgoing message for the case when the `event` has value *motion* would be look like this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"latitude": "40.730610",
|
||||||
|
"longitude": "-73.935242",
|
||||||
|
"event": "motion"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceName": "GPS-001",
|
||||||
|
"deviceType": "GPS Tracker",
|
||||||
|
"ts": "1685479440000",
|
||||||
|
"keyToFetch1": "speed",
|
||||||
|
"keyToFetch2": "direction",
|
||||||
|
"keyToFetch3": "acceleration",
|
||||||
|
"dynamicIntervalStart": "1685475840000",
|
||||||
|
"speed": "[{\"ts\":1685476840000,\"value\":5.2},{\"ts\":1685477840000,\"value\":15.7},{\"ts\":1685478840000,\"value\":30.2}]",
|
||||||
|
"direction": "[{\"ts\":1685476840000,\"value\":\"N\"},{\"ts\":1685477840000,\"value\":\"NE\"},{\"ts\":1685478840000,\"value\":\"N\"}]",
|
||||||
|
"acceleration": "[{\"ts\":1685476840000,\"value\":2.2},{\"ts\":1685477840000,\"value\":2.4},{\"ts\":1685478840000,\"value\":2.5}]",
|
||||||
|
"fuel_level": "[{\"ts\":1685476840000,\"value\":61.5},{\"ts\":1685477840000,\"value\":57.4},{\"ts\":1685478840000,\"value\":55.6}]",
|
||||||
|
"battery_level": "[{\"ts\":1685476840000,\"value\":88.1},{\"ts\":1685477840000,\"value\":87.8},{\"ts\":1685478840000,\"value\":87.2}]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
- outgoing message for the case when the `event` has value *parked* would be look like this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"latitude": "40.730610",
|
||||||
|
"longitude": "-73.935242",
|
||||||
|
"event": "parked"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceName": "GPS-001",
|
||||||
|
"deviceType": "GPS Tracker",
|
||||||
|
"ts": "1685379440000",
|
||||||
|
"keyToFetch1": "parked_location",
|
||||||
|
"keyToFetch2": "parked_duration",
|
||||||
|
"keyToFetch3": "parked_time",
|
||||||
|
"dynamicIntervalStart": "1685375840000",
|
||||||
|
"parked_location": "[{\"ts\":1685376840000,\"value\":\"dr5rtwceb\"},{\"ts\":1685377840000,\"value\":\"dr5rtwceb\"},{\"ts\":1685378840000,\"value\":\"dr5rtwceb\"}]",
|
||||||
|
"parked_duration": "[{\"ts\":1685376840000,\"value\":6300000},{\"ts\":1685377840000,\"value\":7300000},{\"ts\":1685378840000,\"value\":8300000}]",
|
||||||
|
"parked_time": "[{\"ts\":1685376840000,\"value\":1685376840000},{\"ts\":1685377840000,\"value\":1685377840000},{\"ts\":1685378840000,\"value\":1685378840000}]",
|
||||||
|
"fuel_level": "[{\"ts\":1685376840000,\"value\":61.5},{\"ts\":1685377840000,\"value\":57.4},{\"ts\":1685378840000,\"value\":55.6}]",
|
||||||
|
"battery_level": "[{\"ts\":1685376840000,\"value\":88.1},{\"ts\":1685377840000,\"value\":87.8},{\"ts\":1685378840000,\"value\":87.2}]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
These examples showcases using the **originator telemetry** node with dynamic configuration based on the substitution of metadata fields.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
@ -0,0 +1,132 @@
|
|||||||
|
#### Fields templatization
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
{% include rulenode/common_node_fields_templatization %}
|
||||||
|
|
||||||
|
##### Examples
|
||||||
|
|
||||||
|
Let's assume that we have a moisture meter device(message originator) that publishes a telemetry messages that includes the following data readings:
|
||||||
|
|
||||||
|
- `soilMoisture`
|
||||||
|
- `windSpeed`
|
||||||
|
- `windDirection`
|
||||||
|
- `temperature`
|
||||||
|
- `humidity`
|
||||||
|
|
||||||
|
Depending on certain conditions, we might need to fetch additional server-side attributes from the related irrigation controller device.
|
||||||
|
|
||||||
|
Specifically, if the soil moisture reading drops below a certain threshold, let's say 30%, this is considered critical as it directly impacts crop health and growth.
|
||||||
|
In this case, we need to fetch the `lastIrrigationTime` attribute.
|
||||||
|
This additional information can help us understand when the field was last watered and take necessary action, such as activating the irrigation system.
|
||||||
|
However, if the soil moisture is above this critical threshold, then we need to check another condition.
|
||||||
|
If the wind speed is above a certain level, say 8 m/s, we might need to fetch the `lastIrrigationPauseTime` attribute.
|
||||||
|
This additional information can help us understand when the last time the irrigation system was paused due to high wind conditions,
|
||||||
|
which might help to make more informed decisions about when and how to irrigate, considering both current and historical weather conditions.
|
||||||
|
|
||||||
|
Consider that you write a script that depending on a conditions written above will add to the message metadata additional key: `keyToFetch` with one of the next values:
|
||||||
|
|
||||||
|
- `lastIrrigationTime`
|
||||||
|
- `lastIrrigationPauseTime`
|
||||||
|
|
||||||
|
In order to fetch value of one of the following keys for the further message processing you can define next node configuration:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- message definition that match first condition after processing in the script node:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"temperature": 26.5,
|
||||||
|
"humidity": 75.2,
|
||||||
|
"soilMoisture": 28.9,
|
||||||
|
"windSpeed": 8.2,
|
||||||
|
"windDirection": "NNE"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "default",
|
||||||
|
"deviceName": "MM-001",
|
||||||
|
"ts": "1685379440000",
|
||||||
|
"keyToFetch": "lastIrrigationTime"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
- message definition that match second condition after processing in the script node:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"temperature": 26.5,
|
||||||
|
"humidity": 75.2,
|
||||||
|
"soilMoisture": 32.5,
|
||||||
|
"windSpeed": 10.4,
|
||||||
|
"windDirection": "NNE"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "default",
|
||||||
|
"deviceName": "MM-001",
|
||||||
|
"ts": "1685379440000",
|
||||||
|
"keyToFetch": "lastIrrigationPauseTime"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Rule node configuration set to fetch data to the message metadata. In the following way:
|
||||||
|
|
||||||
|
- outgoing message for the first condition would be updated to:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"temperature": 26.5,
|
||||||
|
"humidity": 75.2,
|
||||||
|
"soilMoisture": 28.9,
|
||||||
|
"windSpeed": 8.2,
|
||||||
|
"windDirection": "NNE"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "default",
|
||||||
|
"deviceName": "MM-001",
|
||||||
|
"ts": "1685379440000",
|
||||||
|
"keyToFetch": "lastIrrigationTime",
|
||||||
|
"ss_lastIrrigationTime": "1685369440000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
- outgoing message the second condition would be updated to:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"temperature": 26.5,
|
||||||
|
"humidity": 75.2,
|
||||||
|
"soilMoisture": 32.5,
|
||||||
|
"windSpeed": 10.4,
|
||||||
|
"windDirection": "NNE"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "default",
|
||||||
|
"deviceName": "MM-001",
|
||||||
|
"ts": "1685379440000",
|
||||||
|
"keyToFetch": "lastIrrigationPauseTime",
|
||||||
|
"ss_lastIrrigationPauseTime": "1685359440000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
These examples showcases using the **related device attributes** node with dynamic configuration based on the substitution of metadata fields.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
@ -0,0 +1,111 @@
|
|||||||
|
#### Fields templatization
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
{% include rulenode/common_node_fields_templatization %}
|
||||||
|
|
||||||
|
##### Examples
|
||||||
|
|
||||||
|
Let's consider a scenario where we possess an asset that serves as a warehouse
|
||||||
|
and is responsible for overseeing two categories of devices:
|
||||||
|
|
||||||
|
- sensors measuring `temperature`.
|
||||||
|
- sensors measuring `humidity`.
|
||||||
|
|
||||||
|
Additionally, let's assume that this asset has configured thresholds set as attributes for each device type:
|
||||||
|
|
||||||
|
- *temperature_min_threshold* and *temperature_max_threshold* for temperature sensor with values set to *10* and *30* accordingly.
|
||||||
|
- *humidity_min_threshold* and *humidity_max_threshold* for humidity sensor with values set to *70* and *85* accordingly.
|
||||||
|
|
||||||
|
Each message received from device includes `deviceType` property in the message metadata
|
||||||
|
with either `temperature` or `humidity` value according to the sensor type.
|
||||||
|
|
||||||
|
In order to fetch the threshold value for the further message processing you can define next node configuration:
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
Imagine that you receive message defined below from the `temperature` sensor
|
||||||
|
and forwarded it to the **related entity data** node with configuration added above.
|
||||||
|
|
||||||
|
- incoming message definition:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"temperature": 32
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "temperature",
|
||||||
|
"deviceName": "TH-001",
|
||||||
|
"ts": "1685379440000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
The same example for the `humidity` sensor:
|
||||||
|
|
||||||
|
- incoming message definition:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"humidity": 77
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "humidity",
|
||||||
|
"deviceName": "HM-001",
|
||||||
|
"ts": "1685379440000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Rule node configuration set to fetch data to the message metadata. In the following way:
|
||||||
|
|
||||||
|
- outgoing message for the `temperature` sensor would be updated to:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"temperature": 32
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "temperature",
|
||||||
|
"deviceName": "TH-001",
|
||||||
|
"ts": "1685379440000",
|
||||||
|
"min_threshold": "10",
|
||||||
|
"max_threshold": "30"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
- outgoing message for the `humidity` sensor would be updated to:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"humidity": 77
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "humidity",
|
||||||
|
"deviceName": "HM-001",
|
||||||
|
"ts": "1685379440000",
|
||||||
|
"min_threshold": "70",
|
||||||
|
"max_threshold": "85"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
These examples showcases using the **related entity data** node with dynamic configuration based on the substitution of metadata fields.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
@ -0,0 +1,107 @@
|
|||||||
|
#### Fields templatization
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
{% include rulenode/common_node_fields_templatization %}
|
||||||
|
|
||||||
|
##### Examples
|
||||||
|
|
||||||
|
Let's assume that tenant manage two type of devices: `temperature` and `humidity` sensors.
|
||||||
|
Additionally, let's assume that tenant configured the thresholds settings for each device type.
|
||||||
|
Threshold settings stored as an attributes on a tenant level:
|
||||||
|
|
||||||
|
- *temperature_min_threshold* and *temperature_max_threshold* for temperature sensor with values set to *10* and *30* accordingly.
|
||||||
|
- *humidity_min_threshold* and *humidity_max_threshold* for humidity sensor with values set to *70* and *85* accordingly.
|
||||||
|
|
||||||
|
Each message received from device includes `deviceType` property in the message metadata
|
||||||
|
with either `temperature` or `humidity` value according to the sensor type.
|
||||||
|
|
||||||
|
In order to fetch the threshold value for the further message processing you can define next node configuration:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Imagine that you receive message defined below from the `temperature` sensor
|
||||||
|
and forwarded it to the **tenant attributes** node with configuration added above.
|
||||||
|
|
||||||
|
- incoming message definition:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"temperature": 32
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "temperature",
|
||||||
|
"deviceName": "TH-001",
|
||||||
|
"ts": "1685379440000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
The same example for the `humidity` sensor:
|
||||||
|
|
||||||
|
- incoming message definition:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"humidity": 77
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "humidity",
|
||||||
|
"deviceName": "HM-001",
|
||||||
|
"ts": "1685379440000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Rule node configuration set to fetch data to the message metadata. In the following way:
|
||||||
|
|
||||||
|
- outgoing message for the `temperature` sensor would be updated to:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"temperature": 32
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "temperature",
|
||||||
|
"deviceName": "TH-001",
|
||||||
|
"ts": "1685379440000",
|
||||||
|
"min_threshold": "10",
|
||||||
|
"max_threshold": "30"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
- outgoing message for the `humidity` sensor would be updated to:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"humidity": 77
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"deviceType": "humidity",
|
||||||
|
"deviceName": "HM-001",
|
||||||
|
"ts": "1685379440000",
|
||||||
|
"min_threshold": "70",
|
||||||
|
"max_threshold": "85"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
These examples showcases using the **tenant attributes** node with dynamic configuration based on the substitution of metadata fields.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 81 KiB |