ThingsBoard Documentation

Documentation for using ThingsBoard IoT Platform.
Open documentation for releases before 1.3

Extensions (Plugins and Actions) Development Guide

Introduction

ThingsBoard extensions are custom modules that provide additional functionality to the ThingsBoard core components.

They can be easily developed based on your business needs.

For example, a custom extension can be added to send messages from ThingsBoard to any other external system once ThingsBoard receives telemetry (timeseries or attributes). Additionally, an extension can apply some transformations to the telemetry before forwarding a message.

Alternatively, an extension can forward the same device telemetry data to your RESTfull microservice. The microservice itself does the stream analytics on data and pushes the aggregated result back to ThingsBoard as new telemetry data for this device.

Here is the high level design flow of ThingsBoard extensions:

image

Design

Extensions are designed as additional components that can be easily added to ThingsBoard.

In order to achieve this goal, extensions are implemented as separate project dependencies.

You can check the list of the extensions that are included into ThingsBoard by default here.

ThingsBoard scans the dependency classes, and adds new extensions that are marked with the predefined set of ThingsBoard Plugin and Action Annotations to the platform. This is done during the application startup.

Adding an extension is as easy as annotating custom classes with special annotations and adding your jar file to the classpath of the ThingsBoard server instance. Everything else will be done by ThingsBoard.

The Approach that we prefer to use in the thingsboard.io and strongly advise to follow is every new extension that is added has to be a separate maven module that resides in extensions sub-module of the core module of ThingsBoard project.

This new maven module should be added as a dependency to the application module dependencies.

Here is an example how Kafka Extension can be added as dependency to application/pom.xml file:

...
<dependencies>
    ...
    <dependency>
        <groupId>org.thingsboard.extensions</groupId>
        <artifactId>extension-kafka</artifactId>
        <classifier>extension</classifier>
    </dependency>
</dependencies>
...

During the start-up, ThingsBoard will parse classes in the classpath, find the ones that are annotated with custom ThingsBoard Plugin and Action Annotations and add a new extension to the platform.

The extension becomes ready and can be used by users.

API details

In order to properly build a new extension you’ll need to develop Action and Plugin functionality using the predefined set of Classes, Interfaces and Annotations. Also, you’ll need to implement JSON descriptors for the UI forms of the Plugin and Action components.

We are going to provide the details on how to create new extensions to the system based on the REST API Call Extension that is already placed in the system.

Extension is responsible for sending HTTP requests to specific endpoints. Here is extension code.

Developing of the extension consists of two stages:

Server-side classes development

The server-side development contains two parts - Action and Plugin classes coding.

image

Action Development Guide

Action is responsible for processing messages that have been sent from device (timeseries or attribute data) and converting it into a Java object (an object must implement RuleToPluginMsg interface) that is going to be processed by Plugin. This Java object contains payload data that is created from the device telemetry message data (timeseries or attribute) and Action configuration. The payload contains data that is going to be sent to external systems, for example, message body for the email or message that is going to be sent to Kafka topic.

Here are particular samples:

To create Action for specific extension, you’ll need to create class that implements org.thingsboard.server.extensions.api.plugins.PluginAction interface:

public interface PluginAction<T> extends ConfigurableComponent<T>, RuleLifecycleComponent {
    Optional<RuleToPluginMsg<?>> convert(RuleContext ctx, ToDeviceActorMsg toDeviceActorMsg, RuleProcessingMetaData deviceMsgMd);
    Optional<ToDeviceMsg> convert(PluginToRuleMsg<?> response);
    boolean isOneWayAction();
}

and org.thingsboard.server.extensions.api.rules.RuleLifecycleComponent interface:

public interface RuleLifecycleComponent {
    void resume();
    void suspend();
    void stop();
}

This class is the core of your Action and here you should implement logic where you will create a Java object (an object that implements RuleToPluginMsg interface). This object is going to be passed to Plugin for processing.

Action class must be annotated with org.thingsboard.server.extensions.api.component.Action so ThingsBoard will correctly process this class during start-up.

In case of REST API Call Extension sample class is org.thingsboard.server.extensions.rest.action.RestApiCallPluginAction. In this extension Action is responsible for creating a Java object that contains information regarding the REST request.

