Star

ThingsBoard Documentation

Documentation for using ThingsBoard IoT Platform.

Rule Node Development Guide

Overview

In this tutorial, you will learn how to create your custom nodes, namely:

image

image

image

image

image

image

Prerequisites

We assume you have completed the following guides and reviewed the articles listed below:

Customization

In order to create new rule node, you should implement the TbNode interface:

package org.thingsboard.rule.engine.api;

...

public interface TbNode {

    void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException;

    void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException;

    void destroy();

}

and annotate your implementation with the following multi-value annotation that refers to the runtime:

org.thingsboard.rule.engine.api.RuleNode 

Also each rule node may have a configuration class that implement NodeConfiguration interface.

package org.thingsboard.rule.engine.api;

public interface NodeConfiguration<T extends NodeConfiguration> {

    T defaultConfiguration();

}
    package org.thingsboard.rule.engine.node.filter;
    
    import lombok.Data;
    import org.thingsboard.rule.engine.api.NodeConfiguration;
    
    @Data
    public class TbKeyFilterNodeConfiguration implements NodeConfiguration<TbKeyFilterNodeConfiguration> {
    
        private String key;
    
        @Override
        public TbKeyFilterNodeConfiguration defaultConfiguration() {
            TbKeyFilterNodeConfiguration configuration = new TbKeyFilterNodeConfiguration();
            configuration.setKey(null);
            return configuration;
        }
    }
    package org.thingsboard.rule.engine.node.transform;
    
    import lombok.Data;
    import org.thingsboard.rule.engine.api.NodeConfiguration;
    
    
    @Data
    public class TbCalculateSumNodeConfiguration implements NodeConfiguration<TbCalculateSumNodeConfiguration> {
    
        private String inputKey;
        private String outputKey;
    
        @Override
        public TbCalculateSumNodeConfiguration defaultConfiguration() {
            TbCalculateSumNodeConfiguration configuration = new TbCalculateSumNodeConfiguration();
            configuration.setInputKey("temperature");
            configuration.setOutputKey("TemperatureSum");
            return configuration;
        }
    }
    package org.thingsboard.rule.engine.node.enrichment;
    
    import lombok.Data;
    import org.thingsboard.rule.engine.api.NodeConfiguration;
    
    @Data
    public class TbGetSumIntoMetadataConfiguration implements NodeConfiguration<TbGetSumIntoMetadataConfiguration> {
    
        private String inputKey;
        private String outputKey;
    
    
        @Override
        public TbGetSumIntoMetadataConfiguration defaultConfiguration() {
            TbGetSumIntoMetadataConfiguration configuration = new TbGetSumIntoMetadataConfiguration();
            configuration.setInputKey("temperature");
            configuration.setOutputKey("TemperatureSum");
            return configuration;
        }
    }

Configuration classes defines in Rule node classes.

Methods flow

In this section, we explain the purpose of each implemented method from TbNode interface:

Method init()

void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException;

Method to initialize rule node after its creation. Body of init method for each above mention rule node is almost the same. We convert the incoming JSON configuration to the specific NodeConfiguration implementation.

    private TbKeyFilterNodeConfiguration config;
    private String key;
            
    @Override
    public void init(TbContext tbContext, TbNodeConfiguration configuration) throws TbNodeException {
        this.config = TbNodeUtils.convert(configuration, TbKeyFilterNodeConfiguration.class);
        key = config.getKey();
    }
    private TbCalculateSumConfiguration config;
    private String inputKey;
    private String outputKey;
    
    @Override
    public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
        this.config = TbNodeUtils.convert(configuration, TbCalculateSumConfiguration.class);
        inputKey = config.getInputKey();
        outputKey = config.getOutputKey();
    }
    private TbGetSumIntoMetadataConfiguration config;
    private String inputKey;
    private String outputKey;
    
    @Override
    public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
        this.config = TbNodeUtils.convert(configuration, TbGetSumIntoMetadataConfiguration.class);
        inputKey = config.getInputKey();
        outputKey = config.getOutputKey();
    }

It is invoked only after rule node creation or updating and takes two input parameters:

ctx.getTelemetryService().saveAndNotify(msg.getOriginator(), tsKvEntryList, ttl, new TelemetryNodeCallback(ctx, msg));
private final JsonNode data;

Method onMsg()

void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException;

Method for processing received messages. It is invoked each time when messages arrive in the node. and also takes two input parameters:

          msg.getData();
          msg.getMetaData();
          msg.getOriginator();
          msg.getType();
          msg.getId();
          msg.getRuleNodeId();
          msg.getRuleChainId();
          msg.getClusterPartition();
          msg.getDataType();

copy message:

        TbMsg copy = msg.copy(UUIDs.timeBased(), entityId, targetId, DEFAULT_CLUSTER_PARTITION);
        
        ***NOTE*** using in cluster mode. 

image image

true & false:

        ctx.tellNext(msg, mapper.readTree(msg.getData()).has(key) ? "True" : "False");

failure:

        ctx.tellFailure(msg, e);
        
        ctx.tellNext(msg, FAILURE, new Exception());

tellSelf and updateSelf methods:

        ctx.tellSelf(tickMsg, curDelay);
        
        ctx.updateSelf(ruleNode);
***NOTE*** Method tellSelf() use in rule nodes that based on a specific delay. Method updateSelf() used on each update the rule node.

Also, TbContext allow to create new message:

        String data = "{temperature:20, humidity:30}"
     
        ctx.newMsg(msg.getType(), msg.getOriginator(), msg.getMetaData(), data);

and transform message:

     ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData());
***NOTE*** The difference between these methods that:
    
       TbMsg newMsg() create a new message with a new messageId;
    
       TbMsg transformMsg() convert an already existing message;

Method destroy()

void destroy();

This method that invoked only after rule node stops or update and don’t have input parameters.

Build

git clone [email protected]:thingsboard/rule-node-examples.git
mvn clean install

Import executable jar-file to your ThingsBoard instance

Import jar-file to your Thingsboard project as dependency library, that should be here:

./target/rule-engine-1.0.0-custom-nodes.jar

Thingsboard using IDE:

Restart ThingsBoard server-side container. Please, refer to the following link to see how to do this: Running server-side container.

**Once ThingsBoard was restarted you need to clear browser cache and refresh the web page to reload UI of Rule Nodes**

Thingsboard as a service:

sudo mv rule-engine-1.0.0-custom-nodes.jar /usr/share/thingsboard/extensions/
sudo chown thingsboard:thingsboard /usr/share/thingsboard/extensions/*

Restart Thingsboard service:

sudo service thingsboard restart

**Once ThingsBoard was restarted you need to clear browser cache and refresh the web page to reload UI of Rule Nodes**

UI configuration

The UI for the ThingsBoard rule nodes was configured with the help of the project that is also hosted on the official github repo. Please, refer to the following link to view how to build the project.

Running Rule Node UI container in hot redeploy mode

To running Rule Node UI container in hot redeploy mode:

cd ${TB_WORK_DIR}/ui/server.js
cd ${TB_RULE_NODE_UI_WORK_DIR}/ui/server.js

This will forward Rule Node UI requests to the server that listen on 3000 port.

Next steps