A Java object that Action creates is a simple instance of a POJO Java class that holds the necessary information.

In case of REST API Call Extension sample class is org.thingsboard.server.extensions.rest.action.RestApiCallActionPayload. It holds information regarding REST request - body, http method, result code, etc. This class must implement Serializable interface to be able to be transferred to Plugin.

Payload object above must be wrapped to a class that extends org.thingsboard.server.extensions.api.plugins.msg.AbstractRuleToPluginMsg class. Basically, this is needed to pass objects between the Plugin and Action components and contains technical metadata.

To finish with Action implementation you’ll need to add POJO Java class that contains the configuration of the UI Action form. It contains fields that are mapped to the UI form of Action. In this form, user will provide details regarding the configuration of your Action.

In case of REST API Call Extension sample class is org.thingsboard.server.extensions.rest.action.RestApiCallPluginActionConfiguration. It holds information regarding the template of the REST request, expected result code etc. Please refer to the UI development part to get details regarding how UI forms are generated.

Plugin Development Guide

A Plugin is responsible for receiving a Java object (implements org.thingsboard.server.extensions.api.plugins.msgRuleToPluginMsg) that was created by Action and process it accordingly. This processing depends on your needs and could be anything like:

Development of Plugin starts from creating a class that implements org.thingsboard.server.extensions.api.plugins.Plugin interface:

public interface Plugin<T> extends ConfigurableComponent<T> {
    void process(PluginContext ctx, PluginWebsocketMsg<?> wsMsg);
    void process(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg<?> msg) throws RuleException;
    void process(PluginContext ctx, PluginRestMsg msg);
    void process(PluginContext ctx, RpcMsg msg);
    void process(PluginContext ctx, FromDeviceRpcResponse msg);
    void process(PluginContext ctx, TimeoutMsg<?> msg);
    void onServerAdded(PluginContext ctx, ServerAddress server);
    void onServerRemoved(PluginContext ctx, ServerAddress server);
    void resume(PluginContext ctx);
    void suspend(PluginContext ctx);
    void stop(PluginContext ctx);
}

This class is the core of your Plugin and here you should implement the logic in order to init the plugin correctly.

It must be annotated with org.thingsboard.server.extensions.api.component.Plugin annotation so ThingsBoard will correctly process this class during start-up.

In case of REST API Call Extension sample class is org.thingsboard.server.extensions.rest.plugin.RestApiCallPlugin. In this extension Plugin is responsible for setting URL and headers for the REST request.

Alos, you need to create class that implements org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler interface.

This class is actually doing ‘real’ work - send messages to HTTP endpoints, Kafka topics, etc.

In case of REST API Call Extension the sample class is org.thingsboard.server.extensions.rest.plugin.RestApiCallMsgHandler. This class is responsible for creating Spring REST template and sending HTTP requests.

To finish with Plugin implementation you’ll need to add a POJO Java class that contains the configuration of the UI Plugin form. It contains fields that are mapped to the UI form of Plugin. In this form, user will provide details regarding the configuration of your Plugin.

In case of REST API Call Extension sample class is org.thingsboard.server.extensions.rest.plugin.RestApiCallPluginConfiguration. It holds information regarding the host of the REST endpoint, base path, authentication details, etc. Please refer to the UI development part to get details regarding how UI forms are generated.

UI JSON descriptors development

Plugin and Action UI forms are auto-generated using react-schema-form builder. It allows building UI forms from pre-defined JSON configuration. JSON file contains description for the schema and form components. Schema and form components are used to describe what types of elements will be on the form and how are they located.

NOTE For the drop-down box with predefined set of options, but without multi-select feature use this sample configuration.

For the extension you’ll need to define two UI configurations - Plugin and Action JSON descriptor configurations.

Plugin JSON descriptor configuration

This JSON file contains schema and form definition for the Plugin form generation. It must be located in the resources folder. This file will be later refernced as ${PLUGIN_FORM_DESCRIPTOR_JSON_FILE}.

Here is a sample:

{
  "schema": {
    "title": "REST API Call Plugin Configuration",
    "type": "object",
    "properties": {
      "host": {
        "title": "Host",
        "type": "string"
      },
      "port": {
        "title": "Port",
        "type": "integer",
        "default": 8080,
        "minimum": 0,
        "maximum": 65536
      },
      "basePath": {
        "title": "Base Path",
        "type": "string",
        "default": "/"
      },
      "authMethod": {
        "title": "Authentication method",
        "type": "string"
      },
      "userName": {
        "title": "Username",
        "type": "string"
      },
      "password": {
        "title": "Password",
        "type": "string"
      },
      "headers": {
        "title": "Request Headers",
        "type": "array",
        "items": {
          "title": "Request Header",
          "type": "object",
          "properties": {
            "key": {
              "title": "Key",
              "type": "string"
            },
            "value": {
              "title": "Value",
              "type": "string"
            }
          }
        }
      }
    },
    "required": [
      "host",
      "port",
      "basePath",
      "authMethod"
    ]
  },
  "form": [
    "host",
    "port",
    "basePath",
    {
      "key": "authMethod",
      "type": "rc-select",
      "multiple": false,
      "items": [
        {
          "value": "NO_AUTH",
          "label": "No authentication"
        },
        {
          "value": "BASIC_AUTH",
          "label": "Basic authentication"
        }
      ]
    },
    "userName",
    {
      "key": "password",
      "type": "password"
    },
    "headers"
  ]
}

JSON Action Descriptor Configuration

This JSON file contains schema and form definition for the Action form generation. It has to be located in the resources folder. This file will be refernced later as ${ACTION_FORM_DESCRIPTOR_JSON_FILE}.

Here is a sample:

{
  "schema": {
    "title": "REST API Call Action Configuration",
    "type": "object",
    "properties": {
      "sync": {
        "title": "Requires delivery confirmation",
        "type": "boolean"
      },
      "template": {
        "title": "Body Template",
        "type": "string"
      },
      "actionPath": {
        "title": "Action Path",
        "type": "string",
        "default": "/"
      },
      "requestMethod": {
        "title": "Request method",
        "type": "string"
      },
      "expectedResultCode": {
        "title": "Expected Result Code",
        "type": "integer"
      }
    },
    "required": [
      "sync",
      "template",
      "actionPath",
      "expectedResultCode",
      "requestMethod"
    ]
  },
  "form": [
    "sync",
    {
      "key": "template",
      "type": "textarea",
      "rows": 5
    },
    "actionPath",
    {
      "key": "requestMethod",
      "type": "rc-select",
      "multiple": false,
      "items": [
        {
          "value": "POST",
          "label": "POST"
        },
        {
          "value": "PUT",
          "label": "PUT"
        }
      ]
    },
    "expectedResultCode"
  ]
}

Here is sample of the REST API Call Extension Plugin and Action form configuration files location:

image

Once the JSON files are created and placed in the resources directory, the Plugin and Action annotations have to be set with the corresponding JSON file names:

@Plugin(name = "${PLUGIN_NAME}", actions = {"${ACTION_CLASS_NAME}".class},
        descriptor = "${PLUGIN_FORM_DESCRIPTOR_JSON_FILE}", configuration = "${PLUGIN_CONFIGURATION_CLASS_NAME}".class)
@Action(name = "${ACTION_NAME}",
        descriptor = "${ACTION_FORM_DESCRIPTOR_JSON_FILE}", configuration =  "${ACTION_CONFIGURATION_CLASS_NAME}".class)

Sample for REST API Call Extension:

@Plugin(name = "REST API Call Plugin", actions = {RestApiCallPluginAction.class},
        descriptor = "RestApiCallPluginDescriptor.json", configuration = RestApiCallPluginConfiguration.class)
@Slf4j
public class RestApiCallPlugin extends AbstractPlugin<RestApiCallPluginConfiguration> {
@Action(name = "REST API Call Plugin Action",
        descriptor = "RestApiCallActionDescriptor.json", configuration = RestApiCallPluginActionConfiguration.class)
@Slf4j
public class RestApiCallPluginAction extends AbstractTemplatePluginAction<RestApiCallPluginActionConfiguration> {

Extension testing and verification

Now it’s time to start ThingsBoard and verify that extension works as you expected.

Here is configuration and verification of REST API Call Extension.

The same steps could be applied for the newly added extension, but taking into account new extension configuration and functionality.