thingsboard-aplcache

Merge remote-tracking branch 'origin/develop/1.5' into

5/9/2018 5:56:32 AM

Changes

application/src/main/data/json/demo/plugins/demo_device_messaging_rpc_plugin.json 13(+0 -13)

application/src/main/data/json/demo/plugins/demo_email_plugin.json 28(+0 -28)

application/src/main/data/json/demo/plugins/demo_time_rpc_plugin.json 11(+0 -11)

application/src/main/data/json/demo/rules/demo_alarm_rule.json 46(+0 -46)

application/src/main/data/json/demo/rules/demo_gettime_rpc_rule.json 35(+0 -35)

application/src/main/data/json/demo/rules/demo_messaging_rpc_rule.json 38(+0 -38)

application/src/main/data/json/system/plugins/system_rpc_plugin.json 11(+0 -11)

application/src/main/data/json/system/plugins/system_telemetry_plugin.json 9(+0 -9)

application/src/main/data/json/system/rules/system_telemetry_rule.json 29(+0 -29)

application/src/test/java/org/thingsboard/server/actors/DefaultActorServiceTest.java 245(+0 -245)

application/src/test/java/org/thingsboard/server/actors/DummySessionID.java 63(+0 -63)

application/src/test/java/org/thingsboard/server/controller/BasePluginControllerTest.java 232(+0 -232)

application/src/test/java/org/thingsboard/server/controller/BaseRuleControllerTest.java 247(+0 -247)

extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/handlers/RpcRestMsgHandler.java 161(+0 -161)

extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/handlers/RpcRuleMsgHandler.java 102(+0 -102)

extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcManager.java 69(+0 -69)

extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcPlugin.java 86(+0 -86)

pom.xml 6(+6 -0)

Details

diff --git a/application/src/main/conf/thingsboard.conf b/application/src/main/conf/thingsboard.conf
index a6e404d..2baee7d 100644
--- a/application/src/main/conf/thingsboard.conf
+++ b/application/src/main/conf/thingsboard.conf
@@ -14,7 +14,7 @@
 # limitations under the License.
 #
 
-export JAVA_OPTS="$JAVA_OPTS -Dplatform=@pkg.platform@"
+export JAVA_OPTS="$JAVA_OPTS -Dplatform=@pkg.platform@ -Dinstall.data_dir=@pkg.installFolder@"
 export LOG_FILENAME=${pkg.name}.out
 export LOADER_PATH=${pkg.installFolder}/conf,${pkg.installFolder}/extensions
 export SQL_DATA_FOLDER=${pkg.installFolder}/data/sql
diff --git a/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json b/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
new file mode 100644
index 0000000..7d6da8d
--- /dev/null
+++ b/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
@@ -0,0 +1,98 @@
+{
+  "ruleChain": {
+    "additionalInfo": null,
+    "name": "Root Rule Chain",
+    "firstRuleNodeId": null,
+    "root": true,
+    "debugMode": false,
+    "configuration": null
+  },
+  "metadata": {
+    "firstNodeIndex": 2,
+    "nodes": [
+      {
+        "additionalInfo": {
+          "layoutX": 824,
+          "layoutY": 156
+        },
+        "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
+        "name": "SaveTS",
+        "debugMode": true,
+        "configuration": {
+          "defaultTTL": 0
+        }
+      },
+      {
+        "additionalInfo": {
+          "layoutX": 825,
+          "layoutY": 52
+        },
+        "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
+        "name": "save client attributes",
+        "debugMode": true,
+        "configuration": {
+          "scope": "CLIENT_SCOPE"
+        }
+      },
+      {
+        "additionalInfo": {
+          "layoutX": 347,
+          "layoutY": 149
+        },
+        "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
+        "name": "Message Type Switch",
+        "debugMode": false,
+        "configuration": {
+          "version": 0
+        }
+      },
+      {
+        "additionalInfo": {
+          "layoutX": 825,
+          "layoutY": 266
+        },
+        "type": "org.thingsboard.rule.engine.action.TbLogNode",
+        "name": "Log RPC",
+        "debugMode": false,
+        "configuration": {
+          "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
+        }
+      },
+      {
+        "additionalInfo": {
+          "layoutX": 825,
+          "layoutY": 379
+        },
+        "type": "org.thingsboard.rule.engine.action.TbLogNode",
+        "name": "Log Other",
+        "debugMode": false,
+        "configuration": {
+          "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
+        }
+      }
+    ],
+    "connections": [
+      {
+        "fromIndex": 2,
+        "toIndex": 4,
+        "type": "Other"
+      },
+      {
+        "fromIndex": 2,
+        "toIndex": 1,
+        "type": "Post attributes"
+      },
+      {
+        "fromIndex": 2,
+        "toIndex": 0,
+        "type": "Post telemetry"
+      },
+      {
+        "fromIndex": 2,
+        "toIndex": 3,
+        "type": "RPC Request"
+      }
+    ],
+    "ruleChainConnections": null
+  }
+}
\ No newline at end of file
diff --git a/application/src/main/data/upgrade/1.5.0/schema_update.cql b/application/src/main/data/upgrade/1.5.0/schema_update.cql
index 5cdaede..9481fc0 100644
--- a/application/src/main/data/upgrade/1.5.0/schema_update.cql
+++ b/application/src/main/data/upgrade/1.5.0/schema_update.cql
@@ -84,6 +84,7 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_chain_by_tenant_and_sear
 
 CREATE TABLE IF NOT EXISTS  thingsboard.rule_node (
     id uuid,
+    rule_chain_id uuid,
     type text,
     name text,
     debug_mode boolean,
diff --git a/application/src/main/data/upgrade/1.5.0/schema_update.sql b/application/src/main/data/upgrade/1.5.0/schema_update.sql
index ab91166..ef5f6db 100644
--- a/application/src/main/data/upgrade/1.5.0/schema_update.sql
+++ b/application/src/main/data/upgrade/1.5.0/schema_update.sql
@@ -28,6 +28,7 @@ CREATE TABLE IF NOT EXISTS rule_chain (
 
 CREATE TABLE IF NOT EXISTS rule_node (
     id varchar(31) NOT NULL CONSTRAINT rule_node_pkey PRIMARY KEY,
+    rule_chain_id varchar(31),
     additional_info varchar,
     configuration varchar(10000000),
     type varchar(255),
diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
index 50d2530..54737f8 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
@@ -49,6 +49,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
 import org.thingsboard.server.dao.device.DeviceService;
 import org.thingsboard.server.dao.event.EventService;
 import org.thingsboard.server.dao.plugin.PluginService;
+import org.thingsboard.server.dao.queue.MsgQueue;
 import org.thingsboard.server.dao.relation.RelationService;
 import org.thingsboard.server.dao.rule.RuleChainService;
 import org.thingsboard.server.dao.rule.RuleService;
@@ -60,8 +61,11 @@ import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
 import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
 import org.thingsboard.server.service.component.ComponentDiscoveryService;
 import org.thingsboard.server.service.executors.DbCallbackExecutorService;
+import org.thingsboard.server.service.executors.ExternalCallExecutorService;
 import org.thingsboard.server.service.mail.MailExecutorService;
+import org.thingsboard.server.service.rpc.DeviceRpcService;
 import org.thingsboard.server.service.script.JsExecutorService;
+import org.thingsboard.server.service.state.DeviceStateService;
 import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
 
 import java.io.IOException;
@@ -163,6 +167,10 @@ public class ActorSystemContext {
 
     @Autowired
     @Getter
+    private DeviceRpcService deviceRpcService;
+
+    @Autowired
+    @Getter
     @Setter
     private PluginWebSocketMsgEndpoint wsMsgEndpoint;
 
@@ -180,23 +188,35 @@ public class ActorSystemContext {
 
     @Autowired
     @Getter
+    private ExternalCallExecutorService externalCallExecutorService;
+
+    @Autowired
+    @Getter
     private MailService mailService;
 
+    @Autowired
+    @Getter
+    private MsgQueue msgQueue;
+
+    @Autowired
+    @Getter
+    private DeviceStateService deviceStateService;
+
     @Value("${actors.session.sync.timeout}")
     @Getter
     private long syncSessionTimeout;
 
-    @Value("${actors.plugin.termination.delay}")
+    @Value("${actors.queue.enabled}")
     @Getter
-    private long pluginActorTerminationDelay;
+    private boolean queuePersistenceEnabled;
 
-    @Value("${actors.plugin.processing.timeout}")
+    @Value("${actors.queue.timeout}")
     @Getter
-    private long pluginProcessingTimeout;
+    private long queuePersistenceTimeout;
 
-    @Value("${actors.plugin.error_persist_frequency}")
+    @Value("${actors.client_side_rpc.timeout}")
     @Getter
-    private long pluginErrorPersistFrequency;
+    private long clientSideRpcTimeout;
 
     @Value("${actors.rule.chain.error_persist_frequency}")
     @Getter
@@ -206,14 +226,6 @@ public class ActorSystemContext {
     @Getter
     private long ruleNodeErrorPersistFrequency;
 
-    @Value("${actors.rule.termination.delay}")
-    @Getter
-    private long ruleActorTerminationDelay;
-
-    @Value("${actors.rule.error_persist_frequency}")
-    @Getter
-    private long ruleErrorPersistFrequency;
-
     @Value("${actors.statistics.enabled}")
     @Getter
     private boolean statisticsEnabled;
diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
index a75158f..4d9b8b4 100644
--- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
@@ -29,13 +29,12 @@ import org.thingsboard.server.actors.shared.plugin.SystemPluginManager;
 import org.thingsboard.server.actors.shared.rulechain.SystemRuleChainManager;
 import org.thingsboard.server.actors.tenant.TenantActor;
 import org.thingsboard.server.common.data.Tenant;
-import org.thingsboard.server.common.data.id.PluginId;
-import org.thingsboard.server.common.data.id.RuleChainId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.page.PageDataIterable;
 import org.thingsboard.server.common.msg.TbActorMsg;
-import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
+import org.thingsboard.server.common.msg.aware.TenantAwareMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
 import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
 import org.thingsboard.server.dao.model.ModelConstants;
@@ -90,12 +89,23 @@ public class AppActor extends RuleChainManagerActor {
     @Override
     protected boolean process(TbActorMsg msg) {
         switch (msg.getMsgType()) {
+            case CLUSTER_EVENT_MSG:
+                broadcast(msg);
+                break;
             case COMPONENT_LIFE_CYCLE_MSG:
                 onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
                 break;
             case SERVICE_TO_RULE_ENGINE_MSG:
                 onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg);
                 break;
+            case DEVICE_SESSION_TO_DEVICE_ACTOR_MSG:
+            case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG:
+            case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG:
+            case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG:
+            case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
+            case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
+                onToDeviceActorMsg((TenantAwareMsg) msg);
+                break;
             default:
                 return false;
         }
@@ -110,48 +120,12 @@ public class AppActor extends RuleChainManagerActor {
         }
     }
 
-
-//    @Override
-//    public void onReceive(Object msg) throws Exception {
-//        logger.debug("Received message: {}", msg);
-//        if (msg instanceof ToDeviceActorMsg) {
-//            processDeviceMsg((ToDeviceActorMsg) msg);
-//        } else if (msg instanceof ToPluginActorMsg) {
-//            onToPluginMsg((ToPluginActorMsg) msg);
-//        } else if (msg instanceof ToDeviceActorNotificationMsg) {
-//            onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg);
-//        } else if (msg instanceof Terminated) {
-//            processTermination((Terminated) msg);
-//        } else if (msg instanceof ClusterEventMsg) {
-//            broadcast(msg);
-//        } else if (msg instanceof ComponentLifecycleMsg) {
-//            onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
-//        } else if (msg instanceof PluginTerminationMsg) {
-//            onPluginTerminated((PluginTerminationMsg) msg);
-//        } else {
-//            logger.warning("Unknown message: {}!", msg);
-//        }
-//    }
-
-    private void onPluginTerminated(PluginTerminationMsg msg) {
-        pluginManager.remove(msg.getId());
-    }
-
-    private void broadcast(Object msg) {
-        pluginManager.broadcast(msg);
+    @Override
+    protected void broadcast(Object msg) {
+        super.broadcast(msg);
         tenantActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
     }
 
-    private void onToPluginMsg(ToPluginActorMsg msg) {
-        ActorRef target;
-        if (SYSTEM_TENANT.equals(msg.getPluginTenantId())) {
-            target = pluginManager.getOrCreateActor(this.context(), msg.getPluginId());
-        } else {
-            target = getOrCreateTenantActor(msg.getPluginTenantId());
-        }
-        target.tell(msg, ActorRef.noSender());
-    }
-
     private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
         ActorRef target;
         if (SYSTEM_TENANT.equals(msg.getTenantId())) {
@@ -166,17 +140,17 @@ public class AppActor extends RuleChainManagerActor {
         }
     }
 
-    private void onToDeviceActorMsg(ToDeviceActorNotificationMsg msg) {
+    private void onToDeviceActorMsg(TenantAwareMsg msg) {
         getOrCreateTenantActor(msg.getTenantId()).tell(msg, ActorRef.noSender());
     }
 
-    private void processDeviceMsg(ToDeviceActorMsg toDeviceActorMsg) {
-        TenantId tenantId = toDeviceActorMsg.getTenantId();
+    private void processDeviceMsg(DeviceToDeviceActorMsg deviceToDeviceActorMsg) {
+        TenantId tenantId = deviceToDeviceActorMsg.getTenantId();
         ActorRef tenantActor = getOrCreateTenantActor(tenantId);
-        if (toDeviceActorMsg.getPayload().getMsgType().requiresRulesProcessing()) {
-//            tenantActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, ruleManager.getRuleChain(this.context())), context().self());
+        if (deviceToDeviceActorMsg.getPayload().getMsgType().requiresRulesProcessing()) {
+//            tenantActor.tell(new RuleChainDeviceMsg(deviceToDeviceActorMsg, ruleManager.getRuleChain(this.context())), context().self());
         } else {
-            tenantActor.tell(toDeviceActorMsg, context().self());
+            tenantActor.tell(deviceToDeviceActorMsg, context().self());
         }
     }
 
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
index 87bc992..93df180 100644
--- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
@@ -24,59 +24,66 @@ import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.msg.TbActorMsg;
 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
+import org.thingsboard.server.common.msg.timeout.DeviceActorClientSideRpcTimeoutMsg;
+import org.thingsboard.server.common.msg.timeout.DeviceActorQueueTimeoutMsg;
+import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg;
 import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
-import org.thingsboard.server.extensions.api.device.DeviceCredentialsUpdateNotificationMsg;
 import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg;
-import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
-import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
-import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
+import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
+import org.thingsboard.server.service.rpc.ToServerRpcResponseActorMsg;
 
 public class DeviceActor extends ContextAwareActor {
 
     private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
 
-    private final TenantId tenantId;
-    private final DeviceId deviceId;
     private final DeviceActorMessageProcessor processor;
 
     private DeviceActor(ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) {
         super(systemContext);
-        this.tenantId = tenantId;
-        this.deviceId = deviceId;
-        this.processor = new DeviceActorMessageProcessor(systemContext, logger, deviceId);
+        this.processor = new DeviceActorMessageProcessor(systemContext, logger, tenantId, deviceId);
     }
 
     @Override
     protected boolean process(TbActorMsg msg) {
-        return false;
-    }
-
-    @Override
-    public void onReceive(Object msg) throws Exception {
-//        if (msg instanceof RuleChainDeviceMsg) {
-//            processor.process(context(), (RuleChainDeviceMsg) msg);
-//        } else if (msg instanceof RulesProcessedMsg) {
-//            processor.onRulesProcessedMsg(context(), (RulesProcessedMsg) msg);
-        if (msg instanceof ToDeviceActorMsg) {
-            processor.process(context(), (ToDeviceActorMsg) msg);
-        } else if (msg instanceof ToDeviceActorNotificationMsg) {
-            if (msg instanceof DeviceAttributesEventNotificationMsg) {
+        switch (msg.getMsgType()) {
+            case CLUSTER_EVENT_MSG:
+                processor.processClusterEventMsg((ClusterEventMsg) msg);
+                break;
+            case DEVICE_SESSION_TO_DEVICE_ACTOR_MSG:
+                processor.process(context(), (DeviceToDeviceActorMsg) msg);
+                break;
+            case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG:
                 processor.processAttributesUpdate(context(), (DeviceAttributesEventNotificationMsg) msg);
-            } else if (msg instanceof ToDeviceRpcRequestPluginMsg) {
-                processor.processRpcRequest(context(), (ToDeviceRpcRequestPluginMsg) msg);
-            } else if (msg instanceof DeviceCredentialsUpdateNotificationMsg){
+                break;
+            case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG:
                 processor.processCredentialsUpdate();
-            } else if (msg instanceof DeviceNameOrTypeUpdateMsg){
+                break;
+            case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG:
                 processor.processNameOrTypeUpdate((DeviceNameOrTypeUpdateMsg) msg);
-            }
-        } else if (msg instanceof TimeoutMsg) {
-            processor.processTimeout(context(), (TimeoutMsg) msg);
-        } else if (msg instanceof ClusterEventMsg) {
-            processor.processClusterEventMsg((ClusterEventMsg) msg);
-        } else {
-            logger.debug("[{}][{}] Unknown msg type.", tenantId, deviceId, msg.getClass().getName());
+                break;
+            case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
+                processor.processRpcRequest(context(), (ToDeviceRpcRequestActorMsg) msg);
+                break;
+            case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
+                processor.processToServerRPCResponse(context(), (ToServerRpcResponseActorMsg) msg);
+                break;
+            case DEVICE_ACTOR_SERVER_SIDE_RPC_TIMEOUT_MSG:
+                processor.processServerSideRpcTimeout(context(), (DeviceActorServerSideRpcTimeoutMsg) msg);
+                break;
+            case DEVICE_ACTOR_CLIENT_SIDE_RPC_TIMEOUT_MSG:
+                processor.processClientSideRpcTimeout(context(), (DeviceActorClientSideRpcTimeoutMsg) msg);
+                break;
+            case DEVICE_ACTOR_QUEUE_TIMEOUT_MSG:
+                processor.processQueueTimeout(context(), (DeviceActorQueueTimeoutMsg) msg);
+                break;
+            case RULE_ENGINE_QUEUE_PUT_ACK_MSG:
+                processor.processQueueAck(context(), (RuleEngineQueuePutAckMsg) msg);
+                break;
+            default:
+                return false;
         }
+        return true;
     }
 
     public static class ActorCreator extends ContextBasedCreator<DeviceActor> {
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
index 3644a49..5112f22 100644
--- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
@@ -18,30 +18,76 @@ package org.thingsboard.server.actors.device;
 import akka.actor.ActorContext;
 import akka.actor.ActorRef;
 import akka.event.LoggingAdapter;
+import com.datastax.driver.core.utils.UUIDs;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
 import org.thingsboard.server.actors.ActorSystemContext;
 import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
 import org.thingsboard.server.common.data.DataConstants;
 import org.thingsboard.server.common.data.Device;
 import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.SessionId;
+import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.kv.AttributeKey;
 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
+import org.thingsboard.server.common.data.kv.KvEntry;
+import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
+import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.TbMsgDataType;
+import org.thingsboard.server.common.msg.TbMsgMetaData;
 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
 import org.thingsboard.server.common.msg.cluster.ServerAddress;
-import org.thingsboard.server.common.msg.core.*;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.core.AttributesUpdateNotification;
+import org.thingsboard.server.common.msg.core.AttributesUpdateRequest;
+import org.thingsboard.server.common.msg.core.BasicCommandAckResponse;
+import org.thingsboard.server.common.msg.core.BasicGetAttributesResponse;
+import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse;
+import org.thingsboard.server.common.msg.core.BasicToDeviceSessionActorMsg;
+import org.thingsboard.server.common.msg.core.GetAttributesRequest;
+import org.thingsboard.server.common.msg.core.RuleEngineError;
+import org.thingsboard.server.common.msg.core.RuleEngineErrorMsg;
+import org.thingsboard.server.common.msg.core.SessionCloseMsg;
+import org.thingsboard.server.common.msg.core.SessionCloseNotification;
+import org.thingsboard.server.common.msg.core.SessionOpenMsg;
+import org.thingsboard.server.common.msg.core.TelemetryUploadRequest;
+import org.thingsboard.server.common.msg.core.ToDeviceRpcRequestMsg;
+import org.thingsboard.server.common.msg.core.ToDeviceRpcResponseMsg;
+import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
+import org.thingsboard.server.common.msg.core.ToServerRpcRequestMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.kv.BasicAttributeKVMsg;
+import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
 import org.thingsboard.server.common.msg.session.FromDeviceMsg;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 import org.thingsboard.server.common.msg.session.SessionType;
 import org.thingsboard.server.common.msg.session.ToDeviceMsg;
-import org.thingsboard.server.extensions.api.device.DeviceAttributes;
+import org.thingsboard.server.common.msg.timeout.DeviceActorClientSideRpcTimeoutMsg;
+import org.thingsboard.server.common.msg.timeout.DeviceActorQueueTimeoutMsg;
+import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg;
 import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
 import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg;
-import org.thingsboard.server.extensions.api.plugins.msg.*;
-
-import java.util.*;
-import java.util.concurrent.ExecutionException;
+import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
+import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
+import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
+import org.thingsboard.server.service.rpc.ToServerRpcResponseActorMsg;
+
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -52,46 +98,46 @@ import java.util.stream.Collectors;
  */
 public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
 
+    private final TenantId tenantId;
     private final DeviceId deviceId;
     private final Map<SessionId, SessionInfo> sessions;
     private final Map<SessionId, SessionInfo> attributeSubscriptions;
     private final Map<SessionId, SessionInfo> rpcSubscriptions;
+    private final Map<Integer, ToDeviceRpcRequestMetadata> toDeviceRpcPendingMap;
+    private final Map<Integer, ToServerRpcRequestMetadata> toServerRpcPendingMap;
+    private final Map<UUID, PendingSessionMsgData> pendingMsgs;
 
-    private final Map<Integer, ToDeviceRpcRequestMetadata> rpcPendingMap;
+    private final Gson gson = new Gson();
+    private final JsonParser jsonParser = new JsonParser();
 
     private int rpcSeq = 0;
     private String deviceName;
     private String deviceType;
-    private DeviceAttributes deviceAttributes;
+    private TbMsgMetaData defaultMetaData;
 
-    public DeviceActorMessageProcessor(ActorSystemContext systemContext, LoggingAdapter logger, DeviceId deviceId) {
+    public DeviceActorMessageProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, DeviceId deviceId) {
         super(systemContext, logger);
+        this.tenantId = tenantId;
         this.deviceId = deviceId;
         this.sessions = new HashMap<>();
         this.attributeSubscriptions = new HashMap<>();
         this.rpcSubscriptions = new HashMap<>();
-        this.rpcPendingMap = new HashMap<>();
+        this.toDeviceRpcPendingMap = new HashMap<>();
+        this.toServerRpcPendingMap = new HashMap<>();
+        this.pendingMsgs = new HashMap<>();
         initAttributes();
     }
 
     private void initAttributes() {
-        //TODO: add invalidation of deviceType cache.
         Device device = systemContext.getDeviceService().findDeviceById(deviceId);
         this.deviceName = device.getName();
         this.deviceType = device.getType();
-        this.deviceAttributes = new DeviceAttributes(fetchAttributes(DataConstants.CLIENT_SCOPE),
-                fetchAttributes(DataConstants.SERVER_SCOPE), fetchAttributes(DataConstants.SHARED_SCOPE));
-    }
-
-    private void refreshAttributes(DeviceAttributesEventNotificationMsg msg) {
-        if (msg.isDeleted()) {
-            msg.getDeletedKeys().forEach(key -> deviceAttributes.remove(key));
-        } else {
-            deviceAttributes.update(msg.getScope(), msg.getValues());
-        }
+        this.defaultMetaData = new TbMsgMetaData();
+        this.defaultMetaData.putValue("deviceName", deviceName);
+        this.defaultMetaData.putValue("deviceType", deviceType);
     }
 
-    void processRpcRequest(ActorContext context, ToDeviceRpcRequestPluginMsg msg) {
+    void processRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg) {
         ToDeviceRpcRequest request = msg.getMsg();
         ToDeviceRpcRequestBody body = request.getBody();
         ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg(
@@ -118,9 +164,8 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
         syncSessionSet.forEach(rpcSubscriptions::remove);
 
         if (request.isOneway() && sent) {
-            ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(msg, (String) null);
-            context.parent().tell(responsePluginMsg, ActorRef.noSender());
             logger.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId());
+            systemContext.getDeviceRpcService().process(new FromDeviceRpcResponse(msg.getMsg().getId(), null, null));
         } else {
             registerPendingRpcRequest(context, msg, sent, rpcRequest, timeout);
         }
@@ -132,24 +177,46 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
 
     }
 
-    private void registerPendingRpcRequest(ActorContext context, ToDeviceRpcRequestPluginMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) {
-        rpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent));
-        TimeoutIntMsg timeoutMsg = new TimeoutIntMsg(rpcRequest.getRequestId(), timeout);
+    private void registerPendingRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) {
+        toDeviceRpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent));
+        DeviceActorServerSideRpcTimeoutMsg timeoutMsg = new DeviceActorServerSideRpcTimeoutMsg(rpcRequest.getRequestId(), timeout);
         scheduleMsgWithDelay(context, timeoutMsg, timeoutMsg.getTimeout());
     }
 
-    public void processTimeout(ActorContext context, TimeoutMsg msg) {
-        ToDeviceRpcRequestMetadata requestMd = rpcPendingMap.remove(msg.getId());
+    void processServerSideRpcTimeout(ActorContext context, DeviceActorServerSideRpcTimeoutMsg msg) {
+        ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(msg.getId());
         if (requestMd != null) {
             logger.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId());
-            ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(requestMd.getMsg(), requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION);
-            context.parent().tell(responsePluginMsg, ActorRef.noSender());
+            systemContext.getDeviceRpcService().process(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
+                    null, requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION));
+        }
+    }
+
+    void processQueueTimeout(ActorContext context, DeviceActorQueueTimeoutMsg msg) {
+        PendingSessionMsgData data = pendingMsgs.remove(msg.getId());
+        if (data != null) {
+            logger.debug("[{}] Queue put [{}] timeout detected!", deviceId, msg.getId());
+            ToDeviceMsg toDeviceMsg = new RuleEngineErrorMsg(data.getSessionMsgType(), RuleEngineError.QUEUE_PUT_TIMEOUT);
+            sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(toDeviceMsg, data.getSessionId()), data.getServerAddress());
+        }
+    }
+
+    void processQueueAck(ActorContext context, RuleEngineQueuePutAckMsg msg) {
+        PendingSessionMsgData data = pendingMsgs.remove(msg.getId());
+        if (data != null && data.isReplyOnQueueAck()) {
+            int remainingAcks = data.getAckMsgCount() - 1;
+            data.setAckMsgCount(remainingAcks);
+            logger.debug("[{}] Queue put [{}] ack detected. Remaining acks: {}!", deviceId, msg.getId(), remainingAcks);
+            if (remainingAcks == 0) {
+                ToDeviceMsg toDeviceMsg = BasicStatusCodeResponse.onSuccess(data.getSessionMsgType(), data.getRequestId());
+                sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(toDeviceMsg, data.getSessionId()), data.getServerAddress());
+            }
         }
     }
 
     private void sendPendingRequests(ActorContext context, SessionId sessionId, SessionType type, Optional<ServerAddress> server) {
-        if (!rpcPendingMap.isEmpty()) {
-            logger.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, rpcPendingMap.size(), sessionId);
+        if (!toDeviceRpcPendingMap.isEmpty()) {
+            logger.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, toDeviceRpcPendingMap.size(), sessionId);
             if (type == SessionType.SYNC) {
                 logger.debug("[{}] Cleanup sync rpc session [{}]", deviceId, sessionId);
                 rpcSubscriptions.remove(sessionId);
@@ -159,12 +226,12 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
         }
         Set<Integer> sentOneWayIds = new HashSet<>();
         if (type == SessionType.ASYNC) {
-            rpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, server, sentOneWayIds));
+            toDeviceRpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, server, sentOneWayIds));
         } else {
-            rpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, server, sentOneWayIds));
+            toDeviceRpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, server, sentOneWayIds));
         }
 
-        sentOneWayIds.forEach(rpcPendingMap::remove);
+        sentOneWayIds.forEach(toDeviceRpcPendingMap::remove);
     }
 
     private Consumer<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> processPendingRpc(ActorContext context, SessionId sessionId, Optional<ServerAddress> server, Set<Integer> sentOneWayIds) {
@@ -173,8 +240,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
             ToDeviceRpcRequestBody body = request.getBody();
             if (request.isOneway()) {
                 sentOneWayIds.add(entry.getKey());
-                ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(entry.getValue().getMsg(), (String) null);
-                context.parent().tell(responsePluginMsg, ActorRef.noSender());
+                systemContext.getDeviceRpcService().process(new FromDeviceRpcResponse(request.getId(), null, null));
             }
             ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg(
                     entry.getKey(),
@@ -186,14 +252,170 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
         };
     }
 
-    void process(ActorContext context, ToDeviceActorMsg msg) {
+    void process(ActorContext context, DeviceToDeviceActorMsg msg) {
         processSubscriptionCommands(context, msg);
         processRpcResponses(context, msg);
         processSessionStateMsgs(msg);
+
+        SessionMsgType sessionMsgType = msg.getPayload().getMsgType();
+        if (sessionMsgType.requiresRulesProcessing()) {
+            switch (sessionMsgType) {
+                case GET_ATTRIBUTES_REQUEST:
+                    handleGetAttributesRequest(msg);
+                    break;
+                case POST_ATTRIBUTES_REQUEST:
+                    handlePostAttributesRequest(context, msg);
+                    reportActivity();
+                    break;
+                case POST_TELEMETRY_REQUEST:
+                    handlePostTelemetryRequest(context, msg);
+                    reportActivity();
+                    break;
+                case TO_SERVER_RPC_REQUEST:
+                    handleClientSideRPCRequest(context, msg);
+                    reportActivity();
+                    break;
+            }
+        }
+    }
+
+    private void reportActivity() {
+        systemContext.getDeviceStateService().onDeviceActivity(deviceId);
+    }
+
+    private void reportSessionOpen() {
+        systemContext.getDeviceStateService().onDeviceConnect(deviceId);
+    }
+
+    private void reportSessionClose() {
+        systemContext.getDeviceStateService().onDeviceDisconnect(deviceId);
+    }
+
+    private void handleGetAttributesRequest(DeviceToDeviceActorMsg src) {
+        GetAttributesRequest request = (GetAttributesRequest) src.getPayload();
+        ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.CLIENT_SCOPE, request.getClientAttributeNames());
+        ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.SHARED_SCOPE, request.getClientAttributeNames());
+
+        Futures.addCallback(Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture)), new FutureCallback<List<List<AttributeKvEntry>>>() {
+            @Override
+            public void onSuccess(@Nullable List<List<AttributeKvEntry>> result) {
+                BasicGetAttributesResponse response = BasicGetAttributesResponse.onSuccess(request.getMsgType(),
+                        request.getRequestId(), BasicAttributeKVMsg.from(result.get(0), result.get(1)));
+                sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(response, src.getSessionId()), src.getServerAddress());
+            }
+
+            @Override
+            public void onFailure(Throwable t) {
+                if (t instanceof Exception) {
+                    ToDeviceMsg toDeviceMsg = BasicStatusCodeResponse.onError(SessionMsgType.GET_ATTRIBUTES_REQUEST, request.getRequestId(), (Exception) t);
+                    sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(toDeviceMsg, src.getSessionId()), src.getServerAddress());
+                } else {
+                    logger.error("[{}] Failed to process attributes request", deviceId, t);
+                }
+            }
+        });
+    }
+
+    private ListenableFuture<List<AttributeKvEntry>> getAttributeKvEntries(DeviceId deviceId, String scope, Optional<Set<String>> names) {
+        if (names.isPresent()) {
+            if (!names.get().isEmpty()) {
+                return systemContext.getAttributesService().find(deviceId, scope, names.get());
+            } else {
+                return systemContext.getAttributesService().findAll(deviceId, scope);
+            }
+        } else {
+            return Futures.immediateFuture(Collections.emptyList());
+        }
+    }
+
+    private void handlePostAttributesRequest(ActorContext context, DeviceToDeviceActorMsg src) {
+        AttributesUpdateRequest request = (AttributesUpdateRequest) src.getPayload();
+
+        JsonObject json = new JsonObject();
+        for (AttributeKvEntry kv : request.getAttributes()) {
+            kv.getBooleanValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
+            kv.getLongValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
+            kv.getDoubleValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
+            kv.getStrValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
+        }
+
+        TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), deviceId, defaultMetaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
+        PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(),
+                SessionMsgType.POST_ATTRIBUTES_REQUEST, request.getRequestId(), true, 1);
+        pushToRuleEngineWithTimeout(context, tbMsg, msgData);
+    }
+
+    private void handlePostTelemetryRequest(ActorContext context, DeviceToDeviceActorMsg src) {
+        TelemetryUploadRequest request = (TelemetryUploadRequest) src.getPayload();
+
+        Map<Long, List<KvEntry>> tsData = request.getData();
+
+        PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(),
+                SessionMsgType.POST_TELEMETRY_REQUEST, request.getRequestId(), true, tsData.size());
+
+        for (Map.Entry<Long, List<KvEntry>> entry : tsData.entrySet()) {
+            JsonObject json = new JsonObject();
+            json.addProperty("ts", entry.getKey());
+            JsonObject values = new JsonObject();
+            for (KvEntry kv : entry.getValue()) {
+                kv.getBooleanValue().ifPresent(v -> values.addProperty(kv.getKey(), v));
+                kv.getLongValue().ifPresent(v -> values.addProperty(kv.getKey(), v));
+                kv.getDoubleValue().ifPresent(v -> values.addProperty(kv.getKey(), v));
+                kv.getStrValue().ifPresent(v -> values.addProperty(kv.getKey(), v));
+            }
+            json.add("values", values);
+            TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, defaultMetaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
+            pushToRuleEngineWithTimeout(context, tbMsg, msgData);
+        }
+    }
+
+    private void handleClientSideRPCRequest(ActorContext context, DeviceToDeviceActorMsg src) {
+        ToServerRpcRequestMsg request = (ToServerRpcRequestMsg) src.getPayload();
+
+        JsonObject json = new JsonObject();
+        json.addProperty("method", request.getMethod());
+        json.add("params", jsonParser.parse(request.getParams()));
+
+        TbMsgMetaData requestMetaData = defaultMetaData.copy();
+        requestMetaData.putValue("requestId", Integer.toString(request.getRequestId()));
+        TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.TO_SERVER_RPC_REQUEST.name(), deviceId, requestMetaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
+        PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(), SessionMsgType.TO_SERVER_RPC_REQUEST, request.getRequestId(), false, 1);
+        pushToRuleEngineWithTimeout(context, tbMsg, msgData);
+
+        scheduleMsgWithDelay(context, new DeviceActorClientSideRpcTimeoutMsg(request.getRequestId(), systemContext.getClientSideRpcTimeout()), systemContext.getClientSideRpcTimeout());
+        toServerRpcPendingMap.put(request.getRequestId(), new ToServerRpcRequestMetadata(src.getSessionId(), src.getSessionType(), src.getServerAddress()));
+    }
+
+    public void processClientSideRpcTimeout(ActorContext context, DeviceActorClientSideRpcTimeoutMsg msg) {
+        ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(msg.getId());
+        if (data != null) {
+            logger.debug("[{}] Client side RPC request [{}] timeout detected!", deviceId, msg.getId());
+            ToDeviceMsg toDeviceMsg = new RuleEngineErrorMsg(SessionMsgType.TO_SERVER_RPC_REQUEST, RuleEngineError.TIMEOUT);
+            sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(toDeviceMsg, data.getSessionId()), data.getServer());
+        }
+    }
+
+    void processToServerRPCResponse(ActorContext context, ToServerRpcResponseActorMsg msg) {
+        ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(msg.getMsg().getRequestId());
+        if (data != null) {
+            sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(msg.getMsg(), data.getSessionId()), data.getServer());
+        }
+    }
+
+    private void pushToRuleEngineWithTimeout(ActorContext context, TbMsg tbMsg, PendingSessionMsgData pendingMsgData) {
+        SessionMsgType sessionMsgType = pendingMsgData.getSessionMsgType();
+        int requestId = pendingMsgData.getRequestId();
+        if (systemContext.isQueuePersistenceEnabled()) {
+            pendingMsgs.put(tbMsg.getId(), pendingMsgData);
+            scheduleMsgWithDelay(context, new DeviceActorQueueTimeoutMsg(tbMsg.getId(), systemContext.getQueuePersistenceTimeout()), systemContext.getQueuePersistenceTimeout());
+        } else {
+            ToDeviceSessionActorMsg response = new BasicToDeviceSessionActorMsg(BasicStatusCodeResponse.onSuccess(sessionMsgType, requestId), pendingMsgData.getSessionId());
+            sendMsgToSessionActor(response, pendingMsgData.getServerAddress());
+        }
+        context.parent().tell(new DeviceActorToRuleEngineMsg(context.self(), tbMsg), context.self());
     }
 
     void processAttributesUpdate(ActorContext context, DeviceAttributesEventNotificationMsg msg) {
-        refreshAttributes(msg);
         if (attributeSubscriptions.size() > 0) {
             ToDeviceMsg notification = null;
             if (msg.isDeleted()) {
@@ -223,50 +445,29 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
         }
     }
 
-//    void process(ActorContext context, RuleChainDeviceMsg srcMsg) {
-//        ChainProcessingMetaData md = new ChainProcessingMetaData(srcMsg.getRuleChain(),
-//                srcMsg.getToDeviceActorMsg(), new DeviceMetaData(deviceId, deviceName, deviceType, deviceAttributes), context.self());
-//        ChainProcessingContext ctx = new ChainProcessingContext(md);
-//        if (ctx.getChainLength() > 0) {
-//            RuleProcessingMsg msg = new RuleProcessingMsg(ctx);
-//            ActorRef ruleActorRef = ctx.getCurrentActor();
-//            ruleActorRef.tell(msg, ActorRef.noSender());
-//        } else {
-//            context.self().tell(new RulesProcessedMsg(ctx), context.self());
-//        }
-//    }
-
-    void processRpcResponses(ActorContext context, ToDeviceActorMsg msg) {
+    private void processRpcResponses(ActorContext context, DeviceToDeviceActorMsg msg) {
         SessionId sessionId = msg.getSessionId();
         FromDeviceMsg inMsg = msg.getPayload();
-        if (inMsg.getMsgType() == MsgType.TO_DEVICE_RPC_RESPONSE) {
+        if (inMsg.getMsgType() == SessionMsgType.TO_DEVICE_RPC_RESPONSE) {
             logger.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId);
             ToDeviceRpcResponseMsg responseMsg = (ToDeviceRpcResponseMsg) inMsg;
-            ToDeviceRpcRequestMetadata requestMd = rpcPendingMap.remove(responseMsg.getRequestId());
+            ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
             boolean success = requestMd != null;
             if (success) {
-                ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(requestMd.getMsg(), responseMsg.getData());
-                Optional<ServerAddress> pluginServerAddress = requestMd.getMsg().getServerAddress();
-                if (pluginServerAddress.isPresent()) {
-                    systemContext.getRpcService().tell(pluginServerAddress.get(), responsePluginMsg);
-                    logger.debug("[{}] Rpc command response sent to remote plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId());
-                } else {
-                    context.parent().tell(responsePluginMsg, ActorRef.noSender());
-                    logger.debug("[{}] Rpc command response sent to local plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId());
-                }
+                systemContext.getDeviceRpcService().process(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), responseMsg.getData(), null));
             } else {
                 logger.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId());
             }
             if (msg.getSessionType() == SessionType.SYNC) {
                 BasicCommandAckResponse response = success
-                        ? BasicCommandAckResponse.onSuccess(MsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId())
-                        : BasicCommandAckResponse.onError(MsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId(), new TimeoutException());
+                        ? BasicCommandAckResponse.onSuccess(SessionMsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId())
+                        : BasicCommandAckResponse.onError(SessionMsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId(), new TimeoutException());
                 sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(response, msg.getSessionId()), msg.getServerAddress());
             }
         }
     }
 
-    public void processClusterEventMsg(ClusterEventMsg msg) {
+    void processClusterEventMsg(ClusterEventMsg msg) {
         if (!msg.isAdded()) {
             logger.debug("[{}] Clearing attributes/rpc subscription for server [{}]", deviceId, msg.getServerAddress());
             Predicate<Map.Entry<SessionId, SessionInfo>> filter = e -> e.getValue().getServer()
@@ -276,69 +477,43 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
         }
     }
 
-    private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, String data) {
-        return toPluginRpcResponseMsg(requestMsg, data, null);
-    }
-
-    private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, RpcError error) {
-        return toPluginRpcResponseMsg(requestMsg, null, error);
-    }
-
-    private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, String data, RpcError error) {
-        return new ToPluginRpcResponseDeviceMsg(
-                requestMsg.getPluginId(),
-                requestMsg.getPluginTenantId(),
-                new FromDeviceRpcResponse(requestMsg.getMsg().getId(),
-                        data,
-                        error
-                )
-        );
-    }
-
-//    void onRulesProcessedMsg(ActorContext context, RulesProcessedMsg msg) {
-//        ChainProcessingContext ctx = msg.getCtx();
-//        ToDeviceActorMsg inMsg = ctx.getInMsg();
-//        SessionId sid = inMsg.getSessionId();
-//        ToDeviceSessionActorMsg response;
-//        if (ctx.getResponse() != null) {
-//            response = new BasicToDeviceSessionActorMsg(ctx.getResponse(), sid);
-//        } else {
-//            response = new BasicToDeviceSessionActorMsg(ctx.getError(), sid);
-//        }
-//        sendMsgToSessionActor(response, inMsg.getServerAddress());
-//    }
-
-    private void processSubscriptionCommands(ActorContext context, ToDeviceActorMsg msg) {
+    private void processSubscriptionCommands(ActorContext context, DeviceToDeviceActorMsg msg) {
         SessionId sessionId = msg.getSessionId();
         SessionType sessionType = msg.getSessionType();
         FromDeviceMsg inMsg = msg.getPayload();
-        if (inMsg.getMsgType() == MsgType.SUBSCRIBE_ATTRIBUTES_REQUEST) {
+        if (inMsg.getMsgType() == SessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST) {
             logger.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId);
             attributeSubscriptions.put(sessionId, new SessionInfo(sessionType, msg.getServerAddress()));
-        } else if (inMsg.getMsgType() == MsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST) {
+        } else if (inMsg.getMsgType() == SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST) {
             logger.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId);
             attributeSubscriptions.remove(sessionId);
-        } else if (inMsg.getMsgType() == MsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST) {
+        } else if (inMsg.getMsgType() == SessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST) {
             logger.debug("[{}] Registering rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType);
             rpcSubscriptions.put(sessionId, new SessionInfo(sessionType, msg.getServerAddress()));
             sendPendingRequests(context, sessionId, sessionType, msg.getServerAddress());
-        } else if (inMsg.getMsgType() == MsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST) {
+        } else if (inMsg.getMsgType() == SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST) {
             logger.debug("[{}] Canceling rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType);
             rpcSubscriptions.remove(sessionId);
         }
     }
 
-    private void processSessionStateMsgs(ToDeviceActorMsg msg) {
+    private void processSessionStateMsgs(DeviceToDeviceActorMsg msg) {
         SessionId sessionId = msg.getSessionId();
         FromDeviceMsg inMsg = msg.getPayload();
         if (inMsg instanceof SessionOpenMsg) {
             logger.debug("[{}] Processing new session [{}]", deviceId, sessionId);
             sessions.put(sessionId, new SessionInfo(SessionType.ASYNC, msg.getServerAddress()));
+            if (sessions.size() == 1) {
+                reportSessionOpen();
+            }
         } else if (inMsg instanceof SessionCloseMsg) {
             logger.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId);
             sessions.remove(sessionId);
             attributeSubscriptions.remove(sessionId);
             rpcSubscriptions.remove(sessionId);
+            if (sessions.isEmpty()) {
+                reportSessionClose();
+            }
         }
     }
 
@@ -352,17 +527,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
         }
     }
 
-    private List<AttributeKvEntry> fetchAttributes(String scope) {
-        try {
-            //TODO: replace this with async operation. Happens only during actor creation, but is still criticla for performance,
-            return systemContext.getAttributesService().findAll(this.deviceId, scope).get();
-        } catch (InterruptedException | ExecutionException e) {
-            logger.warning("[{}] Failed to fetch attributes for scope: {}", deviceId, scope);
-            throw new RuntimeException(e);
-        }
-    }
-
-    public void processCredentialsUpdate() {
+    void processCredentialsUpdate() {
         sessions.forEach((k, v) -> {
             sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(new SessionCloseNotification(), k), v.getServer());
         });
@@ -370,8 +535,12 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
         rpcSubscriptions.clear();
     }
 
-    public void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) {
+    void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) {
         this.deviceName = msg.getDeviceName();
         this.deviceType = msg.getDeviceType();
+        this.defaultMetaData = new TbMsgMetaData();
+        this.defaultMetaData.putValue("deviceName", deviceName);
+        this.defaultMetaData.putValue("deviceType", deviceType);
     }
+
 }
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorToRuleEngineMsg.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorToRuleEngineMsg.java
new file mode 100644
index 0000000..1426644
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorToRuleEngineMsg.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.actors.device;
+
+import akka.actor.ActorRef;
+import lombok.Data;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.MsgType;
+import org.thingsboard.server.common.msg.TbActorMsg;
+import org.thingsboard.server.common.msg.TbMsg;
+
+/**
+ * Created by ashvayka on 15.03.18.
+ */
+@Data
+public final class DeviceActorToRuleEngineMsg implements TbActorMsg {
+
+    private final ActorRef callbackRef;
+    private final TbMsg tbMsg;
+
+    @Override
+    public MsgType getMsgType() {
+        return MsgType.DEVICE_ACTOR_TO_RULE_ENGINE_MSG;
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/PendingSessionMsgData.java b/application/src/main/java/org/thingsboard/server/actors/device/PendingSessionMsgData.java
new file mode 100644
index 0000000..23ad966
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/device/PendingSessionMsgData.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.actors.device;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.thingsboard.server.common.data.id.SessionId;
+import org.thingsboard.server.common.msg.cluster.ServerAddress;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+
+import java.util.Optional;
+
+/**
+ * Created by ashvayka on 17.04.18.
+ */
+@Data
+@AllArgsConstructor
+public final class PendingSessionMsgData {
+
+    private final SessionId sessionId;
+    private final Optional<ServerAddress> serverAddress;
+    private final SessionMsgType sessionMsgType;
+    private final int requestId;
+    private final boolean replyOnQueueAck;
+    private int ackMsgCount;
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/RuleEngineQueuePutAckMsg.java b/application/src/main/java/org/thingsboard/server/actors/device/RuleEngineQueuePutAckMsg.java
new file mode 100644
index 0000000..0b6fe54
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/device/RuleEngineQueuePutAckMsg.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.actors.device;
+
+import lombok.Data;
+import org.thingsboard.server.common.msg.MsgType;
+import org.thingsboard.server.common.msg.TbActorMsg;
+
+import java.util.UUID;
+
+/**
+ * Created by ashvayka on 15.03.18.
+ */
+@Data
+public final class RuleEngineQueuePutAckMsg implements TbActorMsg {
+
+    private final UUID id;
+
+    @Override
+    public MsgType getMsgType() {
+        return MsgType.RULE_ENGINE_QUEUE_PUT_ACK_MSG;
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java b/application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java
index 01342fd..8a4262c 100644
--- a/application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java
+++ b/application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java
@@ -16,13 +16,13 @@
 package org.thingsboard.server.actors.device;
 
 import lombok.Data;
-import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
+import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
 
 /**
  * @author Andrew Shvayka
  */
 @Data
 public class ToDeviceRpcRequestMetadata {
-    private final ToDeviceRpcRequestPluginMsg msg;
+    private final ToDeviceRpcRequestActorMsg msg;
     private final boolean sent;
 }
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java b/application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java
new file mode 100644
index 0000000..f82a8c2
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.actors.device;
+
+import lombok.Data;
+import org.thingsboard.server.common.data.id.SessionId;
+import org.thingsboard.server.common.msg.cluster.ServerAddress;
+import org.thingsboard.server.common.msg.session.SessionType;
+
+import java.util.Optional;
+
+/**
+ * @author Andrew Shvayka
+ */
+@Data
+public class ToServerRpcRequestMetadata {
+    private final SessionId sessionId;
+    private final SessionType type;
+    private final Optional<ServerAddress> server;
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActor.java b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActor.java
index 88278f3..7cb3161 100644
--- a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActor.java
@@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.msg.TbActorMsg;
 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
-import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
+import org.thingsboard.server.common.msg.timeout.TimeoutMsg;
 import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
 import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
 import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
@@ -153,6 +153,7 @@ public class PluginActor extends ComponentActor<PluginId, PluginActorMessageProc
 
     @Override
     protected long getErrorPersistFrequency() {
-        return systemContext.getPluginErrorPersistFrequency();
+        return 0;
+//        return systemContext.getPluginErrorPersistFrequency();
     }
 }
diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java
index f6bf54d..afd589a 100644
--- a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java
@@ -30,13 +30,14 @@ import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
 import org.thingsboard.server.common.msg.cluster.ServerAddress;
 import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse;
 import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 import org.thingsboard.server.extensions.api.plugins.Plugin;
 import org.thingsboard.server.extensions.api.plugins.PluginInitializationException;
 import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
 import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg;
 import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
-import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
+import org.thingsboard.server.common.msg.timeout.TimeoutMsg;
 import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
 import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
 import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
@@ -108,7 +109,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId>
             } catch (Exception ex) {
                 logger.debug("[{}] Failed to process RuleToPlugin msg: [{}] [{}]", tenantId, msg.getMsg(), ex);
                 RuleToPluginMsg ruleMsg = msg.getMsg();
-                MsgType responceMsgType = MsgType.RULE_ENGINE_ERROR;
+                SessionMsgType responceMsgType = SessionMsgType.RULE_ENGINE_ERROR;
                 Integer requestId = 0;
                 if (ruleMsg.getPayload() instanceof FromDeviceRequestMsg) {
                     requestId = ((FromDeviceRequestMsg) ruleMsg.getPayload()).getRequestId();
@@ -216,7 +217,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId>
     @Override
     public void onStop(ActorContext context) {
         onStop();
-        scheduleMsgWithDelay(context, new PluginTerminationMsg(entityId), systemContext.getPluginActorTerminationDelay());
+//        scheduleMsgWithDelay(context, new PluginTerminationMsg(entityId), systemContext.getPluginActorTerminationDelay());
     }
 
     private void onStop() {
diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java
index 10f16ce..5e16aab 100644
--- a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java
@@ -36,9 +36,12 @@ import org.thingsboard.server.common.data.page.TextPageLink;
 import org.thingsboard.server.common.data.plugin.PluginMetaData;
 import org.thingsboard.server.common.data.relation.EntityRelation;
 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
+import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
 import org.thingsboard.server.common.data.rule.RuleChain;
 import org.thingsboard.server.common.data.rule.RuleMetaData;
 import org.thingsboard.server.common.msg.cluster.ServerAddress;
+import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
+import org.thingsboard.server.common.msg.timeout.TimeoutMsg;
 import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
 import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext;
 import org.thingsboard.server.extensions.api.plugins.PluginCallback;
@@ -348,7 +351,7 @@ public final class PluginProcessingContext implements PluginContext {
                     throw new IllegalStateException("Not Implemented!");
             }
         } else {
-            callback.onSuccess(this, ValidationResult.ok());
+            callback.onSuccess(this, ValidationResult.ok(null));
         }
     }
 
@@ -366,7 +369,7 @@ public final class PluginProcessingContext implements PluginContext {
                     } else if (ctx.isCustomerUser() && !device.getCustomerId().equals(ctx.getCustomerId())) {
                         return ValidationResult.accessDenied("Device doesn't belong to the current Customer!");
                     } else {
-                        return ValidationResult.ok();
+                        return ValidationResult.ok(null);
                     }
                 }
             }));
@@ -387,7 +390,7 @@ public final class PluginProcessingContext implements PluginContext {
                     } else if (ctx.isCustomerUser() && !asset.getCustomerId().equals(ctx.getCustomerId())) {
                         return ValidationResult.accessDenied("Asset doesn't belong to the current Customer!");
                     } else {
-                        return ValidationResult.ok();
+                        return ValidationResult.ok(null);
                     }
                 }
             }));
@@ -408,7 +411,7 @@ public final class PluginProcessingContext implements PluginContext {
                     } else if (ctx.isSystemAdmin() && !rule.getTenantId().isNullUid()) {
                         return ValidationResult.accessDenied("Rule is not in system scope!");
                     } else {
-                        return ValidationResult.ok();
+                        return ValidationResult.ok(null);
                     }
                 }
             }));
@@ -429,7 +432,7 @@ public final class PluginProcessingContext implements PluginContext {
                     } else if (ctx.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) {
                         return ValidationResult.accessDenied("Rule chain is not in system scope!");
                     } else {
-                        return ValidationResult.ok();
+                        return ValidationResult.ok(null);
                     }
                 }
             }));
@@ -451,7 +454,7 @@ public final class PluginProcessingContext implements PluginContext {
                     } else if (ctx.isSystemAdmin() && !plugin.getTenantId().isNullUid()) {
                         return ValidationResult.accessDenied("Plugin is not in system scope!");
                     } else {
-                        return ValidationResult.ok();
+                        return ValidationResult.ok(null);
                     }
                 }
             }));
@@ -472,7 +475,7 @@ public final class PluginProcessingContext implements PluginContext {
                     } else if (ctx.isCustomerUser() && !customer.getId().equals(ctx.getCustomerId())) {
                         return ValidationResult.accessDenied("Customer doesn't relate to the currently authorized customer user!");
                     } else {
-                        return ValidationResult.ok();
+                        return ValidationResult.ok(null);
                     }
                 }
             }));
@@ -483,7 +486,7 @@ public final class PluginProcessingContext implements PluginContext {
         if (ctx.isCustomerUser()) {
             callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
         } else if (ctx.isSystemAdmin()) {
-            callback.onSuccess(this, ValidationResult.ok());
+            callback.onSuccess(this, ValidationResult.ok(null));
         } else {
             ListenableFuture<Tenant> tenantFuture = pluginCtx.tenantService.findTenantByIdAsync(new TenantId(entityId.getId()));
             Futures.addCallback(tenantFuture, getCallback(callback, tenant -> {
@@ -492,7 +495,7 @@ public final class PluginProcessingContext implements PluginContext {
                 } else if (!tenant.getId().equals(ctx.getTenantId())) {
                     return ValidationResult.accessDenied("Tenant doesn't relate to the currently authorized user!");
                 } else {
-                    return ValidationResult.ok();
+                    return ValidationResult.ok(null);
                 }
             }));
         }
diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java b/application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java
index 1828970..dcabedc 100644
--- a/application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java
@@ -18,13 +18,13 @@ package org.thingsboard.server.actors.plugin;
 import akka.actor.ActorRef;
 import lombok.extern.slf4j.Slf4j;
 import org.thingsboard.server.actors.ActorSystemContext;
-import org.thingsboard.server.common.data.Device;
 import org.thingsboard.server.common.data.id.DeviceId;
-import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.PluginId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.msg.cluster.ServerAddress;
+import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
+import org.thingsboard.server.common.msg.timeout.TimeoutMsg;
 import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint;
-import org.thingsboard.server.common.data.id.PluginId;
 import org.thingsboard.server.dao.asset.AssetService;
 import org.thingsboard.server.dao.attributes.AttributesService;
 import org.thingsboard.server.dao.audit.AuditLogService;
@@ -37,9 +37,6 @@ import org.thingsboard.server.dao.rule.RuleService;
 import org.thingsboard.server.dao.tenant.TenantService;
 import org.thingsboard.server.dao.timeseries.TimeseriesService;
 import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
-import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
-import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
-import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
 import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
 import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
 import scala.concurrent.duration.Duration;
@@ -108,8 +105,8 @@ public final class SharedPluginProcessingContext {
 
     public void sendRpcRequest(ToDeviceRpcRequest msg) {
         log.trace("[{}] Forwarding msg {} to device actor!", pluginId, msg);
-        ToDeviceRpcRequestPluginMsg rpcMsg = new ToDeviceRpcRequestPluginMsg(pluginId, tenantId, msg);
-        forward(msg.getDeviceId(), rpcMsg, rpcService::tell);
+//        ToDeviceRpcRequestPluginMsg rpcMsg = new ToDeviceRpcRequestPluginMsg(pluginId, tenantId, msg);
+//        forward(msg.getDeviceId(), rpcMsg, rpcService::tell);
     }
 
     private <T> void forward(DeviceId deviceId, T msg, BiConsumer<ServerAddress, T> rpcFunction) {
diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/ValidationResult.java b/application/src/main/java/org/thingsboard/server/actors/plugin/ValidationResult.java
index f135ffb..0319a3e 100644
--- a/application/src/main/java/org/thingsboard/server/actors/plugin/ValidationResult.java
+++ b/application/src/main/java/org/thingsboard/server/actors/plugin/ValidationResult.java
@@ -20,29 +20,30 @@ import lombok.Data;
 
 @Data
 @AllArgsConstructor
-public class ValidationResult {
+public class ValidationResult<V> {
 
     private final ValidationResultCode resultCode;
     private final String message;
+    private final V v;
 
-    public static ValidationResult ok() {
-        return new ValidationResult(ValidationResultCode.OK, "Ok");
+    public static <V> ValidationResult<V> ok(V v) {
+        return new ValidationResult<>(ValidationResultCode.OK, "Ok", v);
     }
 
-    public static ValidationResult accessDenied(String message) {
-        return new ValidationResult(ValidationResultCode.ACCESS_DENIED, message);
+    public static <V> ValidationResult<V> accessDenied(String message) {
+        return new ValidationResult<>(ValidationResultCode.ACCESS_DENIED, message, null);
     }
 
-    public static ValidationResult entityNotFound(String message) {
-        return new ValidationResult(ValidationResultCode.ENTITY_NOT_FOUND, message);
+    public static <V> ValidationResult<V> entityNotFound(String message) {
+        return new ValidationResult<>(ValidationResultCode.ENTITY_NOT_FOUND, message, null);
     }
 
-    public static ValidationResult unauthorized(String message) {
-        return new ValidationResult(ValidationResultCode.UNAUTHORIZED, message);
+    public static <V> ValidationResult<V> unauthorized(String message) {
+        return new ValidationResult<>(ValidationResultCode.UNAUTHORIZED, message, null);
     }
 
-    public static ValidationResult internalError(String message) {
-        return new ValidationResult(ValidationResultCode.INTERNAL_ERROR, message);
+    public static <V> ValidationResult<V> internalError(String message) {
+        return new ValidationResult<>(ValidationResultCode.INTERNAL_ERROR, message, null);
     }
 
 }
diff --git a/application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java b/application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java
index 4fa2227..bc36dc8 100644
--- a/application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java
+++ b/application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java
@@ -24,10 +24,12 @@ import org.thingsboard.server.actors.service.ActorService;
 import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.PluginId;
 import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
 import org.thingsboard.server.common.msg.cluster.ServerAddress;
 import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
 import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
+import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
 import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
 import org.thingsboard.server.extensions.api.plugins.msg.*;
 import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
@@ -35,6 +37,7 @@ import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg;
 import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
 import org.thingsboard.server.service.cluster.rpc.GrpcSession;
 import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener;
+import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
 
 import java.io.Serializable;
 import java.util.UUID;
@@ -83,7 +86,7 @@ public class BasicRpcSessionListener implements GrpcSessionListener {
     @Override
     public void onToDeviceActorRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceActorRpcMessage msg) {
         log.trace("{} session [{}] received device actor msg {}", getType(session), session.getRemoteServer(), msg);
-        service.onMsg((ToDeviceActorMsg) deserialize(msg.getData().toByteArray()));
+        service.onMsg((DeviceToDeviceActorMsg) deserialize(msg.getData().toByteArray()));
     }
 
     @Override
@@ -139,28 +142,20 @@ public class BasicRpcSessionListener implements GrpcSessionListener {
         return new UUID(uid.getPluginUuidMsb(), uid.getPluginUuidLsb());
     }
 
-    private static ToDeviceRpcRequestPluginMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage msg) {
-        ClusterAPIProtos.PluginAddress address = msg.getAddress();
-        TenantId pluginTenantId = new TenantId(toUUID(address.getTenantId()));
-        PluginId pluginId = new PluginId(toUUID(address.getPluginId()));
-
+    private static ToDeviceRpcRequestActorMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage msg) {
         TenantId deviceTenantId = new TenantId(toUUID(msg.getDeviceTenantId()));
         DeviceId deviceId = new DeviceId(toUUID(msg.getDeviceId()));
 
         ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(msg.getMethod(), msg.getParams());
-        ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), null, deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody);
+        ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody);
 
-        return new ToDeviceRpcRequestPluginMsg(serverAddress, pluginId, pluginTenantId, request);
+        return new ToDeviceRpcRequestActorMsg(serverAddress, request);
     }
 
     private static ToPluginRpcResponseDeviceMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToPluginRpcResponseRpcMessage msg) {
-        ClusterAPIProtos.PluginAddress address = msg.getAddress();
-        TenantId pluginTenantId = new TenantId(toUUID(address.getTenantId()));
-        PluginId pluginId = new PluginId(toUUID(address.getPluginId()));
-
         RpcError error = !StringUtils.isEmpty(msg.getError()) ? RpcError.valueOf(msg.getError()) : null;
         FromDeviceRpcResponse response = new FromDeviceRpcResponse(toUUID(msg.getMsgId()), msg.getResponse(), error);
-        return new ToPluginRpcResponseDeviceMsg(pluginId, pluginTenantId, response);
+        return new ToPluginRpcResponseDeviceMsg(null, null, response);
     }
 
     @SuppressWarnings("unchecked")
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
index 8ffd378..f6c45c0 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
@@ -16,15 +16,20 @@
 package org.thingsboard.server.actors.ruleChain;
 
 import akka.actor.ActorRef;
-import akka.actor.Cancellable;
+import com.datastax.driver.core.utils.UUIDs;
 import com.google.common.base.Function;
 import org.thingsboard.rule.engine.api.*;
 import org.thingsboard.server.actors.ActorSystemContext;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.id.RuleNodeId;
 import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
 import org.thingsboard.server.common.data.rule.RuleNode;
 import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.TbMsgMetaData;
 import org.thingsboard.server.common.msg.cluster.ServerAddress;
+import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
 import org.thingsboard.server.dao.alarm.AlarmService;
 import org.thingsboard.server.dao.asset.AssetService;
 import org.thingsboard.server.dao.attributes.AttributesService;
@@ -41,13 +46,13 @@ import scala.concurrent.duration.Duration;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 /**
  * Created by ashvayka on 19.03.18.
  */
 class DefaultTbContext implements TbContext {
 
-    private static final Function<? super List<Void>, ? extends Void> LIST_VOID_FUNCTION = v -> null;
     private final ActorSystemContext mainCtx;
     private final RuleNodeCtx nodeCtx;
 
@@ -63,8 +68,13 @@ class DefaultTbContext implements TbContext {
 
     @Override
     public void tellNext(TbMsg msg, String relationType) {
+        tellNext(msg, relationType, null);
+    }
+
+    @Override
+    public void tellNext(TbMsg msg, String relationType, Throwable th) {
         if (nodeCtx.getSelf().isDebugMode()) {
-            mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg);
+            mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, th);
         }
         nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationType, msg), nodeCtx.getSelfActor());
     }
@@ -113,6 +123,16 @@ class DefaultTbContext implements TbContext {
     }
 
     @Override
+    public TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) {
+        return new TbMsg(UUIDs.timeBased(), type, originator, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), 0L);
+    }
+
+    @Override
+    public TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) {
+        return new TbMsg(origMsg.getId(), type, originator, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), 0L);
+    }
+
+    @Override
     public RuleNodeId getSelfId() {
         return nodeCtx.getSelf().getId();
     }
@@ -143,6 +163,11 @@ class DefaultTbContext implements TbContext {
     }
 
     @Override
+    public ListeningExecutor getExternalCallExecutor() {
+        return mainCtx.getExternalCallExecutorService();
+    }
+
+    @Override
     public ScriptEngine createJsScriptEngine(String script, String functionName, String... argNames) {
         return new NashornJsEngine(script, functionName, argNames);
     }
@@ -206,4 +231,28 @@ class DefaultTbContext implements TbContext {
     public MailService getMailService() {
         return mainCtx.getMailService();
     }
+
+    @Override
+    public RuleEngineRpcService getRpcService() {
+        return new RuleEngineRpcService() {
+            @Override
+            public void sendRpcReply(DeviceId deviceId, int requestId, String body) {
+                mainCtx.getDeviceRpcService().sendRpcReplyToDevice(nodeCtx.getTenantId(), deviceId, requestId, body);
+            }
+
+            @Override
+            public void sendRpcRequest(RuleEngineDeviceRpcRequest src, Consumer<RuleEngineDeviceRpcResponse> consumer) {
+                ToDeviceRpcRequest request = new ToDeviceRpcRequest(UUIDs.timeBased(), nodeCtx.getTenantId(), src.getDeviceId(),
+                        src.isOneway(), System.currentTimeMillis() + src.getTimeout(), new ToDeviceRpcRequestBody(src.getMethod(), src.getBody()));
+                mainCtx.getDeviceRpcService().process(request, response -> {
+                    consumer.accept(RuleEngineDeviceRpcResponse.builder()
+                            .deviceId(src.getDeviceId())
+                            .requestId(src.getRequestId())
+                            .error(response.getError())
+                            .response(response.getResponse())
+                            .build());
+                });
+            }
+        };
+    }
 }
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java
index f539e32..4812002 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java
@@ -18,6 +18,7 @@ package org.thingsboard.server.actors.ruleChain;
 import akka.actor.OneForOneStrategy;
 import akka.actor.SupervisorStrategy;
 import org.thingsboard.server.actors.ActorSystemContext;
+import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg;
 import org.thingsboard.server.actors.service.ComponentActor;
 import org.thingsboard.server.actors.service.ContextBasedCreator;
 import org.thingsboard.server.common.data.id.RuleChainId;
@@ -44,9 +45,15 @@ public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMe
             case SERVICE_TO_RULE_ENGINE_MSG:
                 processor.onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg);
                 break;
+            case DEVICE_ACTOR_TO_RULE_ENGINE_MSG:
+                processor.onDeviceActorToRuleEngineMsg((DeviceActorToRuleEngineMsg) msg);
+                break;
             case RULE_TO_RULE_CHAIN_TELL_NEXT_MSG:
                 processor.onTellNext((RuleNodeToRuleChainTellNextMsg) msg);
                 break;
+            case RULE_CHAIN_TO_RULE_CHAIN_MSG:
+                processor.onRuleChainToRuleChainMsg((RuleChainToRuleChainMsg) msg);
+                break;
             default:
                 return false;
         }
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
index d588a63..2cd1755 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
@@ -19,7 +19,10 @@ import akka.actor.ActorContext;
 import akka.actor.ActorRef;
 import akka.actor.Props;
 import akka.event.LoggingAdapter;
+import com.datastax.driver.core.utils.UUIDs;
 import org.thingsboard.server.actors.ActorSystemContext;
+import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg;
+import org.thingsboard.server.actors.device.RuleEngineQueuePutAckMsg;
 import org.thingsboard.server.actors.service.DefaultActorService;
 import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
 import org.thingsboard.server.common.data.EntityType;
@@ -39,6 +42,7 @@ import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
 import org.thingsboard.server.dao.rule.RuleChainService;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -50,6 +54,7 @@ import java.util.stream.Collectors;
  */
 public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleChainId> {
 
+    private static final long DEFAULT_CLUSTER_PARTITION = 0L;
     private final ActorRef parent;
     private final ActorRef self;
     private final Map<RuleNodeId, RuleNodeCtx> nodeActors;
@@ -58,6 +63,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
 
     private RuleNodeId firstId;
     private RuleNodeCtx firstNode;
+    private boolean started;
 
     RuleChainActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, ActorSystemContext systemContext
             , LoggingAdapter logger, ActorRef parent, ActorRef self) {
@@ -71,14 +77,33 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
 
     @Override
     public void start(ActorContext context) throws Exception {
-        RuleChain ruleChain = service.findRuleChainById(entityId);
-        List<RuleNode> ruleNodeList = service.getRuleChainNodes(entityId);
-        // Creating and starting the actors;
+        if (!started) {
+            RuleChain ruleChain = service.findRuleChainById(entityId);
+            List<RuleNode> ruleNodeList = service.getRuleChainNodes(entityId);
+            // Creating and starting the actors;
+            for (RuleNode ruleNode : ruleNodeList) {
+                ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
+                nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
+            }
+            initRoutes(ruleChain, ruleNodeList);
+            reprocess(ruleNodeList);
+            started = true;
+        } else {
+            onUpdate(context);
+        }
+    }
+
+    private void reprocess(List<RuleNode> ruleNodeList) {
         for (RuleNode ruleNode : ruleNodeList) {
-            ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
-            nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
+            for (TbMsg tbMsg : queue.findUnprocessed(ruleNode.getId().getId(), 0L)) {
+                pushMsgToNode(nodeActors.get(ruleNode.getId()), tbMsg);
+            }
+        }
+        if (firstNode != null) {
+            for (TbMsg tbMsg : queue.findUnprocessed(entityId.getId(), 0L)) {
+                pushMsgToNode(firstNode, tbMsg);
+            }
         }
-        initRoutes(ruleChain, ruleNodeList);
     }
 
     @Override
@@ -105,6 +130,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
         });
 
         initRoutes(ruleChain, ruleNodeList);
+        reprocess(ruleNodeList);
     }
 
     @Override
@@ -113,6 +139,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
         nodeActors.clear();
         nodeRoutes.clear();
         context.stop(self);
+        started = false;
     }
 
     @Override
@@ -133,15 +160,19 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
         // Populating the routes map;
         for (RuleNode ruleNode : ruleNodeList) {
             List<EntityRelation> relations = service.getRuleNodeRelations(ruleNode.getId());
-            for (EntityRelation relation : relations) {
-                if (relation.getTo().getEntityType() == EntityType.RULE_NODE) {
-                    RuleNodeCtx ruleNodeCtx = nodeActors.get(new RuleNodeId(relation.getTo().getId()));
-                    if (ruleNodeCtx == null) {
-                        throw new IllegalArgumentException("Rule Node [" + relation.getFrom() + "] has invalid relation to Rule node [" + relation.getTo() + "]");
+            if (relations.size() == 0) {
+                nodeRoutes.put(ruleNode.getId(), Collections.emptyList());
+            } else {
+                for (EntityRelation relation : relations) {
+                    if (relation.getTo().getEntityType() == EntityType.RULE_NODE) {
+                        RuleNodeCtx ruleNodeCtx = nodeActors.get(new RuleNodeId(relation.getTo().getId()));
+                        if (ruleNodeCtx == null) {
+                            throw new IllegalArgumentException("Rule Node [" + relation.getFrom() + "] has invalid relation to Rule node [" + relation.getTo() + "]");
+                        }
                     }
+                    nodeRoutes.computeIfAbsent(ruleNode.getId(), k -> new ArrayList<>())
+                            .add(new RuleNodeRelation(ruleNode.getId(), relation.getTo(), relation.getType()));
                 }
-                nodeRoutes.computeIfAbsent(ruleNode.getId(), k -> new ArrayList<>())
-                        .add(new RuleNodeRelation(ruleNode.getId(), relation.getTo(), relation.getType()));
             }
         }
 
@@ -152,37 +183,81 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
 
     void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg envelope) {
         checkActive();
-        TbMsg tbMsg = envelope.getTbMsg();
-        //TODO: push to queue and act on ack in async way
-        pushMsgToNode(firstNode, tbMsg);
+        putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> pushMsgToNode(firstNode, msg));
+    }
+
+    void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg envelope) {
+        checkActive();
+        putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> {
+            pushMsgToNode(firstNode, msg);
+            envelope.getCallbackRef().tell(new RuleEngineQueuePutAckMsg(msg.getId()), self);
+        });
+    }
+
+    void onRuleChainToRuleChainMsg(RuleChainToRuleChainMsg envelope) {
+        checkActive();
+        if (envelope.isEnqueue()) {
+            putToQueue(enrichWithRuleChainId(envelope.getMsg()), msg -> pushMsgToNode(firstNode, msg));
+        } else {
+            pushMsgToNode(firstNode, envelope.getMsg());
+        }
     }
 
     void onTellNext(RuleNodeToRuleChainTellNextMsg envelope) {
         checkActive();
         RuleNodeId originator = envelope.getOriginator();
         String targetRelationType = envelope.getRelationType();
-        List<RuleNodeRelation> relations = nodeRoutes.get(originator);
-        if (relations == null) {
-            return;
-        }
-        boolean copy = relations.size() > 1;
-        for (RuleNodeRelation relation : relations) {
-            TbMsg msg = envelope.getMsg();
-            if (copy) {
-                msg = msg.copy();
+        List<RuleNodeRelation> relations = nodeRoutes.get(originator).stream()
+                .filter(r -> targetRelationType == null || targetRelationType.equalsIgnoreCase(r.getType()))
+                .collect(Collectors.toList());
+
+        TbMsg msg = envelope.getMsg();
+        int relationsCount = relations.size();
+        EntityId ackId = msg.getRuleNodeId() != null ? msg.getRuleNodeId() : msg.getRuleChainId();
+        if (relationsCount == 0) {
+            queue.ack(msg, ackId.getId(), msg.getClusterPartition());
+        } else if (relationsCount == 1) {
+            for (RuleNodeRelation relation : relations) {
+                pushToTarget(msg, relation.getOut());
             }
-            if (targetRelationType == null || targetRelationType.equalsIgnoreCase(relation.getType())) {
-                switch (relation.getOut().getEntityType()) {
+        } else {
+            for (RuleNodeRelation relation : relations) {
+                EntityId target = relation.getOut();
+                switch (target.getEntityType()) {
                     case RULE_NODE:
-                        RuleNodeId targetRuleNodeId = new RuleNodeId(relation.getOut().getId());
-                        RuleNodeCtx targetRuleNode = nodeActors.get(targetRuleNodeId);
-                        pushMsgToNode(targetRuleNode, msg);
+                        enqueueAndForwardMsgCopyToNode(msg, target);
                         break;
                     case RULE_CHAIN:
-//                        TODO: implement
+                        enqueueAndForwardMsgCopyToChain(msg, target);
                         break;
                 }
             }
+            //TODO: Ideally this should happen in async way when all targets confirm that the copied messages are successfully written to corresponding target queues.
+            queue.ack(msg, ackId.getId(), msg.getClusterPartition());
+        }
+    }
+
+    private void enqueueAndForwardMsgCopyToChain(TbMsg msg, EntityId target) {
+        RuleChainId targetRCId = new RuleChainId(target.getId());
+        TbMsg copyMsg = msg.copy(UUIDs.timeBased(), targetRCId, null, DEFAULT_CLUSTER_PARTITION);
+        parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, copyMsg, true), self);
+    }
+
+    private void enqueueAndForwardMsgCopyToNode(TbMsg msg, EntityId target) {
+        RuleNodeId targetId = new RuleNodeId(target.getId());
+        RuleNodeCtx targetNodeCtx = nodeActors.get(targetId);
+        TbMsg copy = msg.copy(UUIDs.timeBased(), entityId, targetId, DEFAULT_CLUSTER_PARTITION);
+        putToQueue(copy, queuedMsg -> pushMsgToNode(targetNodeCtx, queuedMsg));
+    }
+
+    private void pushToTarget(TbMsg msg, EntityId target) {
+        switch (target.getEntityType()) {
+            case RULE_NODE:
+                pushMsgToNode(nodeActors.get(new RuleNodeId(target.getId())), msg);
+                break;
+            case RULE_CHAIN:
+                parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, msg, false), self);
+                break;
         }
     }
 
@@ -192,4 +267,8 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
         }
     }
 
+    private TbMsg enrichWithRuleChainId(TbMsg tbMsg) {
+        // We don't put firstNodeId because it may change over time;
+        return new TbMsg(tbMsg.getId(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.getMetaData(), tbMsg.getData(), entityId, null, 0L);
+    }
 }
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java
index 940bd5b..a67b4f9 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java
@@ -31,31 +31,29 @@ import org.thingsboard.server.dao.rule.RuleChainService;
 public abstract class RuleChainManagerActor extends ContextAwareActor {
 
     protected final RuleChainManager ruleChainManager;
-    protected final PluginManager pluginManager;
     protected final RuleChainService ruleChainService;
 
     public RuleChainManagerActor(ActorSystemContext systemContext, RuleChainManager ruleChainManager, PluginManager pluginManager) {
         super(systemContext);
         this.ruleChainManager = ruleChainManager;
-        this.pluginManager = pluginManager;
         this.ruleChainService = systemContext.getRuleChainService();
     }
 
     protected void initRuleChains() {
-        pluginManager.init(this.context());
         ruleChainManager.init(this.context());
     }
 
     protected ActorRef getEntityActorRef(EntityId entityId) {
         ActorRef target = null;
         switch (entityId.getEntityType()) {
-            case PLUGIN:
-                target = pluginManager.getOrCreateActor(this.context(), (PluginId) entityId);
-                break;
             case RULE_CHAIN:
                 target = ruleChainManager.getOrCreateActor(this.context(), (RuleChainId) entityId);
                 break;
         }
         return target;
     }
+
+    protected void broadcast(Object msg) {
+        ruleChainManager.broadcast(msg);
+    }
 }
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java
new file mode 100644
index 0000000..2b2623b
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.actors.ruleChain;
+
+import lombok.Data;
+import org.thingsboard.rule.engine.api.TbContext;
+import org.thingsboard.server.common.data.id.RuleChainId;
+import org.thingsboard.server.common.msg.MsgType;
+import org.thingsboard.server.common.msg.TbActorMsg;
+import org.thingsboard.server.common.msg.TbMsg;
+
+/**
+ * Created by ashvayka on 19.03.18.
+ */
+@Data
+public final class RuleChainToRuleChainMsg implements TbActorMsg {
+
+    private final RuleChainId target;
+    private final RuleChainId source;
+    private final TbMsg msg;
+    private final boolean enqueue;
+
+    @Override
+    public MsgType getMsgType() {
+        return MsgType.RULE_CHAIN_TO_RULE_CHAIN_MSG;
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/service/ActorService.java b/application/src/main/java/org/thingsboard/server/actors/service/ActorService.java
index 0be0385..e15eb2f 100644
--- a/application/src/main/java/org/thingsboard/server/actors/service/ActorService.java
+++ b/application/src/main/java/org/thingsboard/server/actors/service/ActorService.java
@@ -32,4 +32,5 @@ public interface ActorService extends SessionMsgProcessor, WebSocketMsgProcessor
     void onCredentialsUpdate(TenantId tenantId, DeviceId deviceId);
 
     void onDeviceNameOrTypeUpdate(TenantId tenantId, DeviceId deviceId, String deviceName, String deviceType);
+
 }
diff --git a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
index fbb5a14..df0d122 100644
--- a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
+++ b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
@@ -39,7 +39,7 @@ import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
 import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
 import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
 import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
 import org.thingsboard.server.extensions.api.device.DeviceCredentialsUpdateNotificationMsg;
 import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
@@ -156,7 +156,7 @@ public class DefaultActorService implements ActorService {
     }
 
     @Override
-    public void onMsg(ToDeviceActorMsg msg) {
+    public void onMsg(DeviceToDeviceActorMsg msg) {
         log.trace("Processing device rpc msg: {}", msg);
         appActor.tell(msg, ActorRef.noSender());
     }
diff --git a/application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java
index 96526dd..bbaef5d 100644
--- a/application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java
@@ -22,8 +22,8 @@ import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.SessionId;
 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
 import org.thingsboard.server.common.msg.cluster.ServerAddress;
-import org.thingsboard.server.common.msg.device.BasicToDeviceActorMsg;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.BasicDeviceToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.session.*;
 import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg;
 
@@ -37,7 +37,7 @@ abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgP
 
     protected final SessionId sessionId;
     protected SessionContext sessionCtx;
-    protected ToDeviceActorMsg toDeviceActorMsgPrototype;
+    protected DeviceToDeviceActorMsg deviceToDeviceActorMsgPrototype;
 
     protected AbstractSessionActorMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) {
         super(ctx, logger);
@@ -64,29 +64,29 @@ abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgP
 
     protected void updateSessionCtx(ToDeviceActorSessionMsg msg, SessionType type) {
         sessionCtx = msg.getSessionMsg().getSessionContext();
-        toDeviceActorMsgPrototype = new BasicToDeviceActorMsg(msg, type);
+        deviceToDeviceActorMsgPrototype = new BasicDeviceToDeviceActorMsg(msg, type);
     }
 
-    protected ToDeviceActorMsg toDeviceMsg(ToDeviceActorSessionMsg msg) {
+    protected DeviceToDeviceActorMsg toDeviceMsg(ToDeviceActorSessionMsg msg) {
         AdaptorToSessionActorMsg adaptorMsg = msg.getSessionMsg();
-        return new BasicToDeviceActorMsg(toDeviceActorMsgPrototype, adaptorMsg.getMsg());
+        return new BasicDeviceToDeviceActorMsg(deviceToDeviceActorMsgPrototype, adaptorMsg.getMsg());
     }
 
-    protected Optional<ToDeviceActorMsg> toDeviceMsg(FromDeviceMsg msg) {
-        if (toDeviceActorMsgPrototype != null) {
-            return Optional.of(new BasicToDeviceActorMsg(toDeviceActorMsgPrototype, msg));
+    protected Optional<DeviceToDeviceActorMsg> toDeviceMsg(FromDeviceMsg msg) {
+        if (deviceToDeviceActorMsgPrototype != null) {
+            return Optional.of(new BasicDeviceToDeviceActorMsg(deviceToDeviceActorMsgPrototype, msg));
         } else {
             return Optional.empty();
         }
     }
 
-    protected Optional<ServerAddress> forwardToAppActor(ActorContext ctx, ToDeviceActorMsg toForward) {
+    protected Optional<ServerAddress> forwardToAppActor(ActorContext ctx, DeviceToDeviceActorMsg toForward) {
         Optional<ServerAddress> address = systemContext.getRoutingService().resolveById(toForward.getDeviceId());
         forwardToAppActor(ctx, toForward, address);
         return address;
     }
 
-    protected Optional<ServerAddress> forwardToAppActorIfAdressChanged(ActorContext ctx, ToDeviceActorMsg toForward, Optional<ServerAddress> oldAddress) {
+    protected Optional<ServerAddress> forwardToAppActorIfAdressChanged(ActorContext ctx, DeviceToDeviceActorMsg toForward, Optional<ServerAddress> oldAddress) {
         Optional<ServerAddress> newAddress = systemContext.getRoutingService().resolveById(toForward.getDeviceId());
         if (!newAddress.equals(oldAddress)) {
             if (newAddress.isPresent()) {
@@ -99,7 +99,7 @@ abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgP
         return newAddress;
     }
 
-    protected void forwardToAppActor(ActorContext ctx, ToDeviceActorMsg toForward, Optional<ServerAddress> address) {
+    protected void forwardToAppActor(ActorContext ctx, DeviceToDeviceActorMsg toForward, Optional<ServerAddress> address) {
         if (address.isPresent()) {
             systemContext.getRpcService().tell(address.get(),
                     toForward.toOtherAddress(systemContext.getRoutingService().getCurrentServer()));
@@ -114,6 +114,6 @@ abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgP
     }
 
     public DeviceId getDeviceId() {
-        return toDeviceActorMsgPrototype.getDeviceId();
+        return deviceToDeviceActorMsgPrototype.getDeviceId();
     }
 }
diff --git a/application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java
index ab75cdb..fa5287f 100644
--- a/application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java
@@ -22,12 +22,11 @@ import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
 import org.thingsboard.server.common.msg.cluster.ServerAddress;
 import org.thingsboard.server.common.msg.core.*;
 import org.thingsboard.server.common.msg.core.SessionCloseMsg;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.session.*;
 
 import akka.actor.ActorContext;
 import akka.event.LoggingAdapter;
-import org.thingsboard.server.common.msg.session.ctrl.*;
 import org.thingsboard.server.common.msg.session.ex.SessionException;
 
 import java.util.HashMap;
@@ -37,7 +36,7 @@ import java.util.Optional;
 class ASyncMsgProcessor extends AbstractSessionActorMsgProcessor {
 
     private boolean firstMsg = true;
-    private Map<Integer, ToDeviceActorMsg> pendingMap = new HashMap<>();
+    private Map<Integer, DeviceToDeviceActorMsg> pendingMap = new HashMap<>();
     private Optional<ServerAddress> currentTargetServer;
     private boolean subscribedToAttributeUpdates;
     private boolean subscribedToRpcCommands;
@@ -53,7 +52,7 @@ class ASyncMsgProcessor extends AbstractSessionActorMsgProcessor {
             toDeviceMsg(new SessionOpenMsg()).ifPresent(m -> forwardToAppActor(ctx, m));
             firstMsg = false;
         }
-        ToDeviceActorMsg pendingMsg = toDeviceMsg(msg);
+        DeviceToDeviceActorMsg pendingMsg = toDeviceMsg(msg);
         FromDeviceMsg fromDeviceMsg = pendingMsg.getPayload();
         switch (fromDeviceMsg.getMsgType()) {
             case POST_TELEMETRY_REQUEST:
@@ -86,8 +85,8 @@ class ASyncMsgProcessor extends AbstractSessionActorMsgProcessor {
     @Override
     public void processToDeviceMsg(ActorContext context, ToDeviceMsg msg) {
         try {
-            if (msg.getMsgType() != MsgType.SESSION_CLOSE) {
-                switch (msg.getMsgType()) {
+            if (msg.getSessionMsgType() != SessionMsgType.SESSION_CLOSE) {
+                switch (msg.getSessionMsgType()) {
                     case STATUS_CODE_RESPONSE:
                     case GET_ATTRIBUTES_RESPONSE:
                         ResponseMsg responseMsg = (ResponseMsg) msg;
diff --git a/application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java
index d696503..7f520b5 100644
--- a/application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java
@@ -20,7 +20,7 @@ import org.thingsboard.server.actors.shared.SessionTimeoutMsg;
 import org.thingsboard.server.common.data.id.SessionId;
 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
 import org.thingsboard.server.common.msg.cluster.ServerAddress;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.session.*;
 import org.thingsboard.server.common.msg.session.ToDeviceActorSessionMsg;
 import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg;
@@ -32,7 +32,7 @@ import akka.event.LoggingAdapter;
 import java.util.Optional;
 
 class SyncMsgProcessor extends AbstractSessionActorMsgProcessor {
-    private ToDeviceActorMsg pendingMsg;
+    private DeviceToDeviceActorMsg pendingMsg;
     private Optional<ServerAddress> currentTargetServer;
     private boolean pendingResponse;
 
diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java
index e25d3a7..4a7e072 100644
--- a/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java
@@ -17,22 +17,32 @@ package org.thingsboard.server.actors.shared;
 
 import akka.actor.ActorContext;
 import akka.event.LoggingAdapter;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
 import org.thingsboard.server.actors.ActorSystemContext;
 import org.thingsboard.server.actors.stats.StatsPersistTick;
+import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
+import org.thingsboard.server.common.msg.TbMsg;
 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
+import org.thingsboard.server.dao.queue.MsgQueue;
 
-public abstract class ComponentMsgProcessor<T> extends AbstractContextAwareMsgProcessor {
+import javax.annotation.Nullable;
+import java.util.function.Consumer;
+
+public abstract class ComponentMsgProcessor<T extends EntityId> extends AbstractContextAwareMsgProcessor {
 
     protected final TenantId tenantId;
     protected final T entityId;
+    protected final MsgQueue queue;
     protected ComponentLifecycleState state;
 
     protected ComponentMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, T id) {
         super(systemContext, logger);
         this.tenantId = tenantId;
         this.entityId = id;
+        this.queue = systemContext.getMsgQueue();
     }
 
     public abstract void start(ActorContext context) throws Exception;
@@ -75,4 +85,19 @@ public abstract class ComponentMsgProcessor<T> extends AbstractContextAwareMsgPr
             throw new IllegalStateException("Rule chain is not active!");
         }
     }
+
+    protected void putToQueue(final TbMsg tbMsg, final Consumer<TbMsg> onSuccess) {
+        EntityId entityId = tbMsg.getRuleNodeId() != null ? tbMsg.getRuleNodeId() : tbMsg.getRuleChainId();
+        Futures.addCallback(queue.put(tbMsg, entityId.getId(), 0), new FutureCallback<Void>() {
+            @Override
+            public void onSuccess(@Nullable Void result) {
+                onSuccess.accept(tbMsg);
+            }
+
+            @Override
+            public void onFailure(Throwable t) {
+                logger.debug("Failed to push message [{}] to queue due to [{}]", tbMsg, t);
+            }
+        });
+    }
 }
diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
index d53c054..10e247b 100644
--- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
@@ -16,13 +16,16 @@
 package org.thingsboard.server.actors.tenant;
 
 import akka.actor.ActorRef;
+import akka.actor.OneForOneStrategy;
 import akka.actor.Props;
-import akka.event.Logging;
-import akka.event.LoggingAdapter;
+import akka.actor.SupervisorStrategy;
+import akka.japi.Function;
 import org.thingsboard.server.actors.ActorSystemContext;
 import org.thingsboard.server.actors.device.DeviceActor;
+import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg;
 import org.thingsboard.server.actors.plugin.PluginTerminationMsg;
 import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor;
+import org.thingsboard.server.actors.ruleChain.RuleChainToRuleChainMsg;
 import org.thingsboard.server.actors.service.ContextBasedCreator;
 import org.thingsboard.server.actors.service.DefaultActorService;
 import org.thingsboard.server.actors.shared.plugin.TenantPluginManager;
@@ -30,11 +33,13 @@ import org.thingsboard.server.actors.shared.rulechain.TenantRuleChainManager;
 import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.msg.TbActorMsg;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
 import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
 import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
 import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
+import scala.concurrent.duration.Duration;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -50,6 +55,12 @@ public class TenantActor extends RuleChainManagerActor {
         this.deviceActors = new HashMap<>();
     }
 
+
+    @Override
+    public SupervisorStrategy supervisorStrategy() {
+        return strategy;
+    }
+
     @Override
     public void preStart() {
         logger.info("[{}] Starting tenant actor.", tenantId);
@@ -64,63 +75,56 @@ public class TenantActor extends RuleChainManagerActor {
     @Override
     protected boolean process(TbActorMsg msg) {
         switch (msg.getMsgType()) {
+            case CLUSTER_EVENT_MSG:
+                broadcast(msg);
+                break;
             case COMPONENT_LIFE_CYCLE_MSG:
                 onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
                 break;
             case SERVICE_TO_RULE_ENGINE_MSG:
                 onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg);
                 break;
+            case DEVICE_ACTOR_TO_RULE_ENGINE_MSG:
+                onDeviceActorToRuleEngineMsg((DeviceActorToRuleEngineMsg) msg);
+                break;
+            case DEVICE_SESSION_TO_DEVICE_ACTOR_MSG:
+            case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG:
+            case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG:
+            case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG:
+            case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
+            case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
+                onToDeviceActorMsg((DeviceAwareMsg) msg);
+                break;
+            case RULE_CHAIN_TO_RULE_CHAIN_MSG:
+                onRuleChainMsg((RuleChainToRuleChainMsg) msg);
+                break;
             default:
                 return false;
         }
         return true;
     }
 
+    @Override
+    protected void broadcast(Object msg) {
+        super.broadcast(msg);
+        deviceActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
+    }
+
     private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) {
         ruleChainManager.getRootChainActor().tell(msg, self());
     }
 
-
-//    @Override
-//    public void onReceive(Object msg) throws Exception {
-//        logger.debug("[{}] Received message: {}", tenantId, msg);
-//        if (msg instanceof ToDeviceActorMsg) {
-//            onToDeviceActorMsg((ToDeviceActorMsg) msg);
-//        } else if (msg instanceof ToPluginActorMsg) {
-//            onToPluginMsg((ToPluginActorMsg) msg);
-//        } else if (msg instanceof ToDeviceActorNotificationMsg) {
-//            onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg);
-//        } else if (msg instanceof ClusterEventMsg) {
-//            broadcast(msg);
-//        } else if (msg instanceof ComponentLifecycleMsg) {
-//            onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
-//        } else if (msg instanceof PluginTerminationMsg) {
-//            onPluginTerminated((PluginTerminationMsg) msg);
-//        } else {
-//            logger.warning("[{}] Unknown message: {}!", tenantId, msg);
-//        }
-//    }
-
-    private void broadcast(Object msg) {
-        pluginManager.broadcast(msg);
-        deviceActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
+    private void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg msg) {
+        ruleChainManager.getRootChainActor().tell(msg, self());
     }
 
-    private void onToDeviceActorMsg(ToDeviceActorMsg msg) {
-        getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender());
+    private void onRuleChainMsg(RuleChainToRuleChainMsg msg) {
+        ruleChainManager.getOrCreateActor(context(), msg.getTarget()).tell(msg, self());
     }
 
-    private void onToDeviceActorMsg(ToDeviceActorNotificationMsg msg) {
-        getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender());
-    }
 
-    private void onToPluginMsg(ToPluginActorMsg msg) {
-        if (msg.getPluginTenantId().equals(tenantId)) {
-            ActorRef pluginActor = pluginManager.getOrCreateActor(this.context(), msg.getPluginId());
-            pluginActor.tell(msg, ActorRef.noSender());
-        } else {
-            context().parent().tell(msg, ActorRef.noSender());
-        }
+    private void onToDeviceActorMsg(DeviceAwareMsg msg) {
+        getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender());
     }
 
     private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
@@ -132,11 +136,6 @@ public class TenantActor extends RuleChainManagerActor {
         }
     }
 
-    private void onPluginTerminated(PluginTerminationMsg msg) {
-        pluginManager.remove(msg.getId());
-    }
-
-
     private ActorRef getOrCreateDeviceActor(DeviceId deviceId) {
         return deviceActors.computeIfAbsent(deviceId, k -> context().actorOf(Props.create(new DeviceActor.ActorCreator(systemContext, tenantId, deviceId))
                 .withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), deviceId.toString()));
@@ -158,4 +157,12 @@ public class TenantActor extends RuleChainManagerActor {
         }
     }
 
+    private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), new Function<Throwable, SupervisorStrategy.Directive>() {
+        @Override
+        public SupervisorStrategy.Directive apply(Throwable t) {
+            logger.error(t, "Unknown failure");
+            return SupervisorStrategy.resume();
+        }
+    });
+
 }
diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
index 29436c1..a35e5dc 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -63,6 +63,7 @@ import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
 import org.thingsboard.server.common.data.exception.ThingsboardException;
 import org.thingsboard.server.service.component.ComponentDiscoveryService;
 import org.thingsboard.server.service.security.model.SecurityUser;
+import org.thingsboard.server.service.state.DeviceStateService;
 
 import javax.mail.MessagingException;
 import javax.servlet.http.HttpServletRequest;
@@ -137,6 +138,9 @@ public abstract class BaseController {
     @Autowired
     protected DeviceOfflineService offlineService;
 
+    @Autowired
+    protected DeviceStateService deviceStateService;
+
     @ExceptionHandler(ThingsboardException.class)
     public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) {
         errorResponseHandler.handle(ex, response);
@@ -606,7 +610,7 @@ public abstract class BaseController {
         auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo);
     }
 
-    protected static Exception toException(Throwable error) {
+    public static Exception toException(Throwable error) {
         return Exception.class.isInstance(error) ? (Exception) error : new Exception(error);
     }
 
diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
index f97603e..5322b2c 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
@@ -90,6 +90,11 @@ public class DeviceController extends BaseController {
                     savedDevice.getCustomerId(),
                     device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
 
+            if (device.getId() == null) {
+                deviceStateService.onDeviceAdded(savedDevice);
+            } else {
+                deviceStateService.onDeviceUpdated(savedDevice);
+            }
             return savedDevice;
         } catch (Exception e) {
             logEntityAction(emptyId(EntityType.DEVICE), device,
@@ -112,6 +117,7 @@ public class DeviceController extends BaseController {
                     device.getCustomerId(),
                     ActionType.DELETED, null, strDeviceId);
 
+            deviceStateService.onDeviceDeleted(device);
         } catch (Exception e) {
             logEntityAction(emptyId(EntityType.DEVICE),
                     null,
@@ -387,7 +393,7 @@ public class DeviceController extends BaseController {
     @RequestMapping(value = "/device/online", method = RequestMethod.GET)
     @ResponseBody
     public List<Device> getOnlineDevices(@RequestParam("contactType") DeviceStatusQuery.ContactType contactType,
-                                          @RequestParam("threshold") long threshold) throws ThingsboardException {
+                                         @RequestParam("threshold") long threshold) throws ThingsboardException {
         try {
             TenantId tenantId = getCurrentUser().getTenantId();
             ListenableFuture<List<Device>> offlineDevices = offlineService.findOnlineDevices(tenantId.getId(), contactType, threshold);
diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcController.java b/application/src/main/java/org/thingsboard/server/controller/RpcController.java
new file mode 100644
index 0000000..28261be
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/RpcController.java
@@ -0,0 +1,225 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.controller;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.util.concurrent.FutureCallback;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.context.request.async.DeferredResult;
+import org.thingsboard.server.common.data.audit.ActionType;
+import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
+import org.thingsboard.server.common.data.exception.ThingsboardException;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.UUIDBased;
+import org.thingsboard.server.common.data.rpc.RpcRequest;
+import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
+import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
+import org.thingsboard.server.extensions.api.exception.ToErrorResponseEntity;
+import org.thingsboard.server.extensions.api.plugins.PluginConstants;
+import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
+import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
+import org.thingsboard.server.service.rpc.DeviceRpcService;
+import org.thingsboard.server.service.rpc.LocalRequestMetaData;
+import org.thingsboard.server.service.security.AccessValidator;
+import org.thingsboard.server.service.security.model.SecurityUser;
+
+import javax.annotation.Nullable;
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.io.IOException;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+
+/**
+ * Created by ashvayka on 22.03.18.
+ */
+@RestController
+@RequestMapping(PluginConstants.RPC_URL_PREFIX)
+@Slf4j
+public class RpcController extends BaseController {
+
+    public static final int DEFAULT_TIMEOUT = 10000;
+    protected final ObjectMapper jsonMapper = new ObjectMapper();
+
+    @Autowired
+    private DeviceRpcService deviceRpcService;
+
+    @Autowired
+    private AccessValidator accessValidator;
+
+    private ExecutorService executor;
+
+    @PostConstruct
+    public void initExecutor() {
+        executor = Executors.newSingleThreadExecutor();
+    }
+
+    @PreDestroy
+    public void shutdownExecutor() {
+        if (executor != null) {
+            executor.shutdownNow();
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
+    @RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST)
+    @ResponseBody
+    public DeferredResult<ResponseEntity> handleOneWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
+        return handleDeviceRPCRequest(true, new DeviceId(UUID.fromString(deviceIdStr)), requestBody);
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
+    @RequestMapping(value = "/twoway/{deviceId}", method = RequestMethod.POST)
+    @ResponseBody
+    public DeferredResult<ResponseEntity> handleTwoWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
+        return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody);
+    }
+
+
+    private DeferredResult<ResponseEntity> handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody) throws ThingsboardException {
+        try {
+            JsonNode rpcRequestBody = jsonMapper.readTree(requestBody);
+            RpcRequest cmd = new RpcRequest(rpcRequestBody.get("method").asText(),
+                    jsonMapper.writeValueAsString(rpcRequestBody.get("params")));
+
+            if (rpcRequestBody.has("timeout")) {
+                cmd.setTimeout(rpcRequestBody.get("timeout").asLong());
+            }
+            SecurityUser currentUser = getCurrentUser();
+            TenantId tenantId = currentUser.getTenantId();
+            final DeferredResult<ResponseEntity> response = new DeferredResult<>();
+            long timeout = System.currentTimeMillis() + (cmd.getTimeout() != null ? cmd.getTimeout() : DEFAULT_TIMEOUT);
+            ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData());
+            accessValidator.validate(currentUser, deviceId, new HttpValidationCallback(response, new FutureCallback<DeferredResult<ResponseEntity>>() {
+                @Override
+                public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) {
+                    ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(UUID.randomUUID(),
+                            tenantId,
+                            deviceId,
+                            oneWay,
+                            timeout,
+                            body
+                    );
+                    deviceRpcService.process(rpcRequest, new Consumer<FromDeviceRpcResponse>(){
+
+                        @Override
+                        public void accept(FromDeviceRpcResponse fromDeviceRpcResponse) {
+                            reply(new LocalRequestMetaData(rpcRequest, currentUser, result), fromDeviceRpcResponse);
+                        }
+                    });
+                }
+
+                @Override
+                public void onFailure(Throwable e) {
+                    ResponseEntity entity;
+                    if (e instanceof ToErrorResponseEntity) {
+                        entity = ((ToErrorResponseEntity) e).toErrorResponseEntity();
+                    } else {
+                        entity = new ResponseEntity(HttpStatus.UNAUTHORIZED);
+                    }
+                    logRpcCall(currentUser, deviceId, body, oneWay, Optional.empty(), e);
+                    response.setResult(entity);
+                }
+            }));
+            return response;
+        } catch (IOException ioe) {
+            throw new ThingsboardException("Invalid request body", ioe, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
+        }
+    }
+
+    public void reply(LocalRequestMetaData rpcRequest, FromDeviceRpcResponse response) {
+        Optional<RpcError> rpcError = response.getError();
+        DeferredResult<ResponseEntity> responseWriter = rpcRequest.getResponseWriter();
+        if (rpcError.isPresent()) {
+            logRpcCall(rpcRequest, rpcError, null);
+            RpcError error = rpcError.get();
+            switch (error) {
+                case TIMEOUT:
+                    responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT));
+                    break;
+                case NO_ACTIVE_CONNECTION:
+                    responseWriter.setResult(new ResponseEntity<>(HttpStatus.CONFLICT));
+                    break;
+                default:
+                    responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT));
+                    break;
+            }
+        } else {
+            Optional<String> responseData = response.getResponse();
+            if (responseData.isPresent() && !StringUtils.isEmpty(responseData.get())) {
+                String data = responseData.get();
+                try {
+                    logRpcCall(rpcRequest, rpcError, null);
+                    responseWriter.setResult(new ResponseEntity<>(jsonMapper.readTree(data), HttpStatus.OK));
+                } catch (IOException e) {
+                    log.debug("Failed to decode device response: {}", data, e);
+                    logRpcCall(rpcRequest, rpcError, e);
+                    responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE));
+                }
+            } else {
+                logRpcCall(rpcRequest, rpcError, null);
+                responseWriter.setResult(new ResponseEntity<>(HttpStatus.OK));
+            }
+        }
+    }
+
+    private void logRpcCall(LocalRequestMetaData rpcRequest, Optional<RpcError> rpcError, Throwable e) {
+        logRpcCall(rpcRequest.getUser(), rpcRequest.getRequest().getDeviceId(), rpcRequest.getRequest().getBody(), rpcRequest.getRequest().isOneway(), rpcError, null);
+    }
+
+
+    private void logRpcCall(SecurityUser user, EntityId entityId, ToDeviceRpcRequestBody body, boolean oneWay, Optional<RpcError> rpcError, Throwable e) {
+        String rpcErrorStr = "";
+        if (rpcError.isPresent()) {
+            rpcErrorStr = "RPC Error: " + rpcError.get().name();
+        }
+        String method = body.getMethod();
+        String params = body.getParams();
+
+        auditLogService.logEntityAction(
+                user.getTenantId(),
+                user.getCustomerId(),
+                user.getId(),
+                user.getName(),
+                (UUIDBased & EntityId) entityId,
+                null,
+                ActionType.RPC_CALL,
+                BaseController.toException(e),
+                rpcErrorStr,
+                oneWay,
+                method,
+                params);
+    }
+
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
index 48c9cd5..05e7836 100644
--- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
@@ -237,7 +237,7 @@ public class RuleChainController extends BaseController {
             ScriptEngine engine = null;
             try {
                 engine = new NashornJsEngine(script, functionName, argNames);
-                TbMsg inMsg = new TbMsg(UUIDs.timeBased(), msgType, null, new TbMsgMetaData(metadata), data);
+                TbMsg inMsg = new TbMsg(UUIDs.timeBased(), msgType, null, new TbMsgMetaData(metadata), data, null, null, 0L);
                 switch (scriptType) {
                     case "update":
                         output = msgToOutput(engine.executeUpdate(inMsg));
diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java
index bf49074..1a7c116 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java
@@ -15,21 +15,34 @@
  */
 package org.thingsboard.server.controller;
 
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
 import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.exception.ThingsboardException;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.page.TextPageData;
 import org.thingsboard.server.common.data.page.TextPageLink;
 import org.thingsboard.server.dao.tenant.TenantService;
-import org.thingsboard.server.common.data.exception.ThingsboardException;
+import org.thingsboard.server.service.install.InstallScripts;
 
 @RestController
 @RequestMapping("/api")
+@Slf4j
 public class TenantController extends BaseController {
-    
+
+    @Autowired
+    private InstallScripts installScripts;
+
     @Autowired
     private TenantService tenantService;
 
@@ -49,10 +62,15 @@ public class TenantController extends BaseController {
 
     @PreAuthorize("hasAuthority('SYS_ADMIN')")
     @RequestMapping(value = "/tenant", method = RequestMethod.POST)
-    @ResponseBody 
+    @ResponseBody
     public Tenant saveTenant(@RequestBody Tenant tenant) throws ThingsboardException {
         try {
-            return checkNotNull(tenantService.saveTenant(tenant));
+            boolean newTenant = tenant.getId() == null;
+            tenant = checkNotNull(tenantService.saveTenant(tenant));
+            if (newTenant) {
+                installScripts.createDefaultRuleChains(tenant.getId());
+            }
+            return tenant;
         } catch (Exception e) {
             throw handleException(e);
         }
@@ -72,7 +90,7 @@ public class TenantController extends BaseController {
     }
 
     @PreAuthorize("hasAuthority('SYS_ADMIN')")
-    @RequestMapping(value = "/tenants", params = { "limit" }, method = RequestMethod.GET)
+    @RequestMapping(value = "/tenants", params = {"limit"}, method = RequestMethod.GET)
     @ResponseBody
     public TextPageData<Tenant> getTenants(@RequestParam int limit,
                                            @RequestParam(required = false) String textSearch,
@@ -85,5 +103,5 @@ public class TenantController extends BaseController {
             throw handleException(e);
         }
     }
-    
+
 }
diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
index 1835a93..0b23a89 100644
--- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
+++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
@@ -23,9 +23,7 @@ import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Profile;
 import org.springframework.stereotype.Service;
 import org.thingsboard.server.service.component.ComponentDiscoveryService;
-import org.thingsboard.server.service.install.DatabaseSchemaService;
-import org.thingsboard.server.service.install.DatabaseUpgradeService;
-import org.thingsboard.server.service.install.SystemDataLoaderService;
+import org.thingsboard.server.service.install.*;
 
 import java.nio.file.Paths;
 
@@ -40,9 +38,6 @@ public class ThingsboardInstallService {
     @Value("${install.upgrade.from_version:1.2.3}")
     private String upgradeFromVersion;
 
-    @Value("${install.data_dir}")
-    private String dataDir;
-
     @Value("${install.load_demo:false}")
     private Boolean loadDemo;
 
@@ -61,6 +56,9 @@ public class ThingsboardInstallService {
     @Autowired
     private SystemDataLoaderService systemDataLoaderService;
 
+    @Autowired
+    private DataUpdateService dataUpdateService;
+
     public void performInstall() {
         try {
             if (isUpgrade) {
@@ -87,6 +85,8 @@ public class ThingsboardInstallService {
 
                         databaseUpgradeService.upgradeDatabase("1.4.0");
 
+                        dataUpdateService.updateData("1.4.0");
+
                         log.info("Updating system data...");
 
                         systemDataLoaderService.deleteSystemWidgetBundle("charts");
@@ -113,13 +113,6 @@ public class ThingsboardInstallService {
 
                 log.info("Starting ThingsBoard Installation...");
 
-                if (this.dataDir == null) {
-                    throw new RuntimeException("'install.data_dir' property should specified!");
-                }
-                if (!Paths.get(this.dataDir).toFile().isDirectory()) {
-                    throw new RuntimeException("'install.data_dir' property value is not a valid directory!");
-                }
-
                 log.info("Installing DataBase schema...");
 
                 databaseSchemaService.createDatabaseSchema();
@@ -131,8 +124,8 @@ public class ThingsboardInstallService {
                 systemDataLoaderService.createSysAdmin();
                 systemDataLoaderService.createAdminSettings();
                 systemDataLoaderService.loadSystemWidgets();
-                systemDataLoaderService.loadSystemPlugins();
-                systemDataLoaderService.loadSystemRules();
+//                systemDataLoaderService.loadSystemPlugins();
+//                systemDataLoaderService.loadSystemRules();
 
                 if (loadDemo) {
                     log.info("Loading demo data...");
diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterGrpcService.java b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterGrpcService.java
index 5a6b307..27334c6 100644
--- a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterGrpcService.java
+++ b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterGrpcService.java
@@ -19,7 +19,6 @@ import com.google.protobuf.ByteString;
 import io.grpc.Server;
 import io.grpc.ServerBuilder;
 import io.grpc.stub.StreamObserver;
-import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -27,29 +26,24 @@ import org.springframework.util.SerializationUtils;
 import org.thingsboard.server.actors.rpc.RpcBroadcastMsg;
 import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg;
 import org.thingsboard.server.actors.rpc.RpcSessionTellMsg;
-import org.thingsboard.server.actors.service.ActorService;
 import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.msg.cluster.ServerAddress;
 import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
 import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
 import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
-import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
-import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
+import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
 import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
 import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
 import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
 import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc;
-import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
 import org.thingsboard.server.service.cluster.discovery.ServerInstance;
 import org.thingsboard.server.service.cluster.discovery.ServerInstanceService;
-import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
+import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
 
-import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
 import java.io.IOException;
-import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -124,7 +118,7 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
     }
 
     @Override
-    public void tell(ServerAddress serverAddress, ToDeviceActorMsg toForward) {
+    public void tell(ServerAddress serverAddress, DeviceToDeviceActorMsg toForward) {
         ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder()
                 .setToDeviceActorRpcMsg(toProtoMsg(toForward)).build();
         tell(serverAddress, msg);
@@ -138,7 +132,7 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
     }
 
     @Override
-    public void tell(ServerAddress serverAddress, ToDeviceRpcRequestPluginMsg toForward) {
+    public void tell(ServerAddress serverAddress, ToDeviceRpcRequestActorMsg toForward) {
         ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder()
                 .setToDeviceRpcRequestRpcMsg(toProtoMsg(toForward)).build();
         tell(serverAddress, msg);
@@ -190,7 +184,7 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
         }
     }
 
-    private static ClusterAPIProtos.ToDeviceActorRpcMessage toProtoMsg(ToDeviceActorMsg msg) {
+    private static ClusterAPIProtos.ToDeviceActorRpcMessage toProtoMsg(DeviceToDeviceActorMsg msg) {
         return ClusterAPIProtos.ToDeviceActorRpcMessage.newBuilder().setData(
                 ByteString.copyFrom(SerializationUtils.serialize(msg))
         ).build();
@@ -202,15 +196,10 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
         ).build();
     }
 
-    private static ClusterAPIProtos.ToDeviceRpcRequestRpcMessage toProtoMsg(ToDeviceRpcRequestPluginMsg msg) {
+    private static ClusterAPIProtos.ToDeviceRpcRequestRpcMessage toProtoMsg(ToDeviceRpcRequestActorMsg msg) {
         ClusterAPIProtos.ToDeviceRpcRequestRpcMessage.Builder builder = ClusterAPIProtos.ToDeviceRpcRequestRpcMessage.newBuilder();
         ToDeviceRpcRequest request = msg.getMsg();
 
-        builder.setAddress(ClusterAPIProtos.PluginAddress.newBuilder()
-                .setTenantId(toUid(msg.getPluginTenantId().getId()))
-                .setPluginId(toUid(msg.getPluginId().getId()))
-                .build());
-
         builder.setDeviceTenantId(toUid(msg.getTenantId()));
         builder.setDeviceId(toUid(msg.getDeviceId()));
 
@@ -227,11 +216,6 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
         ClusterAPIProtos.ToPluginRpcResponseRpcMessage.Builder builder = ClusterAPIProtos.ToPluginRpcResponseRpcMessage.newBuilder();
         FromDeviceRpcResponse request = msg.getResponse();
 
-        builder.setAddress(ClusterAPIProtos.PluginAddress.newBuilder()
-                .setTenantId(toUid(msg.getPluginTenantId().getId()))
-                .setPluginId(toUid(msg.getPluginId().getId()))
-                .build());
-
         builder.setMsgId(toUid(request.getId()));
         request.getResponse().ifPresent(builder::setResponse);
         request.getError().ifPresent(e -> builder.setError(e.name()));
diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterRpcService.java b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterRpcService.java
index 8c50bb7..6aefe46 100644
--- a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterRpcService.java
+++ b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterRpcService.java
@@ -19,12 +19,12 @@ import io.grpc.stub.StreamObserver;
 import org.thingsboard.server.common.msg.cluster.ServerAddress;
 import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
 import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
-import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
 import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
 import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
 import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
+import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
 
 import java.util.UUID;
 
@@ -35,13 +35,13 @@ public interface ClusterRpcService {
 
     void init(RpcMsgListener listener);
 
-    void tell(ServerAddress serverAddress, ToDeviceActorMsg toForward);
+    void tell(ServerAddress serverAddress, DeviceToDeviceActorMsg toForward);
 
     void tell(ServerAddress serverAddress, ToDeviceSessionActorMsg toForward);
 
     void tell(ServerAddress serverAddress, ToDeviceActorNotificationMsg toForward);
 
-    void tell(ServerAddress serverAddress, ToDeviceRpcRequestPluginMsg toForward);
+    void tell(ServerAddress serverAddress, ToDeviceRpcRequestActorMsg toForward);
 
     void tell(ServerAddress serverAddress, ToPluginRpcResponseDeviceMsg toForward);
 
@@ -50,4 +50,5 @@ public interface ClusterRpcService {
     void broadcast(ToAllNodesMsg msg);
 
     void onSessionCreated(UUID msgUid, StreamObserver<ClusterAPIProtos.ToRpcServerMessage> inputStream);
+
 }
diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcMsgListener.java b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcMsgListener.java
index a5c3151..5d26fae 100644
--- a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcMsgListener.java
+++ b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcMsgListener.java
@@ -20,18 +20,16 @@ import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg;
 import org.thingsboard.server.actors.rpc.RpcSessionTellMsg;
 import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
 import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
 import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
-import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
-import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
 
 /**
  * @author Andrew Shvayka
  */
 public interface RpcMsgListener {
 
-    void onMsg(ToDeviceActorMsg msg);
+    void onMsg(DeviceToDeviceActorMsg msg);
 
     void onMsg(ToDeviceActorNotificationMsg msg);
 
diff --git a/application/src/main/java/org/thingsboard/server/service/executors/ExternalCallExecutorService.java b/application/src/main/java/org/thingsboard/server/service/executors/ExternalCallExecutorService.java
new file mode 100644
index 0000000..3f6d87e
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/executors/ExternalCallExecutorService.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.executors;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ExternalCallExecutorService extends AbstractListeningExecutor {
+
+    @Value("${actors.rule.external_call_thread_pool_size}")
+    private int externalCallExecutorThreadPoolSize;
+
+    @Override
+    protected int getThreadPollSize() {
+        return externalCallExecutorThreadPoolSize;
+    }
+
+}
+
diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseSchemaService.java
index 5bdc0a7..8478021 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseSchemaService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseSchemaService.java
@@ -37,17 +37,16 @@ public class CassandraDatabaseSchemaService implements DatabaseSchemaService {
     private static final String CASSANDRA_DIR = "cassandra";
     private static final String SCHEMA_CQL = "schema.cql";
 
-    @Value("${install.data_dir}")
-    private String dataDir;
-
     @Autowired
     private CassandraInstallCluster cluster;
 
+    @Autowired
+    private InstallScripts installScripts;
+
     @Override
     public void createDatabaseSchema() throws Exception {
         log.info("Installing Cassandra DataBase schema...");
-
-        Path schemaFile = Paths.get(this.dataDir, CASSANDRA_DIR, SCHEMA_CQL);
+        Path schemaFile = Paths.get(installScripts.getDataDir(), CASSANDRA_DIR, SCHEMA_CQL);
         loadCql(schemaFile);
 
     }
diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
index 8dd9698..e889126 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
@@ -43,9 +43,6 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
 
     private static final String SCHEMA_UPDATE_CQL = "schema_update.cql";
 
-    @Value("${install.data_dir}")
-    private String dataDir;
-
     @Autowired
     private CassandraCluster cluster;
 
@@ -55,6 +52,9 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
     @Autowired
     private DashboardService dashboardService;
 
+    @Autowired
+    private InstallScripts installScripts;
+
     @Override
     public void upgradeDatabase(String fromVersion) throws Exception {
 
@@ -91,7 +91,7 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
                 log.info("Relations dumped.");
 
                 log.info("Updating schema ...");
-                Path schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.3.0", SCHEMA_UPDATE_CQL);
+                Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.3.0", SCHEMA_UPDATE_CQL);
                 loadCql(schemaUpdateFile);
                 log.info("Schema updated.");
 
@@ -173,7 +173,7 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
 
 
                 log.info("Updating schema ...");
-                schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_CQL);
+                schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.4.0", SCHEMA_UPDATE_CQL);
                 loadCql(schemaUpdateFile);
                 log.info("Schema updated.");
 
@@ -189,7 +189,7 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
             case "1.4.0":
 
                 log.info("Updating schema ...");
-                schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.5.0", SCHEMA_UPDATE_CQL);
+                schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.5.0", SCHEMA_UPDATE_CQL);
                 loadCql(schemaUpdateFile);
                 log.info("Schema updated.");
 
diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultDataUpdateService.java
new file mode 100644
index 0000000..37e5b30
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultDataUpdateService.java
@@ -0,0 +1,106 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.install;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.IdBased;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.rule.RuleChain;
+import org.thingsboard.server.dao.rule.RuleChainService;
+import org.thingsboard.server.dao.tenant.TenantService;
+
+import java.util.List;
+import java.util.UUID;
+
+@Service
+@Profile("install")
+@Slf4j
+public class DefaultDataUpdateService implements DataUpdateService {
+
+    @Autowired
+    private TenantService tenantService;
+
+    @Autowired
+    private RuleChainService ruleChainService;
+
+    @Autowired
+    private InstallScripts installScripts;
+
+    @Override
+    public void updateData(String fromVersion) throws Exception {
+        switch (fromVersion) {
+            case "1.4.0":
+                log.info("Updating data from version 1.4.0 to 1.5.0 ...");
+                tenantsDefaultRuleChainUpdater.updateEntities(null);
+                break;
+            default:
+                throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
+        }
+    }
+
+    private PaginatedUpdater<String, Tenant> tenantsDefaultRuleChainUpdater =
+            new PaginatedUpdater<String, Tenant>() {
+
+                @Override
+                protected List<Tenant> findEntities(String region, TextPageLink pageLink) {
+                    return tenantService.findTenants(pageLink).getData();
+                }
+
+                @Override
+                protected void updateEntity(Tenant tenant) {
+                    try {
+                        RuleChain ruleChain = ruleChainService.getRootTenantRuleChain(tenant.getId());
+                        if (ruleChain == null) {
+                            installScripts.createDefaultRuleChains(tenant.getId());
+                        }
+                    } catch (Exception e) {
+                        log.error("Unable to update Tenant", e);
+                    }
+                }
+            };
+
+    public abstract class PaginatedUpdater<I, D extends IdBased<?>> {
+
+        private static final int DEFAULT_LIMIT = 100;
+
+        public void updateEntities(I id) {
+            TextPageLink pageLink = new TextPageLink(DEFAULT_LIMIT);
+            boolean hasNext = true;
+            while (hasNext) {
+                List<D> entities = findEntities(id, pageLink);
+                for (D entity : entities) {
+                    updateEntity(entity);
+                }
+                hasNext = entities.size() == pageLink.getLimit();
+                if (hasNext) {
+                    int index = entities.size() - 1;
+                    UUID idOffset = entities.get(index).getUuidId();
+                    pageLink.setIdOffset(idOffset);
+                }
+            }
+        }
+
+        protected abstract List<D> findEntities(I id, TextPageLink pageLink);
+
+        protected abstract void updateEntity(D entity);
+
+    }
+
+}
\ No newline at end of file
diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
index 908faf2..e33c2e3 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
@@ -60,21 +60,12 @@ import java.nio.file.Paths;
 @Slf4j
 public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
 
-    private static final String JSON_DIR = "json";
-    private static final String SYSTEM_DIR = "system";
-    private static final String DEMO_DIR = "demo";
-    private static final String WIDGET_BUNDLES_DIR = "widget_bundles";
-    private static final String PLUGINS_DIR = "plugins";
-    private static final String RULES_DIR = "rules";
-    private static final String DASHBOARDS_DIR = "dashboards";
-
     private static final ObjectMapper objectMapper = new ObjectMapper();
-    public static final String JSON_EXT = ".json";
     public static final String CUSTOMER_CRED = "customer";
     public static final String DEFAULT_DEVICE_TYPE = "default";
 
-    @Value("${install.data_dir}")
-    private String dataDir;
+    @Autowired
+    private InstallScripts installScripts;
 
     @Autowired
     private BCryptPasswordEncoder passwordEncoder;
@@ -89,15 +80,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
     private WidgetsBundleService widgetsBundleService;
 
     @Autowired
-    private WidgetTypeService widgetTypeService;
-
-    @Autowired
-    private PluginService pluginService;
-
-    @Autowired
-    private RuleService ruleService;
-
-    @Autowired
     private TenantService tenantService;
 
     @Autowired
@@ -109,9 +91,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
     @Autowired
     private DeviceCredentialsService deviceCredentialsService;
 
-    @Autowired
-    private DashboardService dashboardService;
-
     @Bean
     protected BCryptPasswordEncoder passwordEncoder() {
         return new BCryptPasswordEncoder();
@@ -147,55 +126,12 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
     }
 
     @Override
-    public void loadSystemWidgets() throws Exception {
-        Path widgetBundlesDir = Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR);
-        try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) {
-            dirStream.forEach(
-                    path -> {
-                        try {
-                            JsonNode widgetsBundleDescriptorJson = objectMapper.readTree(path.toFile());
-                            JsonNode widgetsBundleJson = widgetsBundleDescriptorJson.get("widgetsBundle");
-                            WidgetsBundle widgetsBundle = objectMapper.treeToValue(widgetsBundleJson, WidgetsBundle.class);
-                            WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
-                            JsonNode widgetTypesArrayJson = widgetsBundleDescriptorJson.get("widgetTypes");
-                            widgetTypesArrayJson.forEach(
-                                    widgetTypeJson -> {
-                                        try {
-                                            WidgetType widgetType = objectMapper.treeToValue(widgetTypeJson, WidgetType.class);
-                                            widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
-                                            widgetTypeService.saveWidgetType(widgetType);
-                                        } catch (Exception e) {
-                                            log.error("Unable to load widget type from json: [{}]", path.toString());
-                                            throw new RuntimeException("Unable to load widget type from json", e);
-                                        }
-                                    }
-                            );
-                        } catch (Exception e) {
-                            log.error("Unable to load widgets bundle from json: [{}]", path.toString());
-                            throw new RuntimeException("Unable to load widgets bundle from json", e);
-                        }
-                    }
-            );
-        }
-    }
-
-    @Override
-    public void loadSystemPlugins() throws Exception {
-        loadPlugins(Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, PLUGINS_DIR), null);
-    }
-
-
-    @Override
-    public void loadSystemRules() throws Exception {
-//        loadRules(Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, RULES_DIR), null);
-    }
-
-    @Override
     public void loadDemoData() throws Exception {
         Tenant demoTenant = new Tenant();
         demoTenant.setRegion("Global");
         demoTenant.setTitle("Tenant");
         demoTenant = tenantService.saveTenant(demoTenant);
+        installScripts.createDefaultRuleChains(demoTenant.getId());
         createUser(Authority.TENANT_ADMIN, demoTenant.getId(), null, "tenant@thingsboard.org", "tenant");
 
         Customer customerA = new Customer();
@@ -227,9 +163,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
         createDevice(demoTenant.getId(), null, DEFAULT_DEVICE_TYPE, "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " +
                 "Raspberry Pi GPIO control sample application");
 
-        loadPlugins(Paths.get(dataDir, JSON_DIR, DEMO_DIR, PLUGINS_DIR), demoTenant.getId());
-//        loadRules(Paths.get(dataDir, JSON_DIR, DEMO_DIR, RULES_DIR), demoTenant.getId());
-        loadDashboards(Paths.get(dataDir, JSON_DIR, DEMO_DIR, DASHBOARDS_DIR), demoTenant.getId(), null);
+        installScripts.loadDashboards(demoTenant.getId(), null);
     }
 
     @Override
@@ -240,6 +174,11 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
         }
     }
 
+    @Override
+    public void loadSystemWidgets() throws Exception {
+        installScripts.loadSystemWidgets();
+    }
+
     private User createUser(Authority authority,
                             TenantId tenantId,
                             CustomerId customerId,
@@ -282,72 +221,4 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
         return device;
     }
 
-    private void loadPlugins(Path pluginsDir, TenantId tenantId) throws Exception{
-        try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(pluginsDir, path -> path.toString().endsWith(JSON_EXT))) {
-            dirStream.forEach(
-                    path -> {
-                        try {
-                            JsonNode pluginJson = objectMapper.readTree(path.toFile());
-                            PluginMetaData plugin = objectMapper.treeToValue(pluginJson, PluginMetaData.class);
-                            plugin.setTenantId(tenantId);
-                            if (plugin.getState() == ComponentLifecycleState.ACTIVE) {
-                                plugin.setState(ComponentLifecycleState.SUSPENDED);
-                                PluginMetaData savedPlugin = pluginService.savePlugin(plugin);
-                                pluginService.activatePluginById(savedPlugin.getId());
-                            } else {
-                                pluginService.savePlugin(plugin);
-                            }
-                        } catch (Exception e) {
-                            log.error("Unable to load plugin from json: [{}]", path.toString());
-                            throw new RuntimeException("Unable to load plugin from json", e);
-                        }
-                    }
-            );
-        }
-    }
-
-    private void loadRules(Path rulesDir, TenantId tenantId) throws Exception {
-        try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(rulesDir, path -> path.toString().endsWith(JSON_EXT))) {
-            dirStream.forEach(
-                    path -> {
-                        try {
-                            JsonNode ruleJson = objectMapper.readTree(path.toFile());
-                            RuleMetaData rule = objectMapper.treeToValue(ruleJson, RuleMetaData.class);
-                            rule.setTenantId(tenantId);
-                            if (rule.getState() == ComponentLifecycleState.ACTIVE) {
-                                rule.setState(ComponentLifecycleState.SUSPENDED);
-                                RuleMetaData savedRule = ruleService.saveRule(rule);
-                                ruleService.activateRuleById(savedRule.getId());
-                            } else {
-                                ruleService.saveRule(rule);
-                            }
-                        } catch (Exception e) {
-                            log.error("Unable to load rule from json: [{}]", path.toString());
-                            throw new RuntimeException("Unable to load rule from json", e);
-                        }
-                    }
-            );
-        }
-    }
-
-    private void loadDashboards(Path dashboardsDir, TenantId tenantId, CustomerId customerId) throws Exception {
-        try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) {
-            dirStream.forEach(
-                    path -> {
-                        try {
-                            JsonNode dashboardJson = objectMapper.readTree(path.toFile());
-                            Dashboard dashboard = objectMapper.treeToValue(dashboardJson, Dashboard.class);
-                            dashboard.setTenantId(tenantId);
-                            Dashboard savedDashboard = dashboardService.saveDashboard(dashboard);
-                            if (customerId != null && !customerId.isNullUid()) {
-                                dashboardService.assignDashboardToCustomer(savedDashboard.getId(), customerId);
-                            }
-                        } catch (Exception e) {
-                            log.error("Unable to load dashboard from json: [{}]", path.toString());
-                            throw new RuntimeException("Unable to load dashboard from json", e);
-                        }
-                    }
-            );
-        }
-    }
 }
diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
new file mode 100644
index 0000000..de43966
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
@@ -0,0 +1,185 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.install;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.thingsboard.server.common.data.Dashboard;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.rule.RuleChain;
+import org.thingsboard.server.common.data.rule.RuleChainMetaData;
+import org.thingsboard.server.common.data.widget.WidgetType;
+import org.thingsboard.server.common.data.widget.WidgetsBundle;
+import org.thingsboard.server.dao.dashboard.DashboardService;
+import org.thingsboard.server.dao.rule.RuleChainService;
+import org.thingsboard.server.dao.widget.WidgetTypeService;
+import org.thingsboard.server.dao.widget.WidgetsBundleService;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper;
+
+/**
+ * Created by ashvayka on 18.04.18.
+ */
+@Component
+@Slf4j
+public class InstallScripts {
+
+    public static final String APP_DIR = "application";
+    public static final String SRC_DIR = "src";
+    public static final String MAIN_DIR = "main";
+    public static final String DATA_DIR = "data";
+    public static final String JSON_DIR = "json";
+    public static final String SYSTEM_DIR = "system";
+    public static final String TENANT_DIR = "tenant";
+    public static final String DEMO_DIR = "demo";
+    public static final String RULE_CHAINS_DIR = "rule_chains";
+    public static final String WIDGET_BUNDLES_DIR = "widget_bundles";
+    public static final String DASHBOARDS_DIR = "dashboards";
+
+    public static final String JSON_EXT = ".json";
+
+    @Value("${install.data_dir:}")
+    private String dataDir;
+
+    @Autowired
+    private RuleChainService ruleChainService;
+
+    @Autowired
+    private DashboardService dashboardService;
+
+    @Autowired
+    private WidgetTypeService widgetTypeService;
+
+    @Autowired
+    private WidgetsBundleService widgetsBundleService;
+
+    public Path getTenantRuleChainsDir() {
+        return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR);
+    }
+
+    public String getDataDir() {
+        if (!StringUtils.isEmpty(dataDir)) {
+            if (!Paths.get(this.dataDir).toFile().isDirectory()) {
+                throw new RuntimeException("'install.data_dir' property value is not a valid directory!");
+            }
+            return dataDir;
+        } else {
+            String workDir = System.getProperty("user.dir");
+            if (workDir.endsWith("application")) {
+                return Paths.get(workDir, SRC_DIR, MAIN_DIR, DATA_DIR).toString();
+            } else {
+                Path dataDirPath = Paths.get(workDir, APP_DIR, SRC_DIR, MAIN_DIR, DATA_DIR);
+                if (Files.exists(dataDirPath)) {
+                    return dataDirPath.toString();
+                } else {
+                    throw new RuntimeException("Not valid working directory: " + workDir + ". Please use either root project directory, application module directory or specify valid \"install.data_dir\" ENV variable to avoid automatic data directory lookup!");
+                }
+            }
+        }
+    }
+
+    public void createDefaultRuleChains(TenantId tenantId) throws IOException {
+        Path tenantChainsDir = getTenantRuleChainsDir();
+        try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(tenantChainsDir, path -> path.toString().endsWith(InstallScripts.JSON_EXT))) {
+            dirStream.forEach(
+                    path -> {
+                        try {
+                            JsonNode ruleChainJson = objectMapper.readTree(path.toFile());
+                            RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class);
+                            RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class);
+
+                            ruleChain.setTenantId(tenantId);
+                            ruleChain = ruleChainService.saveRuleChain(ruleChain);
+
+                            ruleChainMetaData.setRuleChainId(ruleChain.getId());
+                            ruleChainService.saveRuleChainMetaData(ruleChainMetaData);
+                        } catch (Exception e) {
+                            log.error("Unable to load rule chain from json: [{}]", path.toString());
+                            throw new RuntimeException("Unable to load rule chain from json", e);
+                        }
+                    }
+            );
+        }
+    }
+
+    public void loadSystemWidgets() throws Exception {
+        Path widgetBundlesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR);
+        try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) {
+            dirStream.forEach(
+                    path -> {
+                        try {
+                            JsonNode widgetsBundleDescriptorJson = objectMapper.readTree(path.toFile());
+                            JsonNode widgetsBundleJson = widgetsBundleDescriptorJson.get("widgetsBundle");
+                            WidgetsBundle widgetsBundle = objectMapper.treeToValue(widgetsBundleJson, WidgetsBundle.class);
+                            WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+                            JsonNode widgetTypesArrayJson = widgetsBundleDescriptorJson.get("widgetTypes");
+                            widgetTypesArrayJson.forEach(
+                                    widgetTypeJson -> {
+                                        try {
+                                            WidgetType widgetType = objectMapper.treeToValue(widgetTypeJson, WidgetType.class);
+                                            widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
+                                            widgetTypeService.saveWidgetType(widgetType);
+                                        } catch (Exception e) {
+                                            log.error("Unable to load widget type from json: [{}]", path.toString());
+                                            throw new RuntimeException("Unable to load widget type from json", e);
+                                        }
+                                    }
+                            );
+                        } catch (Exception e) {
+                            log.error("Unable to load widgets bundle from json: [{}]", path.toString());
+                            throw new RuntimeException("Unable to load widgets bundle from json", e);
+                        }
+                    }
+            );
+        }
+    }
+
+    public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception {
+        Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR);
+        try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) {
+            dirStream.forEach(
+                    path -> {
+                        try {
+                            JsonNode dashboardJson = objectMapper.readTree(path.toFile());
+                            Dashboard dashboard = objectMapper.treeToValue(dashboardJson, Dashboard.class);
+                            dashboard.setTenantId(tenantId);
+                            Dashboard savedDashboard = dashboardService.saveDashboard(dashboard);
+                            if (customerId != null && !customerId.isNullUid()) {
+                                dashboardService.assignDashboardToCustomer(savedDashboard.getId(), customerId);
+                            }
+                        } catch (Exception e) {
+                            log.error("Unable to load dashboard from json: [{}]", path.toString());
+                            throw new RuntimeException("Unable to load dashboard from json", e);
+                        }
+                    }
+            );
+        }
+    }
+
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseSchemaService.java
index 443ec0c..535e0f9 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseSchemaService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseSchemaService.java
@@ -16,6 +16,7 @@
 package org.thingsboard.server.service.install;
 
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Profile;
 import org.springframework.stereotype.Service;
@@ -38,9 +39,6 @@ public class SqlDatabaseSchemaService implements DatabaseSchemaService {
     private static final String SQL_DIR = "sql";
     private static final String SCHEMA_SQL = "schema.sql";
 
-    @Value("${install.data_dir}")
-    private String dataDir;
-
     @Value("${spring.datasource.url}")
     private String dbUrl;
 
@@ -50,12 +48,15 @@ public class SqlDatabaseSchemaService implements DatabaseSchemaService {
     @Value("${spring.datasource.password}")
     private String dbPassword;
 
+    @Autowired
+    private InstallScripts installScripts;
+
     @Override
     public void createDatabaseSchema() throws Exception {
 
         log.info("Installing SQL DataBase schema...");
 
-        Path schemaFile = Paths.get(this.dataDir, SQL_DIR, SCHEMA_SQL);
+        Path schemaFile = Paths.get(installScripts.getDataDir(), SQL_DIR, SCHEMA_SQL);
         try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
             String sql = new String(Files.readAllBytes(schemaFile), Charset.forName("UTF-8"));
             conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to load initial thingsboard database schema
diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
index 412ea8b..39db831 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
@@ -44,9 +44,6 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
 
     private static final String SCHEMA_UPDATE_SQL = "schema_update.sql";
 
-    @Value("${install.data_dir}")
-    private String dataDir;
-
     @Value("${spring.datasource.url}")
     private String dbUrl;
 
@@ -59,12 +56,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
     @Autowired
     private DashboardService dashboardService;
 
+    @Autowired
+    private InstallScripts installScripts;
+
     @Override
     public void upgradeDatabase(String fromVersion) throws Exception {
         switch (fromVersion) {
             case "1.3.0":
                 log.info("Updating schema ...");
-                Path schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.3.1", SCHEMA_UPDATE_SQL);
+                Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.3.1", SCHEMA_UPDATE_SQL);
                 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
                     String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8"));
                     conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
@@ -82,7 +82,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
                     log.info("Dashboards dumped.");
 
                     log.info("Updating schema ...");
-                    schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_SQL);
+                    schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.4.0", SCHEMA_UPDATE_SQL);
                     String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8"));
                     conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
                     log.info("Schema updated.");
@@ -100,7 +100,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
             case "1.4.0":
                 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
                     log.info("Updating schema ...");
-                    schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.5.0", SCHEMA_UPDATE_SQL);
+                    schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.5.0", SCHEMA_UPDATE_SQL);
                     String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8"));
                     conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
                     log.info("Schema updated.");
diff --git a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java
index a3dcb68..f3a6af4 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java
@@ -23,10 +23,6 @@ public interface SystemDataLoaderService {
 
     void loadSystemWidgets() throws Exception;
 
-    void loadSystemPlugins() throws Exception;
-
-    void loadSystemRules() throws Exception;
-
     void loadDemoData() throws Exception;
 
     void deleteSystemWidgetBundle(String bundleAlias) throws Exception;
diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java
new file mode 100644
index 0000000..9674490
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java
@@ -0,0 +1,157 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.rpc;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+import org.springframework.web.context.request.async.DeferredResult;
+import org.thingsboard.server.actors.service.ActorService;
+import org.thingsboard.server.common.data.audit.ActionType;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.UUIDBased;
+import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
+import org.thingsboard.server.common.msg.cluster.ServerAddress;
+import org.thingsboard.server.common.msg.core.ToServerRpcResponseMsg;
+import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
+import org.thingsboard.server.controller.BaseController;
+import org.thingsboard.server.dao.audit.AuditLogService;
+import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
+import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
+import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
+import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
+import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
+import org.thingsboard.server.service.security.model.SecurityUser;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.io.IOException;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/**
+ * Created by ashvayka on 27.03.18.
+ */
+@Service
+@Slf4j
+public class DefaultDeviceRpcService implements DeviceRpcService {
+
+    @Autowired
+    private ClusterRoutingService routingService;
+
+    @Autowired
+    private ClusterRpcService rpcService;
+
+    @Autowired
+    private ActorService actorService;
+
+    @Autowired
+    private AuditLogService auditLogService;
+
+    private ScheduledExecutorService rpcCallBackExecutor;
+
+    private final ConcurrentMap<UUID, Consumer<FromDeviceRpcResponse>> localRpcRequests = new ConcurrentHashMap<>();
+
+
+    @PostConstruct
+    public void initExecutor() {
+        rpcCallBackExecutor = Executors.newSingleThreadScheduledExecutor();
+    }
+
+    @PreDestroy
+    public void shutdownExecutor() {
+        if (rpcCallBackExecutor != null) {
+            rpcCallBackExecutor.shutdownNow();
+        }
+    }
+
+    @Override
+    public void process(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer) {
+        log.trace("[{}] Processing local rpc call for device [{}]", request.getTenantId(), request.getDeviceId());
+        sendRpcRequest(request);
+        UUID requestId = request.getId();
+        localRpcRequests.put(requestId, responseConsumer);
+        long timeout = Math.max(0, request.getExpirationTime() - System.currentTimeMillis());
+        log.error("[{}] processing the request: [{}]", this.hashCode(), requestId);
+        rpcCallBackExecutor.schedule(() -> {
+            log.error("[{}] timeout the request: [{}]", this.hashCode(), requestId);
+            Consumer<FromDeviceRpcResponse> consumer = localRpcRequests.remove(requestId);
+            if (consumer != null) {
+                consumer.accept(new FromDeviceRpcResponse(requestId, null, RpcError.TIMEOUT));
+            }
+        }, timeout, TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    public void process(ToDeviceRpcRequest request, ServerAddress originator) {
+//        if (pluginServerAddress.isPresent()) {
+//            systemContext.getRpcService().tell(pluginServerAddress.get(), responsePluginMsg);
+//            logger.debug("[{}] Rpc command response sent to remote plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId());
+//        } else {
+//            context.parent().tell(responsePluginMsg, ActorRef.noSender());
+//            logger.debug("[{}] Rpc command response sent to local plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId());
+//        }
+    }
+
+    @Override
+    public void process(FromDeviceRpcResponse response) {
+        log.error("[{}] response the request: [{}]", this.hashCode(), response.getId());
+        //TODO: send to another server if needed.
+        UUID requestId = response.getId();
+        Consumer<FromDeviceRpcResponse> consumer = localRpcRequests.remove(requestId);
+        if (consumer != null) {
+            consumer.accept(response);
+        } else {
+            log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response);
+        }
+    }
+
+    @Override
+    public void sendRpcReplyToDevice(TenantId tenantId, DeviceId deviceId, int requestId, String body) {
+        ToServerRpcResponseActorMsg rpcMsg = new ToServerRpcResponseActorMsg(tenantId, deviceId, new ToServerRpcResponseMsg(requestId, body));
+        forward(deviceId, rpcMsg, rpcService::tell);
+    }
+
+    private void sendRpcRequest(ToDeviceRpcRequest msg) {
+        log.trace("[{}] Forwarding msg {} to device actor!", msg.getDeviceId(), msg);
+        ToDeviceRpcRequestActorMsg rpcMsg = new ToDeviceRpcRequestActorMsg(msg);
+        forward(msg.getDeviceId(), rpcMsg, rpcService::tell);
+    }
+
+    private <T extends ToDeviceActorNotificationMsg> void forward(DeviceId deviceId, T msg, BiConsumer<ServerAddress, T> rpcFunction) {
+        Optional<ServerAddress> instance = routingService.resolveById(deviceId);
+        if (instance.isPresent()) {
+            log.trace("[{}] Forwarding msg {} to remote device actor!", msg.getTenantId(), msg);
+            rpcFunction.accept(instance.get(), msg);
+        } else {
+            log.trace("[{}] Forwarding msg {} to local device actor!", msg.getTenantId(), msg);
+            actorService.onMsg(msg);
+        }
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DeviceRpcService.java
new file mode 100644
index 0000000..4299120
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/rpc/DeviceRpcService.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.rpc;
+
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.cluster.ServerAddress;
+import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
+import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
+
+import java.util.function.Consumer;
+
+/**
+ * Created by ashvayka on 16.04.18.
+ */
+public interface DeviceRpcService {
+
+    void process(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer);
+
+    void process(ToDeviceRpcRequest request, ServerAddress originator);
+
+    void process(FromDeviceRpcResponse response);
+
+    void sendRpcReplyToDevice(TenantId tenantId, DeviceId deviceId, int requestId, String body);
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/ToServerRpcResponseActorMsg.java b/application/src/main/java/org/thingsboard/server/service/rpc/ToServerRpcResponseActorMsg.java
new file mode 100644
index 0000000..f3183ec
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/rpc/ToServerRpcResponseActorMsg.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.rpc;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.ToString;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.MsgType;
+import org.thingsboard.server.common.msg.cluster.ServerAddress;
+import org.thingsboard.server.common.msg.core.ToServerRpcResponseMsg;
+import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
+
+import java.util.Optional;
+
+/**
+ * Created by ashvayka on 16.04.18.
+ */
+@ToString
+@RequiredArgsConstructor
+public class ToServerRpcResponseActorMsg implements ToDeviceActorNotificationMsg {
+
+    private final ServerAddress serverAddress;
+
+    @Getter
+    private final TenantId tenantId;
+
+    @Getter
+    private final DeviceId deviceId;
+
+    @Getter
+    private final ToServerRpcResponseMsg msg;
+
+    public ToServerRpcResponseActorMsg(TenantId tenantId, DeviceId deviceId, ToServerRpcResponseMsg msg) {
+        this(null, tenantId, deviceId, msg);
+    }
+
+    public Optional<ServerAddress> getServerAddress() {
+        return Optional.ofNullable(serverAddress);
+    }
+
+    @Override
+    public MsgType getMsgType() {
+        return MsgType.SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG;
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/script/NashornJsEngine.java b/application/src/main/java/org/thingsboard/server/service/script/NashornJsEngine.java
index d68f6fe..8e689ec 100644
--- a/application/src/main/java/org/thingsboard/server/service/script/NashornJsEngine.java
+++ b/application/src/main/java/org/thingsboard/server/service/script/NashornJsEngine.java
@@ -118,7 +118,7 @@ public class NashornJsEngine implements org.thingsboard.rule.engine.api.ScriptEn
             String newData = data != null ? data : msg.getData();
             TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData();
             String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType();
-            return new TbMsg(msg.getId(), newMessageType, msg.getOriginator(), newMetadata, newData);
+            return new TbMsg(msg.getId(), newMessageType, msg.getOriginator(), newMetadata, newData, msg.getRuleChainId(), msg.getRuleNodeId(), msg.getClusterPartition());
         } catch (Throwable th) {
             th.printStackTrace();
             throw new RuntimeException("Failed to unbind message data from javascript result", th);
diff --git a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java
index c1f3688..8487279 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java
@@ -25,6 +25,7 @@ import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Component;
 import org.springframework.web.context.request.async.DeferredResult;
 import org.thingsboard.server.actors.plugin.ValidationResult;
+import org.thingsboard.server.common.data.BaseData;
 import org.thingsboard.server.common.data.Customer;
 import org.thingsboard.server.common.data.Device;
 import org.thingsboard.server.common.data.Tenant;
@@ -35,8 +36,10 @@ import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.id.EntityIdFactory;
 import org.thingsboard.server.common.data.id.RuleChainId;
+import org.thingsboard.server.common.data.id.RuleNodeId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.rule.RuleChain;
+import org.thingsboard.server.common.data.rule.RuleNode;
 import org.thingsboard.server.controller.HttpValidationCallback;
 import org.thingsboard.server.dao.alarm.AlarmService;
 import org.thingsboard.server.dao.asset.AssetService;
@@ -140,7 +143,7 @@ public class AccessValidator {
         return response;
     }
 
-    public <T> void validate(SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
+    public void validate(SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
         switch (entityId.getEntityType()) {
             case DEVICE:
                 validateDevice(currentUser, entityId, callback);
@@ -177,14 +180,14 @@ public class AccessValidator {
                     } else if (currentUser.isCustomerUser() && !device.getCustomerId().equals(currentUser.getCustomerId())) {
                         return ValidationResult.accessDenied("Device doesn't belong to the current Customer!");
                     } else {
-                        return ValidationResult.ok();
+                        return ValidationResult.ok(device);
                     }
                 }
             }), executor);
         }
     }
 
-    private <T> void validateAsset(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
+    private void validateAsset(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
         if (currentUser.isSystemAdmin()) {
             callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
         } else {
@@ -198,15 +201,14 @@ public class AccessValidator {
                     } else if (currentUser.isCustomerUser() && !asset.getCustomerId().equals(currentUser.getCustomerId())) {
                         return ValidationResult.accessDenied("Asset doesn't belong to the current Customer!");
                     } else {
-                        return ValidationResult.ok();
+                        return ValidationResult.ok(asset);
                     }
                 }
             }), executor);
         }
     }
 
-
-    private <T> void validateRuleChain(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
+    private void validateRuleChain(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
         if (currentUser.isCustomerUser()) {
             callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
         } else {
@@ -220,14 +222,40 @@ public class AccessValidator {
                     } else if (currentUser.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) {
                         return ValidationResult.accessDenied("Rule chain is not in system scope!");
                     } else {
-                        return ValidationResult.ok();
+                        return ValidationResult.ok(ruleChain);
+                    }
+                }
+            }), executor);
+        }
+    }
+
+    private void validateRule(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
+        if (currentUser.isCustomerUser()) {
+            callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
+        } else {
+            ListenableFuture<RuleNode> ruleNodeFuture = ruleChainService.findRuleNodeByIdAsync(new RuleNodeId(entityId.getId()));
+            Futures.addCallback(ruleNodeFuture, getCallback(callback, ruleNodeTmp -> {
+                RuleNode ruleNode = ruleNodeTmp;
+                if (ruleNode == null) {
+                    return ValidationResult.entityNotFound("Rule node with requested id wasn't found!");
+                } else if (ruleNode.getRuleChainId() == null) {
+                    return ValidationResult.entityNotFound("Rule chain with requested node id wasn't found!");
+                } else {
+                    //TODO: make async
+                    RuleChain ruleChain = ruleChainService.findRuleChainById(ruleNode.getRuleChainId());
+                    if (currentUser.isTenantAdmin() && !ruleChain.getTenantId().equals(currentUser.getTenantId())) {
+                        return ValidationResult.accessDenied("Rule chain doesn't belong to the current Tenant!");
+                    } else if (currentUser.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) {
+                        return ValidationResult.accessDenied("Rule chain is not in system scope!");
+                    } else {
+                        return ValidationResult.ok(ruleNode);
                     }
                 }
             }), executor);
         }
     }
 
-    private <T> void validateCustomer(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
+    private void validateCustomer(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
         if (currentUser.isSystemAdmin()) {
             callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
         } else {
@@ -241,18 +269,18 @@ public class AccessValidator {
                     } else if (currentUser.isCustomerUser() && !customer.getId().equals(currentUser.getCustomerId())) {
                         return ValidationResult.accessDenied("Customer doesn't relate to the currently authorized customer user!");
                     } else {
-                        return ValidationResult.ok();
+                        return ValidationResult.ok(customer);
                     }
                 }
             }), executor);
         }
     }
 
-    private <T> void validateTenant(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
+    private void validateTenant(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
         if (currentUser.isCustomerUser()) {
             callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
         } else if (currentUser.isSystemAdmin()) {
-            callback.onSuccess(ValidationResult.ok());
+            callback.onSuccess(ValidationResult.ok(null));
         } else {
             ListenableFuture<Tenant> tenantFuture = tenantService.findTenantByIdAsync(new TenantId(entityId.getId()));
             Futures.addCallback(tenantFuture, getCallback(callback, tenant -> {
@@ -261,13 +289,13 @@ public class AccessValidator {
                 } else if (!tenant.getId().equals(currentUser.getTenantId())) {
                     return ValidationResult.accessDenied("Tenant doesn't relate to the currently authorized user!");
                 } else {
-                    return ValidationResult.ok();
+                    return ValidationResult.ok(tenant);
                 }
             }), executor);
         }
     }
 
-    private <T> FutureCallback<T> getCallback(FutureCallback<ValidationResult> callback, Function<T, ValidationResult> transformer) {
+    private <T, V> FutureCallback<T> getCallback(FutureCallback<ValidationResult> callback, Function<T, ValidationResult<V>> transformer) {
         return new FutureCallback<T>() {
             @Override
             public void onSuccess(@Nullable T result) {
diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
new file mode 100644
index 0000000..fc17ccd
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
@@ -0,0 +1,390 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.state;
+
+import com.datastax.driver.core.utils.UUIDs;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningScheduledExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.actors.service.ActorService;
+import org.thingsboard.server.common.data.DataConstants;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.kv.AttributeKvEntry;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.TbMsgDataType;
+import org.thingsboard.server.common.msg.TbMsgMetaData;
+import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
+import org.thingsboard.server.dao.attributes.AttributesService;
+import org.thingsboard.server.dao.device.DeviceService;
+import org.thingsboard.server.dao.tenant.TenantService;
+import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
+
+import javax.annotation.Nullable;
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import static org.thingsboard.server.common.data.DataConstants.ACTIVITY_EVENT;
+import static org.thingsboard.server.common.data.DataConstants.CONNECT_EVENT;
+import static org.thingsboard.server.common.data.DataConstants.DISCONNECT_EVENT;
+import static org.thingsboard.server.common.data.DataConstants.INACTIVITY_EVENT;
+
+/**
+ * Created by ashvayka on 01.05.18.
+ */
+@Service
+@Slf4j
+//TODO: refactor to use page links as cursor and not fetch all
+public class DefaultDeviceStateService implements DeviceStateService {
+
+    private static final ObjectMapper json = new ObjectMapper();
+    public static final String ACTIVITY_STATE = "active";
+    public static final String LAST_CONNECT_TIME = "lastConnectTime";
+    public static final String LAST_DISCONNECT_TIME = "lastDisconnectTime";
+    public static final String LAST_ACTIVITY_TIME = "lastActivityTime";
+    public static final String INACTIVITY_ALARM_TIME = "inactivityAlarmTime";
+    public static final String INACTIVITY_TIMEOUT = "inactivityTimeout";
+
+    public static final List<String> PERSISTENT_ATTRIBUTES = Arrays.asList(ACTIVITY_STATE, LAST_CONNECT_TIME, LAST_DISCONNECT_TIME, LAST_ACTIVITY_TIME, INACTIVITY_ALARM_TIME, INACTIVITY_TIMEOUT);
+
+    @Autowired
+    private TenantService tenantService;
+
+    @Autowired
+    private DeviceService deviceService;
+
+    @Autowired
+    private AttributesService attributesService;
+
+    @Autowired
+    private ActorService actorService;
+
+    @Autowired
+    private TelemetrySubscriptionService tsSubService;
+
+    @Value("${state.defaultInactivityTimeoutInSec}")
+    @Getter
+    private long defaultInactivityTimeoutInSec;
+
+    @Value("${state.defaultStateCheckIntervalInSec}")
+    @Getter
+    private long defaultStateCheckIntervalInSec;
+
+// TODO in v2.1
+//    @Value("${state.defaultStatePersistenceIntervalInSec}")
+//    @Getter
+//    private long defaultStatePersistenceIntervalInSec;
+//
+//    @Value("${state.defaultStatePersistencePack}")
+//    @Getter
+//    private long defaultStatePersistencePack;
+
+    private ListeningScheduledExecutorService queueExecutor;
+
+    private ConcurrentMap<TenantId, Set<DeviceId>> tenantDevices = new ConcurrentHashMap<>();
+    private ConcurrentMap<DeviceId, DeviceStateData> deviceStates = new ConcurrentHashMap<>();
+
+    @PostConstruct
+    public void init() {
+        // Should be always single threaded due to absence of locks.
+        queueExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor());
+        queueExecutor.submit(this::initStateFromDB);
+        queueExecutor.scheduleAtFixedRate(this::updateState, defaultStateCheckIntervalInSec, defaultStateCheckIntervalInSec, TimeUnit.SECONDS);
+        //TODO: schedule persistence in v2.1;
+    }
+
+    @PreDestroy
+    public void stop() {
+        if (queueExecutor != null) {
+            queueExecutor.shutdownNow();
+        }
+    }
+
+    @Override
+    public void onDeviceAdded(Device device) {
+        queueExecutor.submit(() -> onDeviceAddedSync(device));
+    }
+
+    @Override
+    public void onDeviceUpdated(Device device) {
+        queueExecutor.submit(() -> onDeviceUpdatedSync(device));
+    }
+
+    @Override
+    public void onDeviceConnect(DeviceId deviceId) {
+        queueExecutor.submit(() -> onDeviceConnectSync(deviceId));
+    }
+
+    @Override
+    public void onDeviceActivity(DeviceId deviceId) {
+        queueExecutor.submit(() -> onDeviceActivitySync(deviceId));
+    }
+
+    @Override
+    public void onDeviceDisconnect(DeviceId deviceId) {
+        queueExecutor.submit(() -> onDeviceDisconnectSync(deviceId));
+    }
+
+    @Override
+    public void onDeviceDeleted(Device device) {
+        queueExecutor.submit(() -> onDeviceDeleted(device.getTenantId(), device.getId()));
+    }
+
+    @Override
+    public void onDeviceInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout) {
+        queueExecutor.submit(() -> onInactivityTimeoutUpdate(deviceId, inactivityTimeout));
+    }
+
+    @Override
+    public Optional<DeviceState> getDeviceState(DeviceId deviceId) {
+        DeviceStateData state = deviceStates.get(deviceId);
+        if (state != null) {
+            return Optional.of(state.getState());
+        } else {
+            return Optional.empty();
+        }
+    }
+
+    private void initStateFromDB() {
+        List<Tenant> tenants = tenantService.findTenants(new TextPageLink(Integer.MAX_VALUE)).getData();
+        for (Tenant tenant : tenants) {
+            List<ListenableFuture<DeviceStateData>> fetchFutures = new ArrayList<>();
+            List<Device> devices = deviceService.findDevicesByTenantId(tenant.getId(), new TextPageLink(Integer.MAX_VALUE)).getData();
+            for (Device device : devices) {
+                fetchFutures.add(fetchDeviceState(device));
+            }
+            try {
+                Futures.successfulAsList(fetchFutures).get().forEach(this::addDeviceUsingState);
+            } catch (InterruptedException | ExecutionException e) {
+                log.warn("Failed to init device state service from DB", e);
+            }
+        }
+    }
+
+    private void addDeviceUsingState(DeviceStateData state) {
+        tenantDevices.computeIfAbsent(state.getTenantId(), id -> ConcurrentHashMap.newKeySet()).add(state.getDeviceId());
+        deviceStates.put(state.getDeviceId(), state);
+    }
+
+    private void updateState() {
+        long ts = System.currentTimeMillis();
+        Set<DeviceId> deviceIds = new HashSet<>(deviceStates.keySet());
+        for (DeviceId deviceId : deviceIds) {
+            DeviceStateData stateData = deviceStates.get(deviceId);
+            DeviceState state = stateData.getState();
+            state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout());
+            if (!state.isActive() && state.getLastInactivityAlarmTime() < state.getLastActivityTime()) {
+                state.setLastInactivityAlarmTime(ts);
+                pushRuleEngineMessage(stateData, INACTIVITY_EVENT);
+                saveAttribute(deviceId, INACTIVITY_ALARM_TIME, ts);
+                saveAttribute(deviceId, ACTIVITY_STATE, state.isActive());
+            }
+        }
+    }
+
+    private void onDeviceConnectSync(DeviceId deviceId) {
+        DeviceStateData stateData = deviceStates.get(deviceId);
+        if (stateData != null) {
+            long ts = System.currentTimeMillis();
+            stateData.getState().setLastConnectTime(ts);
+            pushRuleEngineMessage(stateData, CONNECT_EVENT);
+            saveAttribute(deviceId, LAST_CONNECT_TIME, ts);
+        }
+    }
+
+    private void onDeviceDisconnectSync(DeviceId deviceId) {
+        DeviceStateData stateData = deviceStates.get(deviceId);
+        if (stateData != null) {
+            long ts = System.currentTimeMillis();
+            stateData.getState().setLastDisconnectTime(ts);
+            pushRuleEngineMessage(stateData, DISCONNECT_EVENT);
+            saveAttribute(deviceId, LAST_DISCONNECT_TIME, ts);
+        }
+    }
+
+    private void onDeviceActivitySync(DeviceId deviceId) {
+        DeviceStateData stateData = deviceStates.get(deviceId);
+        if (stateData != null) {
+            DeviceState state = stateData.getState();
+            long ts = System.currentTimeMillis();
+            state.setActive(true);
+            stateData.getState().setLastActivityTime(ts);
+            pushRuleEngineMessage(stateData, ACTIVITY_EVENT);
+            saveAttribute(deviceId, LAST_ACTIVITY_TIME, ts);
+            saveAttribute(deviceId, ACTIVITY_STATE, state.isActive());
+        }
+    }
+
+    private void onInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout) {
+        if (inactivityTimeout == 0L) {
+            return;
+        }
+        DeviceStateData stateData = deviceStates.get(deviceId);
+        if (stateData != null) {
+            long ts = System.currentTimeMillis();
+            DeviceState state = stateData.getState();
+            state.setInactivityTimeout(inactivityTimeout);
+            boolean oldActive = state.isActive();
+            state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout());
+            if (!oldActive && state.isActive()) {
+                saveAttribute(deviceId, ACTIVITY_STATE, state.isActive());
+            }
+        }
+    }
+
+    private void onDeviceAddedSync(Device device) {
+        Futures.addCallback(fetchDeviceState(device), new FutureCallback<DeviceStateData>() {
+            @Override
+            public void onSuccess(@Nullable DeviceStateData state) {
+                addDeviceUsingState(state);
+            }
+
+            @Override
+            public void onFailure(Throwable t) {
+                log.warn("Failed to register device to the state service", t);
+            }
+        });
+    }
+
+    private void onDeviceUpdatedSync(Device device) {
+        DeviceStateData stateData = deviceStates.get(device.getId());
+        if (stateData != null) {
+            TbMsgMetaData md = new TbMsgMetaData();
+            md.putValue("deviceName", device.getName());
+            md.putValue("deviceType", device.getType());
+            stateData.setMetaData(md);
+        }
+    }
+
+    private void onDeviceDeleted(TenantId tenantId, DeviceId deviceId) {
+        deviceStates.remove(deviceId);
+        Set<DeviceId> deviceIds = tenantDevices.get(tenantId);
+        if (deviceIds != null) {
+            deviceIds.remove(deviceId);
+            if (deviceIds.isEmpty()) {
+                tenantDevices.remove(tenantId);
+            }
+        }
+    }
+
+    private ListenableFuture<DeviceStateData> fetchDeviceState(Device device) {
+        ListenableFuture<List<AttributeKvEntry>> attributes = attributesService.find(device.getId(), DataConstants.SERVER_SCOPE, PERSISTENT_ATTRIBUTES);
+        return Futures.transform(attributes, new Function<List<AttributeKvEntry>, DeviceStateData>() {
+            @Nullable
+            @Override
+            public DeviceStateData apply(@Nullable List<AttributeKvEntry> attributes) {
+                long lastActivityTime = getAttributeValue(attributes, LAST_ACTIVITY_TIME, 0L);
+                long inactivityAlarmTime = getAttributeValue(attributes, INACTIVITY_ALARM_TIME, 0L);
+                long inactivityTimeout = getAttributeValue(attributes, INACTIVITY_TIMEOUT, TimeUnit.SECONDS.toMillis(defaultInactivityTimeoutInSec));
+                boolean active = System.currentTimeMillis() < lastActivityTime + inactivityTimeout;
+                DeviceState deviceState = DeviceState.builder()
+                        .active(active)
+                        .lastConnectTime(getAttributeValue(attributes, LAST_CONNECT_TIME, 0L))
+                        .lastDisconnectTime(getAttributeValue(attributes, LAST_DISCONNECT_TIME, 0L))
+                        .lastActivityTime(lastActivityTime)
+                        .lastInactivityAlarmTime(inactivityAlarmTime)
+                        .inactivityTimeout(inactivityTimeout)
+                        .build();
+                TbMsgMetaData md = new TbMsgMetaData();
+                md.putValue("deviceName", device.getName());
+                md.putValue("deviceType", device.getType());
+                return DeviceStateData.builder()
+                        .tenantId(device.getTenantId())
+                        .deviceId(device.getId())
+                        .metaData(md)
+                        .state(deviceState).build();
+            }
+        });
+    }
+
+    private long getLastPersistTime(List<AttributeKvEntry> attributes) {
+        return attributes.stream().map(AttributeKvEntry::getLastUpdateTs).max(Long::compare).orElse(0L);
+    }
+
+    private long getAttributeValue(List<AttributeKvEntry> attributes, String attributeName, long defaultValue) {
+        for (AttributeKvEntry attribute : attributes) {
+            if (attribute.getKey().equals(attributeName)) {
+                return attribute.getLongValue().orElse(defaultValue);
+            }
+        }
+        return defaultValue;
+    }
+
+    private void pushRuleEngineMessage(DeviceStateData stateData, String msgType) {
+        DeviceState state = stateData.getState();
+        try {
+            TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), msgType, stateData.getDeviceId(), stateData.getMetaData(), TbMsgDataType.JSON
+                    , json.writeValueAsString(state)
+                    , null, null, 0L);
+            actorService.onMsg(new ServiceToRuleEngineMsg(stateData.getTenantId(), tbMsg));
+        } catch (Exception e) {
+            log.warn("[{}] Failed to push inactivity alarm: {}", stateData.getDeviceId(), state, e);
+        }
+    }
+
+    private void saveAttribute(DeviceId deviceId, String key, long value) {
+        tsSubService.saveAttrAndNotify(deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value));
+    }
+
+    private void saveAttribute(DeviceId deviceId, String key, boolean value) {
+        tsSubService.saveAttrAndNotify(deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value));
+    }
+
+    private class AttributeSaveCallback implements FutureCallback<Void> {
+        private final DeviceId deviceId;
+        private final String key;
+        private final Object value;
+
+        AttributeSaveCallback(DeviceId deviceId, String key, Object value) {
+            this.deviceId = deviceId;
+            this.key = key;
+            this.value = value;
+        }
+
+        @Override
+        public void onSuccess(@Nullable Void result) {
+            log.trace("[{}] Successfully updated attribute [{}] with value [{}]", deviceId, key, value);
+        }
+
+        @Override
+        public void onFailure(Throwable t) {
+            log.warn("[{}] Failed to update attribute [{}] with value [{}]", deviceId, key, value, t);
+        }
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/state/DeviceState.java b/application/src/main/java/org/thingsboard/server/service/state/DeviceState.java
new file mode 100644
index 0000000..29167ee
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/state/DeviceState.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.state;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * Created by ashvayka on 01.05.18.
+ */
+@Data
+@Builder
+public class DeviceState {
+
+    private boolean active;
+    private long lastConnectTime;
+    private long lastActivityTime;
+    private long lastDisconnectTime;
+    private long lastInactivityAlarmTime;
+    private long inactivityTimeout;
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.java b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.java
new file mode 100644
index 0000000..cb9eeea
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.state;
+
+import lombok.Builder;
+import lombok.Data;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.TbMsgMetaData;
+
+/**
+ * Created by ashvayka on 01.05.18.
+ */
+@Data
+@Builder
+class DeviceStateData {
+
+    private final TenantId tenantId;
+    private final DeviceId deviceId;
+
+    private TbMsgMetaData metaData;
+    private final DeviceState state;
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java
new file mode 100644
index 0000000..37f785c
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.state;
+
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.id.DeviceId;
+
+import java.util.Optional;
+
+/**
+ * Created by ashvayka on 01.05.18.
+ */
+public interface DeviceStateService {
+
+    void onDeviceAdded(Device device);
+
+    void onDeviceUpdated(Device device);
+
+    void onDeviceDeleted(Device device);
+
+    void onDeviceConnect(DeviceId deviceId);
+
+    void onDeviceActivity(DeviceId deviceId);
+
+    void onDeviceDisconnect(DeviceId deviceId);
+
+    void onDeviceInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout);
+
+    Optional<DeviceState> getDeviceState(DeviceId deviceId);
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
index 58bbec5..00a337a 100644
--- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
+++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
@@ -20,11 +20,20 @@ import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
+import org.thingsboard.server.common.data.DataConstants;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
+import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
+import org.thingsboard.server.common.data.kv.BooleanDataEntry;
+import org.thingsboard.server.common.data.kv.DoubleDataEntry;
+import org.thingsboard.server.common.data.kv.LongDataEntry;
+import org.thingsboard.server.common.data.kv.StringDataEntry;
 import org.thingsboard.server.common.data.kv.TsKvEntry;
 import org.thingsboard.server.common.msg.cluster.ServerAddress;
 import org.thingsboard.server.dao.attributes.AttributesService;
@@ -34,11 +43,14 @@ import org.thingsboard.server.extensions.core.plugin.telemetry.sub.Subscription;
 import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionState;
 import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionUpdate;
 import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
+import org.thingsboard.server.service.state.DefaultDeviceStateService;
+import org.thingsboard.server.service.state.DeviceStateService;
 
 import javax.annotation.Nullable;
 import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -70,6 +82,10 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
     @Autowired
     private ClusterRoutingService routingService;
 
+    @Autowired
+    @Lazy
+    private DeviceStateService stateService;
+
     private ExecutorService tsCallBackExecutor;
     private ExecutorService wsCallBackExecutor;
 
@@ -149,10 +165,41 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
         addWsCallback(saveFuture, success -> onAttributesUpdate(entityId, scope, attributes));
     }
 
+    @Override
+    public void saveAttrAndNotify(EntityId entityId, String scope, String key, long value, FutureCallback<Void> callback) {
+        saveAndNotify(entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(key, value)
+                , System.currentTimeMillis())), callback);
+    }
+
+    @Override
+    public void saveAttrAndNotify(EntityId entityId, String scope, String key, String value, FutureCallback<Void> callback) {
+        saveAndNotify(entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(key, value)
+                , System.currentTimeMillis())), callback);
+    }
+
+    @Override
+    public void saveAttrAndNotify(EntityId entityId, String scope, String key, double value, FutureCallback<Void> callback) {
+        saveAndNotify(entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new DoubleDataEntry(key, value)
+                , System.currentTimeMillis())), callback);
+    }
+
+    @Override
+    public void saveAttrAndNotify(EntityId entityId, String scope, String key, boolean value, FutureCallback<Void> callback) {
+        saveAndNotify(entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new BooleanDataEntry(key, value)
+                , System.currentTimeMillis())), callback);
+    }
+
     private void onAttributesUpdate(EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
         Optional<ServerAddress> serverAddress = routingService.resolveById(entityId);
         if (!serverAddress.isPresent()) {
             onLocalAttributesUpdate(entityId, scope, attributes);
+            if (entityId.getEntityType() == EntityType.DEVICE && DataConstants.SERVER_SCOPE.equalsIgnoreCase(scope)) {
+                for (AttributeKvEntry attribute : attributes) {
+                    if (attribute.getKey().equals(DefaultDeviceStateService.INACTIVITY_TIMEOUT)) {
+                        stateService.onDeviceInactivityTimeoutUpdate(new DeviceId(entityId.getId()), attribute.getLongValue().orElse(0L));
+                    }
+                }
+            }
         } else {
 //            rpcHandler.onAttributesUpdate(ctx, serverAddress.get(), entityId, entries);
         }
diff --git a/application/src/main/proto/cluster.proto b/application/src/main/proto/cluster.proto
index ba72097..fc92487 100644
--- a/application/src/main/proto/cluster.proto
+++ b/application/src/main/proto/cluster.proto
@@ -61,7 +61,6 @@ message ConnectRpcMessage {
 }
 
 message ToDeviceRpcRequestRpcMessage {
-  PluginAddress address = 1;
   Uid deviceTenantId = 2;
   Uid deviceId = 3;
 
@@ -73,8 +72,6 @@ message ToDeviceRpcRequestRpcMessage {
 }
 
 message ToPluginRpcResponseRpcMessage {
-  PluginAddress address = 1;
-
   Uid msgId = 2;
   string response = 3;
   string error = 4;
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index a9e47de..af5b8d4 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -207,24 +207,15 @@ actors:
     sync:
       # Default timeout for processing request using synchronous session (HTTP, CoAP) in milliseconds
       timeout: "${ACTORS_SESSION_SYNC_TIMEOUT:10000}"
-  plugin:
-    # Default timeout for termination of the plugin actor after it is stopped
-    termination.delay: "${ACTORS_PLUGIN_TERMINATION_DELAY:60000}"
-    # Default timeout for processing of particular message by particular plugin
-    processing.timeout: "${ACTORS_PLUGIN_TIMEOUT:60000}"
-    # Errors for particular actor are persisted once per specified amount of milliseconds
-    error_persist_frequency: "${ACTORS_PLUGIN_ERROR_FREQUENCY:3000}"
   rule:
-    # Default timeout for termination of the rule actor after it is stopped
-    termination.delay: "${ACTORS_RULE_TERMINATION_DELAY:30000}"
-    # Errors for particular actor are persisted once per specified amount of milliseconds
-    error_persist_frequency: "${ACTORS_RULE_ERROR_FREQUENCY:3000}"
     # Specify thread pool size for database request callbacks executor service
     db_callback_thread_pool_size: "${ACTORS_RULE_DB_CALLBACK_THREAD_POOL_SIZE:1}"
     # Specify thread pool size for javascript executor service
     js_thread_pool_size: "${ACTORS_RULE_JS_THREAD_POOL_SIZE:10}"
     # Specify thread pool size for mail sender executor service
     mail_thread_pool_size: "${ACTORS_RULE_MAIL_THREAD_POOL_SIZE:10}"
+    # Specify thread pool size for external call service
+    external_call_thread_pool_size: "${ACTORS_RULE_EXTERNAL_CALL_THREAD_POOL_SIZE:10}"
     chain:
       # Errors for particular actor are persisted once per specified amount of milliseconds
       error_persist_frequency: "${ACTORS_RULE_CHAIN_ERROR_FREQUENCY:3000}"
@@ -235,6 +226,13 @@ actors:
     # Enable/disable actor statistics
     enabled: "${ACTORS_STATISTICS_ENABLED:true}"
     persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:3600000}"
+  queue:
+    # Enable/disable persistence of un-processed messages to the queue
+    enabled: "${ACTORS_QUEUE_ENABLED:true}"
+    # Maximum allowed timeout for persistence into the queue
+    timeout: "${ACTORS_QUEUE_PERSISTENCE_TIMEOUT:30000}"
+  client_side_rpc:
+    timeout:  "${CLIENT_SIDE_RPC_TIMEOUT:60000}"
 
 cache:
   # caffeine or redis
@@ -306,7 +304,9 @@ spring:
 
 rule:
   queue:
-    msg_partitioning: "${QUEUE_MSG_PARTITIONING:HOURS}"
+    type: "memory"
+    max_size: 10000
+
 
 # PostgreSQL DAO Configuration
 #spring:
@@ -359,4 +359,11 @@ audit_log:
     host: "${AUDIT_LOG_SINK_HOST:localhost}"
     port: "${AUDIT_LOG_SINK_POST:9200}"
     user_name: "${AUDIT_LOG_SINK_USER_NAME:}"
-    password: "${AUDIT_LOG_SINK_PASSWORD:}"
\ No newline at end of file
+    password: "${AUDIT_LOG_SINK_PASSWORD:}"
+
+state:
+  defaultInactivityTimeoutInSec: 10
+  defaultStateCheckIntervalInSec: 10
+# TODO in v2.1
+#  defaultStatePersistenceIntervalInSec: 60
+#  defaultStatePersistencePack: 100
\ No newline at end of file
diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java
index 93fe767..5b895b1 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java
@@ -16,6 +16,8 @@
 package org.thingsboard.server.controller;
 
 import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.thingsboard.server.common.data.DataConstants;
 import org.thingsboard.server.common.data.Event;
 import org.thingsboard.server.common.data.id.EntityId;
@@ -25,12 +27,22 @@ import org.thingsboard.server.common.data.page.TimePageData;
 import org.thingsboard.server.common.data.page.TimePageLink;
 import org.thingsboard.server.common.data.rule.RuleChain;
 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
+import org.thingsboard.server.dao.queue.MsgQueue;
+import org.thingsboard.server.dao.rule.RuleChainService;
+
+import java.io.IOException;
 
 /**
  * Created by ashvayka on 20.03.18.
  */
 public class AbstractRuleEngineControllerTest extends AbstractControllerTest {
 
+    @Autowired
+    protected RuleChainService ruleChainService;
+
+    @Autowired
+    protected MsgQueue msgQueue;
+
     protected RuleChain saveRuleChain(RuleChain ruleChain) throws Exception {
         return doPost("/api/ruleChain", ruleChain, RuleChain.class);
     }
@@ -53,4 +65,13 @@ public class AbstractRuleEngineControllerTest extends AbstractControllerTest {
                 new TypeReference<TimePageData<Event>>() {
                 }, pageLink, entityId.getEntityType(), entityId.getId(), DataConstants.DEBUG_RULE_NODE, tenantId.getId());
     }
+
+    protected JsonNode getMetadata(Event outEvent) {
+        String metaDataStr = outEvent.getBody().get("metadata").asText();
+        try {
+            return mapper.readTree(metaDataStr);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseComponentDescriptorControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseComponentDescriptorControllerTest.java
index 4346538..3a07d29 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseComponentDescriptorControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseComponentDescriptorControllerTest.java
@@ -20,6 +20,7 @@ import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.thingsboard.rule.engine.filter.TbJsFilterNode;
 import org.thingsboard.server.common.data.Tenant;
 import org.thingsboard.server.common.data.User;
 import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
@@ -35,7 +36,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 
 public abstract class BaseComponentDescriptorControllerTest extends AbstractControllerTest {
 
-    private static final int AMOUNT_OF_DEFAULT_PLUGINS_DESCRIPTORS = 5;
+    private static final int AMOUNT_OF_DEFAULT_FILTER_NODES = 4;
     private Tenant savedTenant;
     private User tenantAdmin;
 
@@ -69,38 +70,28 @@ public abstract class BaseComponentDescriptorControllerTest extends AbstractCont
     @Test
     public void testGetByClazz() throws Exception {
         ComponentDescriptor descriptor =
-                doGet("/api/component/" + TelemetryStoragePlugin.class.getName(), ComponentDescriptor.class);
+                doGet("/api/component/" + TbJsFilterNode.class.getName(), ComponentDescriptor.class);
 
         Assert.assertNotNull(descriptor);
         Assert.assertNotNull(descriptor.getId());
         Assert.assertNotNull(descriptor.getName());
         Assert.assertEquals(ComponentScope.TENANT, descriptor.getScope());
-        Assert.assertEquals(ComponentType.PLUGIN, descriptor.getType());
+        Assert.assertEquals(ComponentType.FILTER, descriptor.getType());
         Assert.assertEquals(descriptor.getClazz(), descriptor.getClazz());
     }
 
     @Test
     public void testGetByType() throws Exception {
         List<ComponentDescriptor> descriptors = readResponse(
-                doGet("/api/components/" + ComponentType.PLUGIN).andExpect(status().isOk()), new TypeReference<List<ComponentDescriptor>>() {
+                doGet("/api/components/" + ComponentType.FILTER).andExpect(status().isOk()), new TypeReference<List<ComponentDescriptor>>() {
                 });
 
         Assert.assertNotNull(descriptors);
-        Assert.assertEquals(AMOUNT_OF_DEFAULT_PLUGINS_DESCRIPTORS, descriptors.size());
+        Assert.assertTrue(descriptors.size() >= AMOUNT_OF_DEFAULT_FILTER_NODES);
 
         for (ComponentType type : ComponentType.values()) {
             doGet("/api/components/" + type).andExpect(status().isOk());
         }
     }
 
-    @Test
-    public void testGetActionsByType() throws Exception {
-        List<ComponentDescriptor> descriptors = readResponse(
-                doGet("/api/components/actions/" + TelemetryStoragePlugin.class.getName()).andExpect(status().isOk()), new TypeReference<List<ComponentDescriptor>>() {
-                });
-
-        Assert.assertNotNull(descriptors);
-        Assert.assertEquals(1, descriptors.size());
-        Assert.assertEquals(TelemetryPluginAction.class.getName(), descriptors.get(0).getClazz());
-    }
 }
diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java
index 7c9c058..f3d69ba 100644
--- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java
+++ b/application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java
@@ -15,7 +15,6 @@
  */
 package org.thingsboard.server.mqtt.rpc.sql;
 
-import org.thingsboard.server.dao.service.DaoNoSqlTest;
 import org.thingsboard.server.dao.service.DaoSqlTest;
 import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcIntegrationTest;
 
diff --git a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
index f88eb24..a294816 100644
--- a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
+++ b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
@@ -16,6 +16,8 @@
 package org.thingsboard.server.rules.flow;
 
 import com.datastax.driver.core.utils.UUIDs;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.collect.Lists;
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 import org.junit.After;
@@ -28,6 +30,7 @@ import org.thingsboard.server.actors.service.ActorService;
 import org.thingsboard.server.common.data.*;
 import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
 import org.thingsboard.server.common.data.kv.StringDataEntry;
+import org.thingsboard.server.common.data.page.TextPageLink;
 import org.thingsboard.server.common.data.page.TimePageData;
 import org.thingsboard.server.common.data.rule.RuleChain;
 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
@@ -40,8 +43,10 @@ import org.thingsboard.server.controller.AbstractRuleEngineControllerTest;
 import org.thingsboard.server.dao.attributes.AttributesService;
 import org.thingsboard.server.dao.rule.RuleChainService;
 
+import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
@@ -60,9 +65,6 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
     @Autowired
     protected AttributesService attributesService;
 
-    @Autowired
-    protected RuleChainService ruleChainService;
-
     @Before
     public void beforeTest() throws Exception {
         loginSysAdmin();
@@ -71,6 +73,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
         tenant.setTitle("My tenant");
         savedTenant = doPost("/api/tenant", tenant, Tenant.class);
         Assert.assertNotNull(savedTenant);
+        ruleChainService.deleteRuleChainsByTenantId(savedTenant.getId());
 
         tenantAdmin = new User();
         tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
@@ -149,7 +152,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
                 "CUSTOM",
                 device.getId(),
                 new TbMsgMetaData(),
-                "{}");
+                "{}", null, null, 0L);
         actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg));
 
         Thread.sleep(3000);
@@ -166,7 +169,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
         Assert.assertEquals(ruleChain.getFirstRuleNodeId(), outEvent.getEntityId());
         Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText());
 
-        Assert.assertEquals("serverAttributeValue1", outEvent.getBody().get("metadata").get("ss.serverAttributeKey1").asText());
+        Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText());
 
         RuleChain finalRuleChain = ruleChain;
         RuleNode lastRuleNode = metaData.getNodes().stream().filter(node -> !node.getId().equals(finalRuleChain.getFirstRuleNodeId())).findFirst().get();
@@ -183,8 +186,131 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
         Assert.assertEquals(lastRuleNode.getId(), outEvent.getEntityId());
         Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText());
 
-        Assert.assertEquals("serverAttributeValue1", outEvent.getBody().get("metadata").get("ss.serverAttributeKey1").asText());
-        Assert.assertEquals("serverAttributeValue2", outEvent.getBody().get("metadata").get("ss.serverAttributeKey2").asText());
+        Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText());
+        Assert.assertEquals("serverAttributeValue2", getMetadata(outEvent).get("ss_serverAttributeKey2").asText());
+
+        List<TbMsg> unAckMsgList = Lists.newArrayList(msgQueue.findUnprocessed(ruleChain.getId().getId(), 0L));
+        Assert.assertEquals(0, unAckMsgList.size());
+    }
+
+    @Test
+    public void testTwoRuleChainsWithTwoRules() throws Exception {
+        // Creating Rule Chain
+        RuleChain rootRuleChain = new RuleChain();
+        rootRuleChain.setName("Root Rule Chain");
+        rootRuleChain.setTenantId(savedTenant.getId());
+        rootRuleChain.setRoot(true);
+        rootRuleChain.setDebugMode(true);
+        rootRuleChain = saveRuleChain(rootRuleChain);
+        Assert.assertNull(rootRuleChain.getFirstRuleNodeId());
+
+        // Creating Rule Chain
+        RuleChain secondaryRuleChain = new RuleChain();
+        secondaryRuleChain.setName("Secondary Rule Chain");
+        secondaryRuleChain.setTenantId(savedTenant.getId());
+        secondaryRuleChain.setRoot(false);
+        secondaryRuleChain.setDebugMode(true);
+        secondaryRuleChain = saveRuleChain(secondaryRuleChain);
+        Assert.assertNull(secondaryRuleChain.getFirstRuleNodeId());
+
+        RuleChainMetaData rootMetaData = new RuleChainMetaData();
+        rootMetaData.setRuleChainId(rootRuleChain.getId());
+
+        RuleNode ruleNode1 = new RuleNode();
+        ruleNode1.setName("Simple Rule Node 1");
+        ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
+        ruleNode1.setDebugMode(true);
+        TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration();
+        configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
+        ruleNode1.setConfiguration(mapper.valueToTree(configuration1));
+
+        rootMetaData.setNodes(Collections.singletonList(ruleNode1));
+        rootMetaData.setFirstNodeIndex(0);
+        rootMetaData.addRuleChainConnectionInfo(0, secondaryRuleChain.getId(), "Success", mapper.createObjectNode());
+        rootMetaData = saveRuleChainMetaData(rootMetaData);
+        Assert.assertNotNull(rootMetaData);
+
+        rootRuleChain = getRuleChain(rootRuleChain.getId());
+        Assert.assertNotNull(rootRuleChain.getFirstRuleNodeId());
+
+
+        RuleChainMetaData secondaryMetaData = new RuleChainMetaData();
+        secondaryMetaData.setRuleChainId(secondaryRuleChain.getId());
+
+        RuleNode ruleNode2 = new RuleNode();
+        ruleNode2.setName("Simple Rule Node 2");
+        ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
+        ruleNode2.setDebugMode(true);
+        TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration();
+        configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
+        ruleNode2.setConfiguration(mapper.valueToTree(configuration2));
+
+        secondaryMetaData.setNodes(Collections.singletonList(ruleNode2));
+        secondaryMetaData.setFirstNodeIndex(0);
+        secondaryMetaData = saveRuleChainMetaData(secondaryMetaData);
+        Assert.assertNotNull(secondaryMetaData);
+
+        // Saving the device
+        Device device = new Device();
+        device.setName("My device");
+        device.setType("default");
+        device = doPost("/api/device", device, Device.class);
+
+        attributesService.save(device.getId(), DataConstants.SERVER_SCOPE,
+                Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey1", "serverAttributeValue1"), System.currentTimeMillis())));
+        attributesService.save(device.getId(), DataConstants.SERVER_SCOPE,
+                Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey2", "serverAttributeValue2"), System.currentTimeMillis())));
+
+
+        Thread.sleep(1000);
+
+        // Pushing Message to the system
+        TbMsg tbMsg = new TbMsg(UUIDs.timeBased(),
+                "CUSTOM",
+                device.getId(),
+                new TbMsgMetaData(),
+                "{}", null, null, 0L);
+        actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg));
+
+        Thread.sleep(3000);
+
+        TimePageData<Event> events = getDebugEvents(savedTenant.getId(), rootRuleChain.getFirstRuleNodeId(), 1000);
+
+        Assert.assertEquals(2, events.getData().size());
+
+        Event inEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get();
+        Assert.assertEquals(rootRuleChain.getFirstRuleNodeId(), inEvent.getEntityId());
+        Assert.assertEquals(device.getId().getId().toString(), inEvent.getBody().get("entityId").asText());
+
+        Event outEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.OUT)).findFirst().get();
+        Assert.assertEquals(rootRuleChain.getFirstRuleNodeId(), outEvent.getEntityId());
+        Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText());
+
+        Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText());
+
+        RuleChain finalRuleChain = rootRuleChain;
+        RuleNode lastRuleNode = secondaryMetaData.getNodes().stream().filter(node -> !node.getId().equals(finalRuleChain.getFirstRuleNodeId())).findFirst().get();
+
+        events = getDebugEvents(savedTenant.getId(), lastRuleNode.getId(), 1000);
+
+        Assert.assertEquals(2, events.getData().size());
+
+        inEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get();
+        Assert.assertEquals(lastRuleNode.getId(), inEvent.getEntityId());
+        Assert.assertEquals(device.getId().getId().toString(), inEvent.getBody().get("entityId").asText());
+
+        outEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.OUT)).findFirst().get();
+        Assert.assertEquals(lastRuleNode.getId(), outEvent.getEntityId());
+        Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText());
+
+        Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText());
+        Assert.assertEquals("serverAttributeValue2", getMetadata(outEvent).get("ss_serverAttributeKey2").asText());
+
+        List<TbMsg> unAckMsgList = Lists.newArrayList(msgQueue.findUnprocessed(rootRuleChain.getId().getId(), 0L));
+        Assert.assertEquals(0, unAckMsgList.size());
+
+        unAckMsgList = Lists.newArrayList(msgQueue.findUnprocessed(secondaryRuleChain.getId().getId(), 0L));
+        Assert.assertEquals(0, unAckMsgList.size());
     }
 
 }
diff --git a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
index 22d79f0..0ea6ff4 100644
--- a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
+++ b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
@@ -16,6 +16,7 @@
 package org.thingsboard.server.rules.lifecycle;
 
 import com.datastax.driver.core.utils.UUIDs;
+import com.fasterxml.jackson.databind.JsonNode;
 import lombok.extern.slf4j.Slf4j;
 import org.junit.After;
 import org.junit.Assert;
@@ -42,6 +43,7 @@ import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
 import org.thingsboard.server.controller.AbstractRuleEngineControllerTest;
 import org.thingsboard.server.dao.attributes.AttributesService;
 
+import java.io.IOException;
 import java.util.Collections;
 
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -69,6 +71,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
         tenant.setTitle("My tenant");
         savedTenant = doPost("/api/tenant", tenant, Tenant.class);
         Assert.assertNotNull(savedTenant);
+        ruleChainService.deleteRuleChainsByTenantId(savedTenant.getId());
 
         tenantAdmin = new User();
         tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
@@ -135,7 +138,8 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
                 "CUSTOM",
                 device.getId(),
                 new TbMsgMetaData(),
-                "{}");
+                "{}",
+                null, null, 0L);
         actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg));
 
         Thread.sleep(3000);
@@ -152,7 +156,75 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
         Assert.assertEquals(ruleChain.getFirstRuleNodeId(), outEvent.getEntityId());
         Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText());
 
-        Assert.assertEquals("serverAttributeValue", outEvent.getBody().get("metadata").get("ss.serverAttributeKey").asText());
+        Assert.assertEquals("serverAttributeValue", getMetadata(outEvent).get("ss_serverAttributeKey").asText());
+    }
+
+    @Test
+    public void testRuleChainWithOneRuleAndMsgFromQueue() throws Exception {
+        // Creating Rule Chain
+        RuleChain ruleChain = new RuleChain();
+        ruleChain.setName("Simple Rule Chain");
+        ruleChain.setTenantId(savedTenant.getId());
+        ruleChain.setRoot(true);
+        ruleChain.setDebugMode(true);
+        ruleChain = saveRuleChain(ruleChain);
+        Assert.assertNull(ruleChain.getFirstRuleNodeId());
+
+        // Saving the device
+        Device device = new Device();
+        device.setName("My device");
+        device.setType("default");
+        device = doPost("/api/device", device, Device.class);
+
+        attributesService.save(device.getId(), DataConstants.SERVER_SCOPE,
+                Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey", "serverAttributeValue"), System.currentTimeMillis())));
+
+        // Pushing Message to the system
+        TbMsg tbMsg = new TbMsg(UUIDs.timeBased(),
+                "CUSTOM",
+                device.getId(),
+                new TbMsgMetaData(),
+                "{}",
+                ruleChain.getId(), null, 0L);
+        msgQueue.put(tbMsg, ruleChain.getId().getId(), 0L);
+
+        Thread.sleep(1000);
+
+        RuleChainMetaData metaData = new RuleChainMetaData();
+        metaData.setRuleChainId(ruleChain.getId());
+
+        RuleNode ruleNode = new RuleNode();
+        ruleNode.setName("Simple Rule Node");
+        ruleNode.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
+        ruleNode.setDebugMode(true);
+        TbGetAttributesNodeConfiguration configuration = new TbGetAttributesNodeConfiguration();
+        configuration.setServerAttributeNames(Collections.singletonList("serverAttributeKey"));
+        ruleNode.setConfiguration(mapper.valueToTree(configuration));
+
+        metaData.setNodes(Collections.singletonList(ruleNode));
+        metaData.setFirstNodeIndex(0);
+
+        metaData = saveRuleChainMetaData(metaData);
+        Assert.assertNotNull(metaData);
+
+        ruleChain = getRuleChain(ruleChain.getId());
+        Assert.assertNotNull(ruleChain.getFirstRuleNodeId());
+
+        Thread.sleep(3000);
+
+        TimePageData<Event> events = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000);
+
+        Assert.assertEquals(2, events.getData().size());
+
+        Event inEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get();
+        Assert.assertEquals(ruleChain.getFirstRuleNodeId(), inEvent.getEntityId());
+        Assert.assertEquals(device.getId().getId().toString(), inEvent.getBody().get("entityId").asText());
+
+        Event outEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.OUT)).findFirst().get();
+        Assert.assertEquals(ruleChain.getFirstRuleNodeId(), outEvent.getEntityId());
+        Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText());
+
+        Assert.assertEquals("serverAttributeValue", getMetadata(outEvent).get("ss_serverAttributeKey").asText());
     }
 
 }
diff --git a/application/src/test/java/org/thingsboard/server/rules/RuleEngineNoSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/rules/RuleEngineNoSqlTestSuite.java
new file mode 100644
index 0000000..bffe491
--- /dev/null
+++ b/application/src/test/java/org/thingsboard/server/rules/RuleEngineNoSqlTestSuite.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.rules;
+
+import org.cassandraunit.dataset.cql.ClassPathCQLDataSet;
+import org.junit.ClassRule;
+import org.junit.extensions.cpsuite.ClasspathSuite;
+import org.junit.runner.RunWith;
+import org.thingsboard.server.dao.CustomCassandraCQLUnit;
+import org.thingsboard.server.dao.CustomSqlUnit;
+
+import java.util.Arrays;
+
+@RunWith(ClasspathSuite.class)
+@ClasspathSuite.ClassnameFilters({
+        "org.thingsboard.server.rules.flow.nosql.*Test",
+        "org.thingsboard.server.rules.lifecycle.nosql.*Test"
+})
+public class RuleEngineNoSqlTestSuite {
+
+    @ClassRule
+    public static CustomCassandraCQLUnit cassandraUnit =
+            new CustomCassandraCQLUnit(
+                    Arrays.asList(
+                            new ClassPathCQLDataSet("cassandra/schema.cql", false, false),
+                            new ClassPathCQLDataSet("cassandra/system-data.cql", false, false)),
+                    "cassandra-test.yaml", 30000l);
+
+}
diff --git a/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java
index 65b4293..7b13e2f 100644
--- a/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java
+++ b/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java
@@ -24,7 +24,8 @@ import java.util.Arrays;
 
 @RunWith(ClasspathSuite.class)
 @ClasspathSuite.ClassnameFilters({
-        "org.thingsboard.server.rules.flow.*Test"})
+        "org.thingsboard.server.rules.flow.sql.*Test",
+        "org.thingsboard.server.rules.lifecycle.sql.*Test"})
 public class RuleEngineSqlTestSuite {
 
     @ClassRule
diff --git a/application/src/test/java/org/thingsboard/server/service/script/NashornJsEngineTest.java b/application/src/test/java/org/thingsboard/server/service/script/NashornJsEngineTest.java
index e6a48e2..1981287 100644
--- a/application/src/test/java/org/thingsboard/server/service/script/NashornJsEngineTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/script/NashornJsEngineTest.java
@@ -42,7 +42,7 @@ public class NashornJsEngineTest {
         metaData.putValue("humidity", "99");
         String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
 
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L);
 
         TbMsg actual = scriptEngine.executeUpdate(msg);
         assertEquals("70", actual.getMetaData().getValue("temp"));
@@ -57,7 +57,7 @@ public class NashornJsEngineTest {
         metaData.putValue("humidity", "99");
         String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
 
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L);
 
         TbMsg actual = scriptEngine.executeUpdate(msg);
         assertEquals("94", actual.getMetaData().getValue("newAttr"));
@@ -72,7 +72,7 @@ public class NashornJsEngineTest {
         metaData.putValue("humidity", "99");
         String rawJson = "{\"name\":\"Vit\",\"passed\": 5,\"bigObj\":{\"prop\":42}}";
 
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L);
 
         TbMsg actual = scriptEngine.executeUpdate(msg);
 
@@ -89,7 +89,7 @@ public class NashornJsEngineTest {
         metaData.putValue("humidity", "99");
         String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
 
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L);
         assertFalse(scriptEngine.executeFilter(msg));
     }
 
@@ -102,7 +102,7 @@ public class NashornJsEngineTest {
         metaData.putValue("humidity", "99");
         String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
 
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L);
         assertTrue(scriptEngine.executeFilter(msg));
     }
 
@@ -122,7 +122,7 @@ public class NashornJsEngineTest {
         metaData.putValue("humidity", "99");
         String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
 
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L);
         Set<String> actual = scriptEngine.executeSwitch(msg);
         assertEquals(Sets.newHashSet("one"), actual);
     }
@@ -143,7 +143,7 @@ public class NashornJsEngineTest {
         metaData.putValue("humidity", "99");
         String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
 
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L);
         Set<String> actual = scriptEngine.executeSwitch(msg);
         assertEquals(Sets.newHashSet("one", "three"), actual);
     }
diff --git a/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java
index cfa0c58..97c6749 100644
--- a/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java
+++ b/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java
@@ -35,5 +35,4 @@ public class SystemSqlTestSuite {
             "sql/drop-all-tables.sql",
             "sql-test.properties");
 
-
 }
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
index 7d4e480..f4e9559 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
@@ -45,4 +45,9 @@ public class DataConstants {
     public static final String IN = "IN";
     public static final String OUT = "OUT";
 
+    public static final String INACTIVITY_EVENT = "INACTIVITY_EVENT";
+    public static final String CONNECT_EVENT = "CONNECT_EVENT";
+    public static final String DISCONNECT_EVENT = "DISCONNECT_EVENT";
+    public static final String ACTIVITY_EVENT = "ACTIVITY_EVENT";
+
 }
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java
index fbc1103..808166b 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java
@@ -22,6 +22,7 @@ import lombok.EqualsAndHashCode;
 import lombok.extern.slf4j.Slf4j;
 import org.thingsboard.server.common.data.HasName;
 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
+import org.thingsboard.server.common.data.id.RuleChainId;
 import org.thingsboard.server.common.data.id.RuleNodeId;
 import org.thingsboard.server.common.data.id.TenantId;
 
@@ -32,6 +33,7 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo<RuleNodeId> impl
 
     private static final long serialVersionUID = -5656679015121235465L;
 
+    private RuleChainId ruleChainId;
     private String type;
     private String name;
     private boolean debugMode;
@@ -49,8 +51,10 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo<RuleNodeId> impl
 
     public RuleNode(RuleNode ruleNode) {
         super(ruleNode);
+        this.ruleChainId = ruleNode.getRuleChainId();
         this.type = ruleNode.getType();
         this.name = ruleNode.getName();
+        this.debugMode = ruleNode.isDebugMode();
         this.setConfiguration(ruleNode.getConfiguration());
     }
 
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ClusterEventMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ClusterEventMsg.java
index 67f4de7..7d157f6 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ClusterEventMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ClusterEventMsg.java
@@ -16,14 +16,20 @@
 package org.thingsboard.server.common.msg.cluster;
 
 import lombok.Data;
+import org.thingsboard.server.common.msg.MsgType;
+import org.thingsboard.server.common.msg.TbActorMsg;
 
 /**
  * @author Andrew Shvayka
  */
 @Data
-public final class ClusterEventMsg {
+public final class ClusterEventMsg implements TbActorMsg {
 
     private final ServerAddress serverAddress;
     private final boolean added;
 
+    @Override
+    public MsgType getMsgType() {
+        return MsgType.CLUSTER_EVENT_MSG;
+    }
 }
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesSubscribeMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesSubscribeMsg.java
index 6894ac5..cadaf3c 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesSubscribeMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesSubscribeMsg.java
@@ -16,14 +16,14 @@
 package org.thingsboard.server.common.msg.core;
 
 import org.thingsboard.server.common.msg.session.FromDeviceMsg;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 /**
  * @author Andrew Shvayka
  */
 public class AttributesSubscribeMsg implements FromDeviceMsg {
     @Override
-    public MsgType getMsgType() {
-        return MsgType.SUBSCRIBE_ATTRIBUTES_REQUEST;
+    public SessionMsgType getMsgType() {
+        return SessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST;
     }
 }
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUnsubscribeMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUnsubscribeMsg.java
index e3fcd6f..c98ad2a 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUnsubscribeMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUnsubscribeMsg.java
@@ -16,14 +16,15 @@
 package org.thingsboard.server.common.msg.core;
 
 import org.thingsboard.server.common.msg.session.FromDeviceMsg;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 /**
  * @author Andrew Shvayka
  */
 public class AttributesUnsubscribeMsg implements FromDeviceMsg {
     @Override
-    public MsgType getMsgType() {
-        return MsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST;
+    public SessionMsgType getMsgType() {
+        return SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST;
     }
 }
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateNotification.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateNotification.java
index 1329489..1f0f9dd 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateNotification.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateNotification.java
@@ -17,7 +17,8 @@ package org.thingsboard.server.common.msg.core;
 
 import lombok.ToString;
 import org.thingsboard.server.common.msg.kv.AttributesKVMsg;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 import org.thingsboard.server.common.msg.session.ToDeviceMsg;
 
 @ToString
@@ -36,9 +37,8 @@ public class AttributesUpdateNotification implements ToDeviceMsg {
         return true;
     }
 
-    @Override
-    public MsgType getMsgType() {
-        return MsgType.ATTRIBUTES_UPDATE_NOTIFICATION;
+    public SessionMsgType getSessionMsgType() {
+        return SessionMsgType.ATTRIBUTES_UPDATE_NOTIFICATION;
     }
 
     public AttributesKVMsg getData() {
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicCommandAckResponse.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicCommandAckResponse.java
index ef772c3..3ee04ba 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicCommandAckResponse.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicCommandAckResponse.java
@@ -15,26 +15,27 @@
  */
 package org.thingsboard.server.common.msg.core;
 
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 public class BasicCommandAckResponse extends BasicResponseMsg<Integer> implements StatusCodeResponse {
 
     private static final long serialVersionUID = 1L;
 
-    public static BasicCommandAckResponse onSuccess(MsgType requestMsgType, Integer requestId) {
+    public static BasicCommandAckResponse onSuccess(SessionMsgType requestMsgType, Integer requestId) {
         return BasicCommandAckResponse.onSuccess(requestMsgType, requestId, 200);
     }
 
-    public static BasicCommandAckResponse onSuccess(MsgType requestMsgType, Integer requestId, Integer code) {
+    public static BasicCommandAckResponse onSuccess(SessionMsgType requestMsgType, Integer requestId, Integer code) {
         return new BasicCommandAckResponse(requestMsgType, requestId, true, null, code);
     }
 
-    public static BasicCommandAckResponse onError(MsgType requestMsgType, Integer requestId, Exception error) {
+    public static BasicCommandAckResponse onError(SessionMsgType requestMsgType, Integer requestId, Exception error) {
         return new BasicCommandAckResponse(requestMsgType, requestId, false, error, null);
     }
 
-    private BasicCommandAckResponse(MsgType requestMsgType, Integer requestId, boolean success, Exception error, Integer code) {
-        super(requestMsgType, requestId, MsgType.TO_DEVICE_RPC_RESPONSE_ACK, success, error, code);
+    private BasicCommandAckResponse(SessionMsgType requestMsgType, Integer requestId, boolean success, Exception error, Integer code) {
+        super(requestMsgType, requestId, SessionMsgType.TO_DEVICE_RPC_RESPONSE_ACK, success, error, code);
     }
 
     @Override
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesRequest.java
index b431c14..e0f6d7e 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesRequest.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesRequest.java
@@ -16,7 +16,8 @@
 package org.thingsboard.server.common.msg.core;
 
 import lombok.ToString;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 import java.util.Collections;
 import java.util.Optional;
@@ -41,8 +42,8 @@ public class BasicGetAttributesRequest extends BasicRequest implements GetAttrib
     }
 
     @Override
-    public MsgType getMsgType() {
-        return MsgType.GET_ATTRIBUTES_REQUEST;
+    public SessionMsgType getMsgType() {
+        return SessionMsgType.GET_ATTRIBUTES_REQUEST;
     }
 
     @Override
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesResponse.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesResponse.java
index 5072de2..e3eb15d 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesResponse.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesResponse.java
@@ -17,23 +17,24 @@ package org.thingsboard.server.common.msg.core;
 
 import lombok.ToString;
 import org.thingsboard.server.common.msg.kv.AttributesKVMsg;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 @ToString
 public class BasicGetAttributesResponse extends BasicResponseMsg<AttributesKVMsg> implements GetAttributesResponse {
 
     private static final long serialVersionUID = 1L;
 
-    public static BasicGetAttributesResponse onSuccess(MsgType requestMsgType, int requestId, AttributesKVMsg code) {
+    public static BasicGetAttributesResponse onSuccess(SessionMsgType requestMsgType, int requestId, AttributesKVMsg code) {
         return new BasicGetAttributesResponse(requestMsgType, requestId, true, null, code);
     }
 
-    public static BasicGetAttributesResponse onError(MsgType requestMsgType, int requestId, Exception error) {
+    public static BasicGetAttributesResponse onError(SessionMsgType requestMsgType, int requestId, Exception error) {
         return new BasicGetAttributesResponse(requestMsgType, requestId, false, error, null);
     }
 
-    private BasicGetAttributesResponse(MsgType requestMsgType, int requestId, boolean success, Exception error, AttributesKVMsg code) {
-        super(requestMsgType, requestId, MsgType.GET_ATTRIBUTES_RESPONSE, success, error, code);
+    private BasicGetAttributesResponse(SessionMsgType requestMsgType, int requestId, boolean success, Exception error, AttributesKVMsg code) {
+        super(requestMsgType, requestId, SessionMsgType.GET_ATTRIBUTES_RESPONSE, success, error, code);
     }
 
 }
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicResponseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicResponseMsg.java
index caaa15c..c61d8e5 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicResponseMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicResponseMsg.java
@@ -18,32 +18,33 @@ package org.thingsboard.server.common.msg.core;
 import java.io.Serializable;
 import java.util.Optional;
 
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 
 public class BasicResponseMsg<T extends Serializable> implements ResponseMsg<T> {
 
     private static final long serialVersionUID = 1L;
 
-    private final MsgType requestMsgType;
+    private final SessionMsgType requestMsgType;
     private final Integer requestId;
-    private final MsgType msgType;
+    private final SessionMsgType sessionMsgType;
     private final boolean success;
     private final T data;
     private final Exception error;
 
-    protected BasicResponseMsg(MsgType requestMsgType, Integer requestId, MsgType msgType, boolean success, Exception error, T data) {
+    protected BasicResponseMsg(SessionMsgType requestMsgType, Integer requestId, SessionMsgType sessionMsgType, boolean success, Exception error, T data) {
         super();
         this.requestMsgType = requestMsgType;
         this.requestId = requestId;
-        this.msgType = msgType;
+        this.sessionMsgType = sessionMsgType;
         this.success = success;
         this.error = error;
         this.data = data;
     }
 
     @Override
-    public MsgType getRequestMsgType() {
+    public SessionMsgType getRequestMsgType() {
         return requestMsgType;
     }
 
@@ -72,8 +73,7 @@ public class BasicResponseMsg<T extends Serializable> implements ResponseMsg<T> 
         return "BasicResponseMsg [success=" + success + ", data=" + data + ", error=" + error + "]";
     }
 
-    @Override
-    public MsgType getMsgType() {
-        return msgType;
+    public SessionMsgType getSessionMsgType() {
+        return sessionMsgType;
     }
 }
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicStatusCodeResponse.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicStatusCodeResponse.java
index 22b525b..f21aa85 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicStatusCodeResponse.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicStatusCodeResponse.java
@@ -16,26 +16,27 @@
 package org.thingsboard.server.common.msg.core;
 
 import lombok.ToString;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 @ToString
 public class BasicStatusCodeResponse extends BasicResponseMsg<Integer> implements StatusCodeResponse {
 
     private static final long serialVersionUID = 1L;
 
-    public static BasicStatusCodeResponse onSuccess(MsgType requestMsgType, Integer requestId) {
+    public static BasicStatusCodeResponse onSuccess(SessionMsgType requestMsgType, Integer requestId) {
         return BasicStatusCodeResponse.onSuccess(requestMsgType, requestId, 0);
     }
 
-    public static BasicStatusCodeResponse onSuccess(MsgType requestMsgType, Integer requestId, Integer code) {
+    public static BasicStatusCodeResponse onSuccess(SessionMsgType requestMsgType, Integer requestId, Integer code) {
         return new BasicStatusCodeResponse(requestMsgType, requestId, true, null, code);
     }
 
-    public static BasicStatusCodeResponse onError(MsgType requestMsgType, Integer requestId, Exception error) {
+    public static BasicStatusCodeResponse onError(SessionMsgType requestMsgType, Integer requestId, Exception error) {
         return new BasicStatusCodeResponse(requestMsgType, requestId, false, error, null);
     }
 
-    private BasicStatusCodeResponse(MsgType requestMsgType, Integer requestId, boolean success, Exception error, Integer code) {
-        super(requestMsgType, requestId, MsgType.STATUS_CODE_RESPONSE, success, error, code);
+    private BasicStatusCodeResponse(SessionMsgType requestMsgType, Integer requestId, boolean success, Exception error, Integer code) {
+        super(requestMsgType, requestId, SessionMsgType.STATUS_CODE_RESPONSE, success, error, code);
     }
 }
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicTelemetryUploadRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicTelemetryUploadRequest.java
index 1d96a65..60faeb1 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicTelemetryUploadRequest.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicTelemetryUploadRequest.java
@@ -21,7 +21,8 @@ import java.util.List;
 import java.util.Map;
 
 import org.thingsboard.server.common.data.kv.KvEntry;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 public class BasicTelemetryUploadRequest extends BasicRequest implements TelemetryUploadRequest {
 
@@ -48,8 +49,8 @@ public class BasicTelemetryUploadRequest extends BasicRequest implements Telemet
     }
 
     @Override
-    public MsgType getMsgType() {
-        return MsgType.POST_TELEMETRY_REQUEST;
+    public SessionMsgType getMsgType() {
+        return SessionMsgType.POST_TELEMETRY_REQUEST;
     }
 
     @Override
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ResponseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ResponseMsg.java
index 2eb0959..3e70460 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ResponseMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ResponseMsg.java
@@ -18,12 +18,12 @@ package org.thingsboard.server.common.msg.core;
 import java.io.Serializable;
 import java.util.Optional;
 
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 import org.thingsboard.server.common.msg.session.ToDeviceMsg;
 
 public interface ResponseMsg<T extends Serializable> extends ToDeviceMsg {
 
-    MsgType getRequestMsgType();
+    SessionMsgType getRequestMsgType();
 
     Integer getRequestId();
 
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcSubscribeMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcSubscribeMsg.java
index d4fc1d7..f8f24e8 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcSubscribeMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcSubscribeMsg.java
@@ -16,14 +16,15 @@
 package org.thingsboard.server.common.msg.core;
 
 import org.thingsboard.server.common.msg.session.FromDeviceMsg;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 /**
  * @author Andrew Shvayka
  */
 public class RpcSubscribeMsg implements FromDeviceMsg {
     @Override
-    public MsgType getMsgType() {
-        return MsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST;
+    public SessionMsgType getMsgType() {
+        return SessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST;
     }
 }
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcUnsubscribeMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcUnsubscribeMsg.java
index b1532d7..23eb238 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcUnsubscribeMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcUnsubscribeMsg.java
@@ -16,14 +16,15 @@
 package org.thingsboard.server.common.msg.core;
 
 import org.thingsboard.server.common.msg.session.FromDeviceMsg;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 /**
  * @author Andrew Shvayka
  */
 public class RpcUnsubscribeMsg implements FromDeviceMsg {
     @Override
-    public MsgType getMsgType() {
-        return MsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST;
+    public SessionMsgType getMsgType() {
+        return SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST;
     }
 }
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineError.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineError.java
index bc8ceb4..dcfde0f 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineError.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineError.java
@@ -21,7 +21,7 @@ package org.thingsboard.server.common.msg.core;
 
 public enum RuleEngineError {
 
-    NO_RULES, NO_ACTIVE_RULES, NO_FILTERS_MATCHED, NO_REQUEST_FROM_ACTIONS, NO_TWO_WAY_ACTIONS, NO_RESPONSE_FROM_ACTIONS, PLUGIN_TIMEOUT(true);
+    QUEUE_PUT_TIMEOUT(true), SERVER_ERROR(true), TIMEOUT;
 
     private final boolean critical;
 
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineErrorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineErrorMsg.java
index 5cb3314..e0ff23b 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineErrorMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineErrorMsg.java
@@ -16,7 +16,8 @@
 package org.thingsboard.server.common.msg.core;
 
 import lombok.Data;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 import org.thingsboard.server.common.msg.session.ToDeviceMsg;
 
 /**
@@ -25,7 +26,7 @@ import org.thingsboard.server.common.msg.session.ToDeviceMsg;
 @Data
 public class RuleEngineErrorMsg implements ToDeviceMsg {
 
-    private final MsgType inMsgType;
+    private final SessionMsgType inSessionMsgType;
     private final RuleEngineError error;
 
     @Override
@@ -33,27 +34,18 @@ public class RuleEngineErrorMsg implements ToDeviceMsg {
         return false;
     }
 
-    @Override
-    public MsgType getMsgType() {
-        return MsgType.RULE_ENGINE_ERROR;
+    public SessionMsgType getSessionMsgType() {
+        return SessionMsgType.RULE_ENGINE_ERROR;
     }
 
     public String getErrorMsg() {
         switch (error) {
-            case NO_RULES:
-                return "No rules configured!";
-            case NO_ACTIVE_RULES:
-                return "No active rules!";
-            case NO_FILTERS_MATCHED:
-                return "No rules that match current message!";
-            case NO_REQUEST_FROM_ACTIONS:
-                return "Rule filters match, but no plugin message produced by rule action!";
-            case NO_TWO_WAY_ACTIONS:
-                return "Rule filters match, but no rule with two-way action configured!";
-            case NO_RESPONSE_FROM_ACTIONS:
-                return "Rule filters match, message processed by plugin, but no response produced by rule action!";
-            case PLUGIN_TIMEOUT:
-                return "Timeout during processing of message by plugin!";
+            case QUEUE_PUT_TIMEOUT:
+                return "Timeout during persistence of the message to the queue!";
+            case SERVER_ERROR:
+                return "Error during processing of message by the server!";
+            case TIMEOUT:
+                return "Timeout during processing of message by the server!";
             default:
                 throw new RuntimeException("Error " + error + " is not supported!");
         }
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseMsg.java
index 738bc6e..6d3ad5e 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseMsg.java
@@ -16,14 +16,15 @@
 package org.thingsboard.server.common.msg.core;
 
 import org.thingsboard.server.common.msg.session.FromDeviceMsg;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 /**
  * @author Andrew Shvayka
  */
 public class SessionCloseMsg implements FromDeviceMsg {
     @Override
-    public MsgType getMsgType() {
-        return MsgType.SESSION_CLOSE;
+    public SessionMsgType getMsgType() {
+        return SessionMsgType.SESSION_CLOSE;
     }
 }
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseNotification.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseNotification.java
index bf3c982..bf36a9b 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseNotification.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseNotification.java
@@ -17,7 +17,8 @@ package org.thingsboard.server.common.msg.core;
 
 import lombok.ToString;
 import org.thingsboard.server.common.msg.kv.AttributesKVMsg;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 import org.thingsboard.server.common.msg.session.ToDeviceMsg;
 
 @ToString
@@ -30,9 +31,8 @@ public class SessionCloseNotification implements ToDeviceMsg {
         return true;
     }
 
-    @Override
-    public MsgType getMsgType() {
-        return MsgType.SESSION_CLOSE;
+    public SessionMsgType getSessionMsgType() {
+        return SessionMsgType.SESSION_CLOSE;
     }
 
 }
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionOpenMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionOpenMsg.java
index f3a7bc7..c121a71 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionOpenMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionOpenMsg.java
@@ -16,14 +16,15 @@
 package org.thingsboard.server.common.msg.core;
 
 import org.thingsboard.server.common.msg.session.FromDeviceMsg;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 /**
  * @author Andrew Shvayka
  */
 public class SessionOpenMsg implements FromDeviceMsg {
     @Override
-    public MsgType getMsgType() {
-        return MsgType.SESSION_OPEN;
+    public SessionMsgType getMsgType() {
+        return SessionMsgType.SESSION_OPEN;
     }
 }
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcRequestMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcRequestMsg.java
index c9eeb4e..d1a1f96 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcRequestMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcRequestMsg.java
@@ -16,7 +16,8 @@
 package org.thingsboard.server.common.msg.core;
 
 import lombok.Data;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 import org.thingsboard.server.common.msg.session.ToDeviceMsg;
 
 /**
@@ -29,9 +30,8 @@ public class ToDeviceRpcRequestMsg implements ToDeviceMsg {
     private final String method;
     private final String params;
 
-    @Override
-    public MsgType getMsgType() {
-        return MsgType.TO_DEVICE_RPC_REQUEST;
+    public SessionMsgType getSessionMsgType() {
+        return SessionMsgType.TO_DEVICE_RPC_REQUEST;
     }
 
     @Override
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcResponseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcResponseMsg.java
index ec739b3..4fa3024 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcResponseMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcResponseMsg.java
@@ -17,7 +17,8 @@ package org.thingsboard.server.common.msg.core;
 
 import lombok.Data;
 import org.thingsboard.server.common.msg.session.FromDeviceMsg;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 /**
  * @author Andrew Shvayka
@@ -29,7 +30,7 @@ public class ToDeviceRpcResponseMsg implements FromDeviceMsg {
     private final String data;
 
     @Override
-    public MsgType getMsgType() {
-        return MsgType.TO_DEVICE_RPC_RESPONSE;
+    public SessionMsgType getMsgType() {
+        return SessionMsgType.TO_DEVICE_RPC_RESPONSE;
     }
 }
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java
index 87708a7..3823aca 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java
@@ -17,7 +17,7 @@ package org.thingsboard.server.common.msg.core;
 
 import lombok.Data;
 import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 /**
  * @author Andrew Shvayka
@@ -30,7 +30,7 @@ public class ToServerRpcRequestMsg implements FromDeviceRequestMsg {
     private final String params;
 
     @Override
-    public MsgType getMsgType() {
-        return MsgType.TO_SERVER_RPC_REQUEST;
+    public SessionMsgType getMsgType() {
+        return SessionMsgType.TO_SERVER_RPC_REQUEST;
     }
 }
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcResponseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcResponseMsg.java
index b9a43d1..82f44e9 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcResponseMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcResponseMsg.java
@@ -17,7 +17,8 @@ package org.thingsboard.server.common.msg.core;
 
 import lombok.Data;
 import org.thingsboard.server.common.msg.session.FromDeviceMsg;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 import org.thingsboard.server.common.msg.session.ToDeviceMsg;
 
 /**
@@ -29,9 +30,8 @@ public class ToServerRpcResponseMsg implements ToDeviceMsg {
     private final int requestId;
     private final String data;
 
-    @Override
-    public MsgType getMsgType() {
-        return MsgType.TO_SERVER_RPC_RESPONSE;
+    public SessionMsgType getSessionMsgType() {
+        return SessionMsgType.TO_SERVER_RPC_RESPONSE;
     }
 
     @Override
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
index f8f2044..d63456e 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
@@ -18,33 +18,41 @@ package org.thingsboard.server.common.msg;
 /**
  * Created by ashvayka on 15.03.18.
  */
+//TODO: add all "See" references
 public enum MsgType {
 
     /**
+     * ADDED/UPDATED/DELETED events for server nodes.
+     *
+     * See {@link org.thingsboard.server.common.msg.cluster.ClusterEventMsg}
+     */
+    CLUSTER_EVENT_MSG,
+
+    /**
      * ADDED/UPDATED/DELETED events for main entities.
      *
-     * @See {@link org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg}
+     * See {@link org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg}
      */
     COMPONENT_LIFE_CYCLE_MSG,
 
     /**
      * Misc messages from the REST API/SERVICE layer to the new rule engine.
      *
-     * @See {@link org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg}
+     * See {@link org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg}
      */
     SERVICE_TO_RULE_ENGINE_MSG,
 
-
-    SESSION_TO_DEVICE_ACTOR_MSG,
-    DEVICE_ACTOR_TO_SESSION_MSG,
-
-
     /**
      * Message that is sent by RuleChainActor to RuleActor with command to process TbMsg.
      */
     RULE_CHAIN_TO_RULE_MSG,
 
     /**
+     * Message that is sent by RuleChainActor to other RuleChainActor with command to process TbMsg.
+     */
+    RULE_CHAIN_TO_RULE_CHAIN_MSG,
+
+    /**
      * Message that is sent by RuleActor to RuleChainActor with command to process TbMsg by next nodes in chain.
      */
     RULE_TO_RULE_CHAIN_TELL_NEXT_MSG,
@@ -59,4 +67,35 @@ public enum MsgType {
      */
     RULE_TO_SELF_MSG,
 
+    /**
+     * Message that is sent by Session Actor to Device Actor. Represents messages from the device itself.
+     */
+    DEVICE_SESSION_TO_DEVICE_ACTOR_MSG,
+
+    DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG,
+
+    DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG,
+
+    DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG,
+
+    DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG,
+
+    SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG,
+
+    DEVICE_ACTOR_SERVER_SIDE_RPC_TIMEOUT_MSG,
+
+    DEVICE_ACTOR_CLIENT_SIDE_RPC_TIMEOUT_MSG,
+
+    DEVICE_ACTOR_QUEUE_TIMEOUT_MSG,
+
+    /**
+     * Message that is sent from the Device Actor to Rule Engine. Requires acknowledgement
+     */
+    DEVICE_ACTOR_TO_RULE_ENGINE_MSG,
+
+    /**
+     * Message that is sent from Rule Engine to the Device Actor when message is successfully pushed to queue.
+     */
+    RULE_ENGINE_QUEUE_PUT_ACK_MSG;
+
 }
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceMsg.java
index 19d45a7..71b6057 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceMsg.java
@@ -19,6 +19,6 @@ import java.io.Serializable;
 
 public interface FromDeviceMsg extends Serializable {
 
-    MsgType getMsgType();
+    SessionMsgType getMsgType();
 
 }
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceMsg.java
index 31ec1fa..705e864 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceMsg.java
@@ -21,6 +21,6 @@ public interface ToDeviceMsg extends Serializable {
 
     boolean isSuccess();
 
-    MsgType getMsgType();
+    SessionMsgType getSessionMsgType();
 
 }
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
index 1c7de3b..2b30872 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
@@ -15,12 +15,13 @@
  */
 package org.thingsboard.server.common.msg;
 
-import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.id.EntityIdFactory;
+import org.thingsboard.server.common.data.id.RuleChainId;
+import org.thingsboard.server.common.data.id.RuleNodeId;
 import org.thingsboard.server.common.msg.gen.MsgProtos;
 
 import java.io.Serializable;
@@ -41,22 +42,40 @@ public final class TbMsg implements Serializable {
     private final TbMsgDataType dataType;
     private final String data;
 
-    public TbMsg(UUID id, String type, EntityId originator, TbMsgMetaData metaData, String data) {
+    //The following fields are not persisted to DB, because they can always be recovered from the context;
+    private final RuleChainId ruleChainId;
+    private final RuleNodeId ruleNodeId;
+    private final long clusterPartition;
+
+    public TbMsg(UUID id, String type, EntityId originator, TbMsgMetaData metaData, String data,
+                 RuleChainId ruleChainId, RuleNodeId ruleNodeId, long clusterPartition) {
         this.id = id;
         this.type = type;
         this.originator = originator;
         this.metaData = metaData;
-        this.dataType = TbMsgDataType.JSON;
         this.data = data;
+        this.dataType = TbMsgDataType.JSON;
+        this.ruleChainId = ruleChainId;
+        this.ruleNodeId = ruleNodeId;
+        this.clusterPartition = clusterPartition;
     }
 
     public static ByteBuffer toBytes(TbMsg msg) {
         MsgProtos.TbMsgProto.Builder builder = MsgProtos.TbMsgProto.newBuilder();
         builder.setId(msg.getId().toString());
         builder.setType(msg.getType());
-        if (msg.getOriginator() != null) {
-            builder.setEntityType(msg.getOriginator().getEntityType().name());
-            builder.setEntityId(msg.getOriginator().getId().toString());
+        builder.setEntityType(msg.getOriginator().getEntityType().name());
+        builder.setEntityIdMSB(msg.getOriginator().getId().getMostSignificantBits());
+        builder.setEntityIdLSB(msg.getOriginator().getId().getLeastSignificantBits());
+
+        if (msg.getRuleChainId() != null) {
+            builder.setRuleChainIdMSB(msg.getRuleChainId().getId().getMostSignificantBits());
+            builder.setRuleChainIdLSB(msg.getRuleChainId().getId().getLeastSignificantBits());
+        }
+
+        if (msg.getRuleNodeId() != null) {
+            builder.setRuleNodeIdMSB(msg.getRuleNodeId().getId().getMostSignificantBits());
+            builder.setRuleNodeIdLSB(msg.getRuleNodeId().getId().getLeastSignificantBits());
         }
 
         if (msg.getMetaData() != null) {
@@ -73,15 +92,21 @@ public final class TbMsg implements Serializable {
         try {
             MsgProtos.TbMsgProto proto = MsgProtos.TbMsgProto.parseFrom(buffer.array());
             TbMsgMetaData metaData = new TbMsgMetaData(proto.getMetaData().getDataMap());
-            EntityId entityId = EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId());
+            EntityId entityId = EntityIdFactory.getByTypeAndUuid(proto.getEntityType(), new UUID(proto.getEntityIdMSB(), proto.getEntityIdLSB()));
+            RuleChainId ruleChainId = new RuleChainId(new UUID(proto.getRuleChainIdMSB(), proto.getRuleChainIdLSB()));
+            RuleNodeId ruleNodeId = null;
+            if(proto.getRuleNodeIdMSB() != 0L && proto.getRuleNodeIdLSB() != 0L) {
+                 ruleNodeId = new RuleNodeId(new UUID(proto.getRuleNodeIdMSB(), proto.getRuleNodeIdLSB()));
+            }
             TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()];
-            return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, dataType, proto.getData());
+            return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, dataType, proto.getData(), ruleChainId, ruleNodeId, proto.getClusterPartition());
         } catch (InvalidProtocolBufferException e) {
             throw new IllegalStateException("Could not parse protobuf for TbMsg", e);
         }
     }
 
-    public TbMsg copy() {
-        return new TbMsg(id, type, originator, metaData.copy(), dataType, data);
+    public TbMsg copy(UUID newId, RuleChainId ruleChainId, RuleNodeId ruleNodeId, long clusterPartition) {
+        return new TbMsg(newId, type, originator, metaData, dataType, data, ruleChainId, ruleNodeId, clusterPartition);
     }
+
 }
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java
index 4b7314c..e157aaa 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java
@@ -31,10 +31,10 @@ import java.util.concurrent.ConcurrentHashMap;
 @NoArgsConstructor
 public final class TbMsgMetaData implements Serializable {
 
-    private Map<String, String> data = new ConcurrentHashMap<>();
+    private final Map<String, String> data = new ConcurrentHashMap<>();
 
     public TbMsgMetaData(Map<String, String> data) {
-        this.data = data;
+        this.data.putAll(data);
     }
 
     public String getValue(String key) {
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/timeout/DeviceActorServerSideRpcTimeoutMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/timeout/DeviceActorServerSideRpcTimeoutMsg.java
new file mode 100644
index 0000000..cd86892
--- /dev/null
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/timeout/DeviceActorServerSideRpcTimeoutMsg.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.common.msg.timeout;
+
+import org.thingsboard.server.common.msg.MsgType;
+
+/**
+ * @author Andrew Shvayka
+ */
+public final class DeviceActorServerSideRpcTimeoutMsg extends TimeoutMsg<Integer> {
+
+    public DeviceActorServerSideRpcTimeoutMsg(Integer id, long timeout) {
+        super(id, timeout);
+    }
+
+    @Override
+    public MsgType getMsgType() {
+        return MsgType.DEVICE_ACTOR_SERVER_SIDE_RPC_TIMEOUT_MSG;
+    }
+}
diff --git a/common/message/src/main/proto/tbmsg.proto b/common/message/src/main/proto/tbmsg.proto
index 4ce1fb6..60003dc 100644
--- a/common/message/src/main/proto/tbmsg.proto
+++ b/common/message/src/main/proto/tbmsg.proto
@@ -27,10 +27,19 @@ message TbMsgProto {
     string id = 1;
     string type = 2;
     string entityType = 3;
-    string entityId = 4;
+    int64 entityIdMSB = 4;
+    int64 entityIdLSB = 5;
 
-    TbMsgMetaDataProto metaData = 5;
+    int64 ruleChainIdMSB = 6;
+    int64 ruleChainIdLSB = 7;
+
+    int64 ruleNodeIdMSB = 8;
+    int64 ruleNodeIdLSB = 9;
+    int64 clusterPartition = 10;
+
+    TbMsgMetaDataProto metaData = 11;
+
+    int32 dataType = 12;
+    string data = 13;
 
-    int32 dataType = 6;
-    string data = 7;
 }
\ No newline at end of file
diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java
index 390266a..b99c450 100644
--- a/common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java
+++ b/common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java
@@ -118,13 +118,13 @@ public class JsonConverter {
         }
     }
 
-    public static UpdateAttributesRequest convertToAttributes(JsonElement element) {
+    public static AttributesUpdateRequest convertToAttributes(JsonElement element) {
         return convertToAttributes(element, BasicRequest.DEFAULT_REQUEST_ID);
     }
 
-    public static UpdateAttributesRequest convertToAttributes(JsonElement element, int requestId) {
+    public static AttributesUpdateRequest convertToAttributes(JsonElement element, int requestId) {
         if (element.isJsonObject()) {
-            BasicUpdateAttributesRequest request = new BasicUpdateAttributesRequest(requestId);
+            BasicAttributesUpdateRequest request = new BasicAttributesUpdateRequest(requestId);
             long ts = System.currentTimeMillis();
             request.add(parseValues(element.getAsJsonObject()).stream().map(kv -> new BaseAttributeKvEntry(kv, ts)).collect(Collectors.toList()));
             return request;
diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java b/common/transport/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java
index 93a6e99..080f874 100644
--- a/common/transport/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java
+++ b/common/transport/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java
@@ -16,7 +16,7 @@
 package org.thingsboard.server.common.transport;
 
 import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 import org.thingsboard.server.common.msg.session.SessionActorToAdaptorMsg;
 import org.thingsboard.server.common.msg.session.SessionContext;
 import org.thingsboard.server.common.transport.adaptor.AdaptorException;
@@ -25,7 +25,7 @@ import java.util.Optional;
 
 public interface TransportAdaptor<C extends SessionContext, T, V> {
 
-    AdaptorToSessionActorMsg convertToActorMsg(C ctx, MsgType type, T inbound) throws AdaptorException;
+    AdaptorToSessionActorMsg convertToActorMsg(C ctx, SessionMsgType type, T inbound) throws AdaptorException;
 
     Optional<V> convertToAdaptorMsg(C ctx, SessionActorToAdaptorMsg msg) throws AdaptorException;
 
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
index 52b15ef..37b9158 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
@@ -352,6 +352,7 @@ public class ModelConstants {
      * Cassandra rule node constants.
      */
     public static final String RULE_NODE_COLUMN_FAMILY_NAME = "rule_node";
+    public static final String RULE_NODE_CHAIN_ID_PROPERTY = "rule_chain_id";
     public static final String RULE_NODE_TYPE_PROPERTY = "type";
     public static final String RULE_NODE_NAME_PROPERTY = "name";
     public static final String RULE_NODE_CONFIGURATION_PROPERTY = "configuration";
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleNodeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleNodeEntity.java
index 8d3f3c3..ec13022 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleNodeEntity.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleNodeEntity.java
@@ -24,6 +24,7 @@ import lombok.EqualsAndHashCode;
 import lombok.Getter;
 import lombok.Setter;
 import lombok.ToString;
+import org.thingsboard.server.common.data.id.RuleChainId;
 import org.thingsboard.server.common.data.id.RuleNodeId;
 import org.thingsboard.server.common.data.rule.RuleNode;
 import org.thingsboard.server.dao.model.SearchTextEntity;
@@ -41,6 +42,8 @@ public class RuleNodeEntity implements SearchTextEntity<RuleNode> {
     @PartitionKey
     @Column(name = ID_PROPERTY)
     private UUID id;
+    @Column(name = RULE_NODE_CHAIN_ID_PROPERTY)
+    private UUID ruleChainId;
     @Column(name = RULE_NODE_TYPE_PROPERTY)
     private String type;
     @Column(name = RULE_NODE_NAME_PROPERTY)
@@ -56,7 +59,6 @@ public class RuleNodeEntity implements SearchTextEntity<RuleNode> {
     @Column(name = DEBUG_MODE)
     private boolean debugMode;
 
-
     public RuleNodeEntity() {
     }
 
@@ -64,6 +66,9 @@ public class RuleNodeEntity implements SearchTextEntity<RuleNode> {
         if (ruleNode.getId() != null) {
             this.id = ruleNode.getUuidId();
         }
+        if (ruleNode.getRuleChainId() != null) {
+            this.ruleChainId = ruleNode.getRuleChainId().getId();
+        }
         this.type = ruleNode.getType();
         this.name = ruleNode.getName();
         this.debugMode = ruleNode.isDebugMode();
@@ -92,6 +97,14 @@ public class RuleNodeEntity implements SearchTextEntity<RuleNode> {
         this.id = id;
     }
 
+    public UUID getRuleChainId() {
+        return ruleChainId;
+    }
+
+    public void setRuleChainId(UUID ruleChainId) {
+        this.ruleChainId = ruleChainId;
+    }
+
     public String getType() {
         return type;
     }
@@ -132,6 +145,9 @@ public class RuleNodeEntity implements SearchTextEntity<RuleNode> {
     public RuleNode toData() {
         RuleNode ruleNode = new RuleNode(new RuleNodeId(id));
         ruleNode.setCreatedTime(UUIDs.unixTimestamp(id));
+        if (this.ruleChainId != null) {
+            ruleNode.setRuleChainId(new RuleChainId(this.ruleChainId));
+        }
         ruleNode.setType(this.type);
         ruleNode.setName(this.name);
         ruleNode.setDebugMode(this.debugMode);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java
index 6a888c2..1d05433 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java
@@ -21,8 +21,10 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 import org.hibernate.annotations.Type;
 import org.hibernate.annotations.TypeDef;
+import org.thingsboard.server.common.data.id.RuleChainId;
 import org.thingsboard.server.common.data.id.RuleNodeId;
 import org.thingsboard.server.common.data.rule.RuleNode;
+import org.thingsboard.server.dao.DaoUtil;
 import org.thingsboard.server.dao.model.BaseSqlEntity;
 import org.thingsboard.server.dao.model.ModelConstants;
 import org.thingsboard.server.dao.model.SearchTextEntity;
@@ -39,6 +41,9 @@ import javax.persistence.Table;
 @Table(name = ModelConstants.RULE_NODE_COLUMN_FAMILY_NAME)
 public class RuleNodeEntity extends BaseSqlEntity<RuleNode> implements SearchTextEntity<RuleNode> {
 
+    @Column(name = ModelConstants.RULE_NODE_CHAIN_ID_PROPERTY)
+    private String ruleChainId;
+
     @Column(name = ModelConstants.RULE_NODE_TYPE_PROPERTY)
     private String type;
 
@@ -66,6 +71,9 @@ public class RuleNodeEntity extends BaseSqlEntity<RuleNode> implements SearchTex
         if (ruleNode.getId() != null) {
             this.setId(ruleNode.getUuidId());
         }
+        if (ruleNode.getRuleChainId() != null) {
+            this.ruleChainId = toString(DaoUtil.getId(ruleNode.getRuleChainId()));
+        }
         this.type = ruleNode.getType();
         this.name = ruleNode.getName();
         this.debugMode = ruleNode.isDebugMode();
@@ -88,6 +96,9 @@ public class RuleNodeEntity extends BaseSqlEntity<RuleNode> implements SearchTex
     public RuleNode toData() {
         RuleNode ruleNode = new RuleNode(new RuleNodeId(getId()));
         ruleNode.setCreatedTime(UUIDs.unixTimestamp(getId()));
+        if (ruleChainId != null) {
+            ruleNode.setRuleChainId(new RuleChainId(toUUID(ruleChainId)));
+        }
         ruleNode.setType(type);
         ruleNode.setName(name);
         ruleNode.setDebugMode(debugMode);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java b/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java
index 85f42ae..ef55bcb 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java
@@ -27,10 +27,9 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.CommandLineRunner;
 import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
+import org.thingsboard.server.common.data.id.RuleChainId;
+import org.thingsboard.server.common.data.id.RuleNodeId;
 import org.thingsboard.server.common.msg.TbMsg;
 import org.thingsboard.server.common.msg.TbMsgDataType;
 import org.thingsboard.server.common.msg.TbMsgMetaData;
@@ -61,11 +60,11 @@ public class QueueBenchmark implements CommandLineRunner {
     }
 
     @Autowired
-    private MsqQueue msqQueue;
+    private MsgQueue msgQueue;
 
     @Override
     public void run(String... strings) throws Exception {
-        System.out.println("It works + " + msqQueue);
+        System.out.println("It works + " + msgQueue);
 
 
         long start = System.currentTimeMillis();
@@ -81,8 +80,8 @@ public class QueueBenchmark implements CommandLineRunner {
                     try {
                         TbMsg msg = randomMsg();
                         UUID nodeId = UUIDs.timeBased();
-                        ListenableFuture<Void> put = msqQueue.put(msg, nodeId, 100L);
-//                    ListenableFuture<Void> put = msqQueue.ack(msg, nodeId, 100L);
+                        ListenableFuture<Void> put = msgQueue.put(msg, nodeId, 100L);
+//                    ListenableFuture<Void> put = msgQueue.ack(msg, nodeId, 100L);
                         Futures.addCallback(put, new FutureCallback<Void>() {
                             @Override
                             public void onSuccess(@Nullable Void result) {
@@ -126,7 +125,7 @@ public class QueueBenchmark implements CommandLineRunner {
         TbMsgMetaData metaData = new TbMsgMetaData();
         metaData.putValue("key", "value");
         String dataStr = "someContent";
-        return new TbMsg(UUIDs.timeBased(), "type", null, metaData, TbMsgDataType.JSON, dataStr);
+        return new TbMsg(UUIDs.timeBased(), "type", null, metaData, TbMsgDataType.JSON, dataStr, new RuleChainId(UUIDs.timeBased()), new RuleNodeId(UUIDs.timeBased()), 0L);
     }
 
     @Bean
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
index cdb9a80..9ce1fbe 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.thingsboard.server.dao.rule;
 
 import com.google.common.util.concurrent.ListenableFuture;
@@ -124,6 +123,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
             }
         }
         for (RuleNode node : toAddOrUpdate) {
+            node.setRuleChainId(ruleChain.getId());
             RuleNode savedNode = ruleNodeDao.save(node);
             try {
                 createRelation(new EntityRelation(ruleChainMetaData.getRuleChainId(), savedNode.getId(),
@@ -137,7 +137,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
             nodes.set(index, savedNode);
             ruleNodeIndexMap.put(savedNode.getId(), index);
         }
-        for (RuleNode node: toDelete) {
+        for (RuleNode node : toDelete) {
             deleteRuleNode(node.getId());
         }
         RuleNodeId firstRuleNodeId = null;
@@ -234,6 +234,12 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
     }
 
     @Override
+    public ListenableFuture<RuleNode> findRuleNodeByIdAsync(RuleNodeId ruleNodeId) {
+        Validator.validateId(ruleNodeId, "Incorrect rule node id for search request.");
+        return ruleNodeDao.findByIdAsync(ruleNodeId.getId());
+    }
+
+    @Override
     public RuleChain getRootTenantRuleChain(TenantId tenantId) {
         Validator.validateId(tenantId, "Incorrect tenant id for search request.");
         List<EntityRelation> relations = relationService.findByFrom(tenantId, RelationTypeGroup.RULE_CHAIN);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
index da7833d..d516e54 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
@@ -46,6 +46,8 @@ public interface RuleChainService {
 
     ListenableFuture<RuleChain> findRuleChainByIdAsync(RuleChainId ruleChainId);
 
+    ListenableFuture<RuleNode> findRuleNodeByIdAsync(RuleNodeId ruleNodeId);
+
     RuleChain getRootTenantRuleChain(TenantId tenantId);
 
     List<RuleNode> getRuleChainNodes(RuleChainId ruleChainId);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitioner.java b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitioner.java
index faad701..a60f685 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitioner.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitioner.java
@@ -30,6 +30,7 @@ import java.time.ZoneOffset;
 import java.util.List;
 import java.util.Optional;
 import java.util.UUID;
+import java.util.concurrent.TimeUnit;
 
 @Component
 @Slf4j
@@ -60,7 +61,7 @@ public class QueuePartitioner {
 
     public List<Long> findUnprocessedPartitions(UUID nodeId, long clusteredHash) {
         Optional<Long> lastPartitionOption = processedPartitionRepository.findLastProcessedPartition(nodeId, clusteredHash);
-        long lastPartition = lastPartitionOption.orElse(System.currentTimeMillis() - 7 * 24 * 60 * 60 * 100);
+        long lastPartition = lastPartitionOption.orElse(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7));
         List<Long> unprocessedPartitions = Lists.newArrayList();
 
         LocalDateTime current = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastPartition), ZoneOffset.UTC);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/queue/InMemoryMsgQueue.java b/dao/src/main/java/org/thingsboard/server/dao/sql/queue/InMemoryMsgQueue.java
new file mode 100644
index 0000000..ce579cd
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/queue/InMemoryMsgQueue.java
@@ -0,0 +1,120 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.sql.queue;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.dao.queue.MsgQueue;
+import org.thingsboard.server.dao.util.SqlDao;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Created by ashvayka on 27.04.18.
+ */
+@Component
+//@ConditionalOnProperty(prefix = "rule.queue", value = "type", havingValue = "memory", matchIfMissing = true)
+@Slf4j
+@SqlDao
+public class InMemoryMsgQueue implements MsgQueue {
+
+    @Value("${rule.queue.max_size}")
+    @Getter
+    private long maxSize;
+
+    private ListeningExecutorService queueExecutor;
+    private AtomicLong pendingMsgCount = new AtomicLong();
+    private Map<InMemoryMsgKey, Map<UUID, TbMsg>> data = new HashMap<>();
+
+    @PostConstruct
+    public void init() {
+        // Should be always single threaded due to absence of locks.
+        queueExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
+    }
+
+    @PreDestroy
+    public void stop() {
+        if (queueExecutor == null) {
+            queueExecutor.shutdownNow();
+        }
+    }
+
+    @Override
+    public ListenableFuture<Void> put(TbMsg msg, UUID nodeId, long clusterPartition) {
+        if (pendingMsgCount.get() < maxSize) {
+            return queueExecutor.submit(() -> {
+                data.computeIfAbsent(new InMemoryMsgKey(nodeId, clusterPartition), key -> new HashMap<>()).put(msg.getId(), msg);
+                pendingMsgCount.incrementAndGet();
+                return null;
+            });
+        } else {
+            return Futures.immediateFailedFuture(new RuntimeException("Message queue is full!"));
+        }
+    }
+
+    @Override
+    public ListenableFuture<Void> ack(TbMsg msg, UUID nodeId, long clusterPartition) {
+        return queueExecutor.submit(() -> {
+            InMemoryMsgKey key = new InMemoryMsgKey(nodeId, clusterPartition);
+            Map<UUID, TbMsg> map = data.get(key);
+            if (map != null) {
+                if (map.remove(msg.getId()) != null) {
+                    pendingMsgCount.decrementAndGet();
+                }
+                if (map.isEmpty()) {
+                    data.remove(key);
+                }
+            }
+            return null;
+        });
+    }
+
+    @Override
+    public Iterable<TbMsg> findUnprocessed(UUID nodeId, long clusterPartition) {
+        ListenableFuture<List<TbMsg>> list = queueExecutor.submit(() -> {
+            InMemoryMsgKey key = new InMemoryMsgKey(nodeId, clusterPartition);
+            Map<UUID, TbMsg> map = data.get(key);
+            if (map != null) {
+                return new ArrayList<>(map.values());
+            } else {
+                return Collections.emptyList();
+            }
+        });
+        try {
+            return list.get();
+        } catch (InterruptedException | ExecutionException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/dao/src/main/resources/cassandra/schema.cql b/dao/src/main/resources/cassandra/schema.cql
index 79e9655..41e0406 100644
--- a/dao/src/main/resources/cassandra/schema.cql
+++ b/dao/src/main/resources/cassandra/schema.cql
@@ -686,6 +686,7 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_chain_by_tenant_and_sear
 
 CREATE TABLE IF NOT EXISTS  thingsboard.rule_node (
     id uuid,
+    rule_chain_id uuid,
     type text,
     name text,
     debug_mode boolean,
diff --git a/dao/src/main/resources/sql/schema.sql b/dao/src/main/resources/sql/schema.sql
index 08fe7fa..f2458e1 100644
--- a/dao/src/main/resources/sql/schema.sql
+++ b/dao/src/main/resources/sql/schema.sql
@@ -272,6 +272,7 @@ CREATE TABLE IF NOT EXISTS rule_chain (
 
 CREATE TABLE IF NOT EXISTS rule_node (
     id varchar(31) NOT NULL CONSTRAINT rule_node_pkey PRIMARY KEY,
+    rule_chain_id varchar(31),
     additional_info varchar,
     configuration varchar(10000000),
     type varchar(255),
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitionerTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitionerTest.java
index a6ec464..2d3b61f 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitionerTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitionerTest.java
@@ -75,7 +75,7 @@ public class QueuePartitionerTest {
         long clusteredHash = 101L;
         when(partitionRepo.findLastProcessedPartition(nodeId, clusteredHash)).thenReturn(Optional.empty());
         List<Long> actual = queuePartitioner.findUnprocessedPartitions(nodeId, clusteredHash);
-        assertEquals(1011, actual.size());
+        assertEquals(10083, actual.size());
     }
 
 }
\ No newline at end of file
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java
index a86238e..cff4dc9 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java
@@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.util.ReflectionTestUtils;
 import org.thingsboard.server.dao.service.AbstractServiceTest;
 import org.thingsboard.server.dao.service.DaoNoSqlTest;
 import org.thingsboard.server.dao.service.queue.cassandra.MsgAck;
@@ -66,6 +67,7 @@ public class CassandraAckRepositoryTest extends AbstractServiceTest {
 
     @Test
     public void expiredAcksAreNotReturned() throws ExecutionException, InterruptedException {
+        ReflectionTestUtils.setField(ackRepository, "ackQueueTtl", 1);
         UUID msgId = UUIDs.timeBased();
         UUID nodeId = UUIDs.timeBased();
         MsgAck ack = new MsgAck(msgId, nodeId, 30L, 40L);
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java
index a766aa4..fa286aa 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java
@@ -22,7 +22,10 @@ import com.google.common.util.concurrent.ListenableFuture;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.util.ReflectionTestUtils;
 import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.RuleChainId;
+import org.thingsboard.server.common.data.id.RuleNodeId;
 import org.thingsboard.server.common.msg.TbMsg;
 import org.thingsboard.server.common.msg.TbMsgDataType;
 import org.thingsboard.server.common.msg.TbMsgMetaData;
@@ -45,7 +48,8 @@ public class CassandraMsgRepositoryTest extends AbstractServiceTest {
 
     @Test
     public void msgCanBeSavedAndRead() throws ExecutionException, InterruptedException {
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, TbMsgDataType.JSON, "0000");
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, TbMsgDataType.JSON, "0000",
+                new RuleChainId(UUIDs.timeBased()), new RuleNodeId(UUIDs.timeBased()), 0L);
         UUID nodeId = UUIDs.timeBased();
         ListenableFuture<Void> future = msgRepository.save(msg, nodeId, 1L, 1L, 1L);
         future.get();
@@ -55,7 +59,9 @@ public class CassandraMsgRepositoryTest extends AbstractServiceTest {
 
     @Test
     public void expiredMsgsAreNotReturned() throws ExecutionException, InterruptedException {
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, TbMsgDataType.JSON, "0000");
+        ReflectionTestUtils.setField(msgRepository, "msqQueueTtl", 1);
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, TbMsgDataType.JSON, "0000",
+                new RuleChainId(UUIDs.timeBased()), new RuleNodeId(UUIDs.timeBased()), 0L);
         UUID nodeId = UUIDs.timeBased();
         ListenableFuture<Void> future = msgRepository.save(msg, nodeId, 2L, 2L, 2L);
         future.get();
@@ -68,7 +74,8 @@ public class CassandraMsgRepositoryTest extends AbstractServiceTest {
         TbMsgMetaData metaData = new TbMsgMetaData();
         metaData.putValue("key", "value");
         String dataStr = "someContent";
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), metaData, TbMsgDataType.JSON, dataStr);
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), metaData, TbMsgDataType.JSON, dataStr,
+                new RuleChainId(UUIDs.timeBased()), new RuleNodeId(UUIDs.timeBased()), 0L);
         UUID nodeId = UUIDs.timeBased();
         ListenableFuture<Void> future = msgRepository.save(msg, nodeId, 1L, 1L, 1L);
         future.get();
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java
index 1b48790..2ae810a 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java
@@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.util.ReflectionTestUtils;
 import org.thingsboard.server.dao.service.AbstractServiceTest;
 import org.thingsboard.server.dao.service.DaoNoSqlTest;
 
@@ -61,6 +62,7 @@ public class CassandraProcessedPartitionRepositoryTest extends AbstractServiceTe
 
     @Test
     public void expiredPartitionsAreNotReturned() throws ExecutionException, InterruptedException {
+        ReflectionTestUtils.setField(partitionRepository, "partitionsTtl", 1);
         UUID nodeId = UUIDs.timeBased();
         ListenableFuture<Void> future = partitionRepository.partitionProcessed(nodeId, 404L, 10L);
         future.get();
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilterTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilterTest.java
index 3935c9b..caccc85 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilterTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilterTest.java
@@ -33,8 +33,8 @@ public class UnprocessedMsgFilterTest {
     public void acknowledgedMsgsAreFilteredOut() {
         UUID id1 = UUID.randomUUID();
         UUID id2 = UUID.randomUUID();
-        TbMsg msg1 = new TbMsg(id1, "T", null, null, null, null);
-        TbMsg msg2 = new TbMsg(id2, "T", null, null, null, null);
+        TbMsg msg1 = new TbMsg(id1, "T", null, null, null, null, null, null, 0L);
+        TbMsg msg2 = new TbMsg(id2, "T", null, null, null, null, null, null, 0L);
         List<TbMsg> msgs = Lists.newArrayList(msg1, msg2);
         List<MsgAck> acks = Lists.newArrayList(new MsgAck(id2, UUID.randomUUID(), 1L, 1L));
         Collection<TbMsg> actual = msgFilter.filter(msgs, acks);
diff --git a/dao/src/test/resources/application-test.properties b/dao/src/test/resources/application-test.properties
index 42b71f8..dbd8b84 100644
--- a/dao/src/test/resources/application-test.properties
+++ b/dao/src/test/resources/application-test.properties
@@ -28,3 +28,6 @@ redis.connection.host=localhost
 redis.connection.port=6379
 redis.connection.db=0
 redis.connection.password=
+
+rule.queue.type=memory
+rule.queue.max_size=10000
\ No newline at end of file
diff --git a/dao/src/test/resources/nosql-test.properties b/dao/src/test/resources/nosql-test.properties
index 7c02666..e37e228 100644
--- a/dao/src/test/resources/nosql-test.properties
+++ b/dao/src/test/resources/nosql-test.properties
@@ -1,6 +1,6 @@
 database.type=cassandra
 
 cassandra.queue.partitioning=HOURS
-cassandra.queue.ack.ttl=1
-cassandra.queue.msg.ttl=1
-cassandra.queue.partitions.ttl=1
\ No newline at end of file
+cassandra.queue.ack.ttl=3600
+cassandra.queue.msg.ttl=3600
+cassandra.queue.partitions.ttl=3600
\ No newline at end of file
diff --git a/extensions/extension-kafka/src/main/java/org/thingsboard/server/extensions/kafka/action/KafkaActionPayload.java b/extensions/extension-kafka/src/main/java/org/thingsboard/server/extensions/kafka/action/KafkaActionPayload.java
index e8e3381..f251e94 100644
--- a/extensions/extension-kafka/src/main/java/org/thingsboard/server/extensions/kafka/action/KafkaActionPayload.java
+++ b/extensions/extension-kafka/src/main/java/org/thingsboard/server/extensions/kafka/action/KafkaActionPayload.java
@@ -17,7 +17,8 @@ package org.thingsboard.server.extensions.kafka.action;
 
 import lombok.Builder;
 import lombok.Data;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 import java.io.Serializable;
 
@@ -30,5 +31,5 @@ public class KafkaActionPayload implements Serializable {
     private final boolean sync;
 
     private final Integer requestId;
-    private final MsgType msgType;
+    private final SessionMsgType sessionMsgType;
 }
diff --git a/extensions/extension-kafka/src/main/java/org/thingsboard/server/extensions/kafka/action/KafkaPluginAction.java b/extensions/extension-kafka/src/main/java/org/thingsboard/server/extensions/kafka/action/KafkaPluginAction.java
index aa95fe0..d294c86 100644
--- a/extensions/extension-kafka/src/main/java/org/thingsboard/server/extensions/kafka/action/KafkaPluginAction.java
+++ b/extensions/extension-kafka/src/main/java/org/thingsboard/server/extensions/kafka/action/KafkaPluginAction.java
@@ -16,7 +16,7 @@
 package org.thingsboard.server.extensions.kafka.action;
 
 import lombok.extern.slf4j.Slf4j;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg;
 import org.thingsboard.server.extensions.api.component.Action;
 import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
@@ -30,9 +30,9 @@ import java.util.Optional;
 public class KafkaPluginAction extends AbstractTemplatePluginAction<KafkaPluginActionConfiguration> {
 
     @Override
-    protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) {
+    protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, DeviceToDeviceActorMsg msg, FromDeviceRequestMsg payload) {
         KafkaActionPayload.KafkaActionPayloadBuilder builder = KafkaActionPayload.builder();
-        builder.msgType(payload.getMsgType());
+        builder.sessionMsgType(payload.getMsgType());
         builder.requestId(payload.getRequestId());
         builder.sync(configuration.isSync());
         builder.topic(configuration.getTopic());
diff --git a/extensions/extension-kafka/src/main/java/org/thingsboard/server/extensions/kafka/plugin/KafkaMsgHandler.java b/extensions/extension-kafka/src/main/java/org/thingsboard/server/extensions/kafka/plugin/KafkaMsgHandler.java
index 3e87507..4000c22 100644
--- a/extensions/extension-kafka/src/main/java/org/thingsboard/server/extensions/kafka/plugin/KafkaMsgHandler.java
+++ b/extensions/extension-kafka/src/main/java/org/thingsboard/server/extensions/kafka/plugin/KafkaMsgHandler.java
@@ -49,10 +49,10 @@ public class KafkaMsgHandler implements RuleMsgHandler {
                         if (payload.isSync()) {
                             if (metadata != null) {
                                 ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId,
-                                        BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId())));
+                                        BasicStatusCodeResponse.onSuccess(payload.getSessionMsgType(), payload.getRequestId())));
                             } else {
                                 ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId,
-                                        BasicStatusCodeResponse.onError(payload.getMsgType(), payload.getRequestId(), e)));
+                                        BasicStatusCodeResponse.onError(payload.getSessionMsgType(), payload.getRequestId(), e)));
                             }
                         }
                     });
diff --git a/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/action/MqttActionPayload.java b/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/action/MqttActionPayload.java
index cee3ac3..d7a0537 100644
--- a/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/action/MqttActionPayload.java
+++ b/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/action/MqttActionPayload.java
@@ -17,7 +17,7 @@ package org.thingsboard.server.extensions.mqtt.action;
 
 import lombok.Builder;
 import lombok.Data;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 import java.io.Serializable;
 
@@ -30,5 +30,5 @@ public class MqttActionPayload implements Serializable {
     private final String msgBody;
 
     private final Integer requestId;
-    private final MsgType msgType;
+    private final SessionMsgType sessionMsgType;
 }
diff --git a/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/action/MqttPluginAction.java b/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/action/MqttPluginAction.java
index a33fb47..694fa9f 100644
--- a/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/action/MqttPluginAction.java
+++ b/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/action/MqttPluginAction.java
@@ -15,7 +15,7 @@
  */
 package org.thingsboard.server.extensions.mqtt.action;
 
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg;
 import org.thingsboard.server.extensions.api.component.Action;
 import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
@@ -28,10 +28,10 @@ import java.util.Optional;
 public class MqttPluginAction extends AbstractTemplatePluginAction<MqttPluginActionConfiguration> {
 
     @Override
-    protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) {
+    protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, DeviceToDeviceActorMsg msg, FromDeviceRequestMsg payload) {
         MqttActionPayload.MqttActionPayloadBuilder builder = MqttActionPayload.builder();
         builder.sync(configuration.isSync());
-        builder.msgType(payload.getMsgType());
+        builder.sessionMsgType(payload.getMsgType());
         builder.requestId(payload.getRequestId());
         builder.topic(configuration.getTopic());
         builder.msgBody(getMsgBody(ctx, msg));
diff --git a/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/plugin/MqttMsgHandler.java b/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/plugin/MqttMsgHandler.java
index e6ac32a..4083729 100644
--- a/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/plugin/MqttMsgHandler.java
+++ b/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/plugin/MqttMsgHandler.java
@@ -51,7 +51,7 @@ public class MqttMsgHandler implements RuleMsgHandler {
                     log.debug("Message [{}] was successfully delivered to topic [{}]!", msg.toString(), payload.getTopic());
                     if (payload.isSync()) {
                         ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId,
-                                BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId())));
+                                BasicStatusCodeResponse.onSuccess(payload.getSessionMsgType(), payload.getRequestId())));
                     }
                 }
                 @Override
@@ -59,7 +59,7 @@ public class MqttMsgHandler implements RuleMsgHandler {
                     log.warn("Failed to deliver message [{}] to topic [{}]!", msg.toString(), payload.getTopic());
                     if (payload.isSync()) {
                         ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId,
-                                BasicStatusCodeResponse.onError(payload.getMsgType(), payload.getRequestId(), new Exception(e))));
+                                BasicStatusCodeResponse.onError(payload.getSessionMsgType(), payload.getRequestId(), new Exception(e))));
                     }
                 }
             });
diff --git a/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/action/RabbitMqActionPayload.java b/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/action/RabbitMqActionPayload.java
index 9484673..68ba709 100644
--- a/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/action/RabbitMqActionPayload.java
+++ b/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/action/RabbitMqActionPayload.java
@@ -17,7 +17,7 @@ package org.thingsboard.server.extensions.rabbitmq.action;
 
 import lombok.Builder;
 import lombok.Data;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 import java.io.Serializable;
 
@@ -35,5 +35,5 @@ public class RabbitMqActionPayload implements Serializable {
 
     private final boolean sync;
     private final Integer requestId;
-    private final MsgType msgType;
+    private final SessionMsgType sessionMsgType;
 }
diff --git a/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/action/RabbitMqPluginAction.java b/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/action/RabbitMqPluginAction.java
index 90c6679..0ae65b7 100644
--- a/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/action/RabbitMqPluginAction.java
+++ b/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/action/RabbitMqPluginAction.java
@@ -15,7 +15,7 @@
  */
 package org.thingsboard.server.extensions.rabbitmq.action;
 
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg;
 import org.thingsboard.server.extensions.api.component.Action;
 import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
@@ -32,13 +32,13 @@ import java.util.Optional;
 public class RabbitMqPluginAction extends AbstractTemplatePluginAction<RabbitMqPluginActionConfiguration> {
 
     @Override
-    protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) {
+    protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, DeviceToDeviceActorMsg msg, FromDeviceRequestMsg payload) {
         RabbitMqActionPayload.RabbitMqActionPayloadBuilder builder = RabbitMqActionPayload.builder();
         builder.sync(configuration.isSync());
         builder.exchange(configuration.getExchange());
         builder.queueName(configuration.getQueueName());
         builder.messageProperties(configuration.getMessageProperties());
-        builder.msgType(payload.getMsgType());
+        builder.sessionMsgType(payload.getMsgType());
         builder.requestId(payload.getRequestId());
         builder.payload(getMsgBody(ctx, msg));
         return Optional.of(new RabbitMqActionMsg(msg.getTenantId(),
diff --git a/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/plugin/RabbitMqMsgHandler.java b/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/plugin/RabbitMqMsgHandler.java
index 764dd0d..b964ee1 100644
--- a/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/plugin/RabbitMqMsgHandler.java
+++ b/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/plugin/RabbitMqMsgHandler.java
@@ -57,7 +57,7 @@ public class RabbitMqMsgHandler implements RuleMsgHandler {
                     payload.getPayload().getBytes(UTF8));
             if (payload.isSync()) {
                 ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId,
-                        BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId())));
+                        BasicStatusCodeResponse.onSuccess(payload.getSessionMsgType(), payload.getRequestId())));
             }
         } catch (IOException e) {
             throw new RuleException(e.getMessage(), e);
diff --git a/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/action/RestApiCallActionPayload.java b/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/action/RestApiCallActionPayload.java
index 996ef36..8a5a5b7 100644
--- a/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/action/RestApiCallActionPayload.java
+++ b/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/action/RestApiCallActionPayload.java
@@ -19,7 +19,8 @@ import lombok.Builder;
 import lombok.Data;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpStatus;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 import java.io.Serializable;
 
@@ -33,5 +34,5 @@ public class RestApiCallActionPayload implements Serializable {
     private final boolean sync;
 
     private final Integer requestId;
-    private final MsgType msgType;
+    private final SessionMsgType sessionMsgType;
 }
diff --git a/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/action/RestApiCallPluginAction.java b/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/action/RestApiCallPluginAction.java
index eec95d1..6e60078 100644
--- a/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/action/RestApiCallPluginAction.java
+++ b/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/action/RestApiCallPluginAction.java
@@ -18,7 +18,7 @@ package org.thingsboard.server.extensions.rest.action;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpStatus;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg;
 import org.thingsboard.server.extensions.api.component.Action;
 import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
@@ -33,9 +33,9 @@ import java.util.Optional;
 public class RestApiCallPluginAction extends AbstractTemplatePluginAction<RestApiCallPluginActionConfiguration> {
 
     @Override
-    protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) {
+    protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, DeviceToDeviceActorMsg msg, FromDeviceRequestMsg payload) {
         RestApiCallActionPayload.RestApiCallActionPayloadBuilder builder = RestApiCallActionPayload.builder();
-        builder.msgType(payload.getMsgType());
+        builder.sessionMsgType(payload.getMsgType());
         builder.requestId(payload.getRequestId());
         builder.sync(configuration.isSync());
         builder.actionPath(configuration.getActionPath());
diff --git a/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/plugin/RestApiCallMsgHandler.java b/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/plugin/RestApiCallMsgHandler.java
index bc7cc71..7c04a43 100644
--- a/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/plugin/RestApiCallMsgHandler.java
+++ b/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/plugin/RestApiCallMsgHandler.java
@@ -52,7 +52,7 @@ public class RestApiCallMsgHandler implements RuleMsgHandler {
                     String.class);
             if (exchangeResponse.getStatusCode().equals(payload.getExpectedResultCode()) && payload.isSync()) {
                 ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId,
-                        BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId())));
+                        BasicStatusCodeResponse.onSuccess(payload.getSessionMsgType(), payload.getRequestId())));
             } else if(!exchangeResponse.getStatusCode().equals(payload.getExpectedResultCode())) {
                 throw new RuntimeException("Response Status Code '"
                         + exchangeResponse.getStatusCode()
diff --git a/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicActionPayload.java b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicActionPayload.java
index afc0c45..6b7caaf 100644
--- a/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicActionPayload.java
+++ b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicActionPayload.java
@@ -17,7 +17,7 @@ package org.thingsboard.server.extensions.sns.action;
 
 import lombok.Builder;
 import lombok.Data;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 import java.io.Serializable;
 
@@ -32,6 +32,6 @@ public class SnsTopicActionPayload implements Serializable {
     private final String msgBody;
 
     private final Integer requestId;
-    private final MsgType msgType;
+    private final SessionMsgType sessionMsgType;
     private final boolean sync;
 }
diff --git a/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicPluginAction.java b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicPluginAction.java
index 13add7d..c65ed99 100644
--- a/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicPluginAction.java
+++ b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicPluginAction.java
@@ -15,7 +15,7 @@
  */
 package org.thingsboard.server.extensions.sns.action;
 
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg;
 import org.thingsboard.server.extensions.api.component.Action;
 import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
@@ -31,9 +31,9 @@ import java.util.Optional;
 public class SnsTopicPluginAction extends AbstractTemplatePluginAction<SnsTopicPluginActionConfiguration> {
 
     @Override
-    protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) {
+    protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, DeviceToDeviceActorMsg msg, FromDeviceRequestMsg payload) {
         SnsTopicActionPayload.SnsTopicActionPayloadBuilder builder = SnsTopicActionPayload.builder();
-        builder.msgType(payload.getMsgType());
+        builder.sessionMsgType(payload.getMsgType());
         builder.requestId(payload.getRequestId());
         builder.topicArn(configuration.getTopicArn());
         builder.msgBody(getMsgBody(ctx, msg));
diff --git a/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsMessageHandler.java b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsMessageHandler.java
index d84208a..b90bb78 100644
--- a/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsMessageHandler.java
+++ b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsMessageHandler.java
@@ -52,7 +52,7 @@ public class SnsMessageHandler implements RuleMsgHandler {
             sns.publish(publishRequest);
             if (payload.isSync()) {
                 ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId,
-                        BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId())));
+                        BasicStatusCodeResponse.onSuccess(payload.getSessionMsgType(), payload.getRequestId())));
             }
            return;
         }
diff --git a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionPayload.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionPayload.java
index 6c1b40c..deafcc7 100644
--- a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionPayload.java
+++ b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionPayload.java
@@ -17,7 +17,7 @@ package org.thingsboard.server.extensions.sqs.action.fifo;
 
 import lombok.Builder;
 import lombok.Data;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 import java.io.Serializable;
 
@@ -33,7 +33,7 @@ public class SqsFifoQueueActionPayload implements Serializable {
     private final String deviceId;
 
     private final Integer requestId;
-    private final MsgType msgType;
+    private final SessionMsgType sessionMsgType;
     private final boolean sync;
 
 }
diff --git a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginAction.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginAction.java
index e834c6d..29c14e1 100644
--- a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginAction.java
+++ b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginAction.java
@@ -15,15 +15,12 @@
  */
 package org.thingsboard.server.extensions.sqs.action.fifo;
 
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg;
 import org.thingsboard.server.extensions.api.component.Action;
 import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
 import org.thingsboard.server.extensions.api.rules.RuleContext;
 import org.thingsboard.server.extensions.core.action.template.AbstractTemplatePluginAction;
-import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueueActionMsg;
-import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueueActionPayload;
-import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueuePluginActionConfiguration;
 
 import java.util.Optional;
 
@@ -34,9 +31,9 @@ import java.util.Optional;
 public class SqsFifoQueuePluginAction extends AbstractTemplatePluginAction<SqsFifoQueuePluginActionConfiguration> {
 
     @Override
-    protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) {
+    protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, DeviceToDeviceActorMsg msg, FromDeviceRequestMsg payload) {
         SqsFifoQueueActionPayload.SqsFifoQueueActionPayloadBuilder builder = SqsFifoQueueActionPayload.builder();
-        builder.msgType(payload.getMsgType());
+        builder.sessionMsgType(payload.getMsgType());
         builder.requestId(payload.getRequestId());
         builder.queue(configuration.getQueue());
         builder.deviceId(msg.getDeviceId().toString());
diff --git a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionPayload.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionPayload.java
index 3e35562..d6f81d3 100644
--- a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionPayload.java
+++ b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionPayload.java
@@ -17,7 +17,8 @@ package org.thingsboard.server.extensions.sqs.action.standard;
 
 import lombok.Builder;
 import lombok.Data;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 
 import java.io.Serializable;
 
@@ -33,7 +34,7 @@ public class SqsStandardQueueActionPayload implements Serializable {
     private final int delaySeconds;
 
     private final Integer requestId;
-    private final MsgType msgType;
+    private final SessionMsgType sessionMsgType;
     private final boolean sync;
 
 }
diff --git a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginAction.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginAction.java
index 232e263..4ebf7bb 100644
--- a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginAction.java
+++ b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginAction.java
@@ -15,7 +15,7 @@
  */
 package org.thingsboard.server.extensions.sqs.action.standard;
 
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg;
 import org.thingsboard.server.extensions.api.component.Action;
 import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
@@ -31,9 +31,9 @@ import java.util.Optional;
 public class SqsStandardQueuePluginAction extends AbstractTemplatePluginAction<SqsStandardQueuePluginActionConfiguration> {
 
     @Override
-    protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) {
+    protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, DeviceToDeviceActorMsg msg, FromDeviceRequestMsg payload) {
         SqsStandardQueueActionPayload.SqsStandardQueueActionPayloadBuilder builder = SqsStandardQueueActionPayload.builder();
-        builder.msgType(payload.getMsgType());
+        builder.sessionMsgType(payload.getMsgType());
         builder.requestId(payload.getRequestId());
         builder.queue(configuration.getQueue());
         builder.delaySeconds(configuration.getDelaySeconds());
diff --git a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsMessageHandler.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsMessageHandler.java
index d049b57..248996a 100644
--- a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsMessageHandler.java
+++ b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsMessageHandler.java
@@ -65,7 +65,7 @@ public class SqsMessageHandler implements RuleMsgHandler {
         sqs.sendMessage(sendMsgRequest);
         if (payload.isSync()) {
             ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId,
-                    BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId())));
+                    BasicStatusCodeResponse.onSuccess(payload.getSessionMsgType(), payload.getRequestId())));
         }
     }
 
@@ -78,7 +78,7 @@ public class SqsMessageHandler implements RuleMsgHandler {
         sqs.sendMessage(sendMsgRequest);
         if (payload.isSync()) {
             ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId,
-                    BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId())));
+                    BasicStatusCodeResponse.onSuccess(payload.getSessionMsgType(), payload.getRequestId())));
         }
     }
 }
diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceAttributesEventNotificationMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceAttributesEventNotificationMsg.java
index 2fcf559..a470784 100644
--- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceAttributesEventNotificationMsg.java
+++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceAttributesEventNotificationMsg.java
@@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.kv.AttributeKey;
 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
+import org.thingsboard.server.common.msg.MsgType;
 
 import java.util.List;
 import java.util.Set;
@@ -54,4 +55,8 @@ public class DeviceAttributesEventNotificationMsg implements ToDeviceActorNotifi
         return new DeviceAttributesEventNotificationMsg(tenantId, deviceId, keys, null, null, true);
     }
 
+    @Override
+    public MsgType getMsgType() {
+        return MsgType.DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG;
+    }
 }
diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceCredentialsUpdateNotificationMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceCredentialsUpdateNotificationMsg.java
index 937018e..b634a82 100644
--- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceCredentialsUpdateNotificationMsg.java
+++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceCredentialsUpdateNotificationMsg.java
@@ -21,6 +21,7 @@ import lombok.ToString;
 import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.kv.AttributeKey;
+import org.thingsboard.server.common.msg.MsgType;
 
 import java.util.Set;
 
@@ -33,4 +34,8 @@ public class DeviceCredentialsUpdateNotificationMsg implements ToDeviceActorNoti
     private final TenantId tenantId;
     private final DeviceId deviceId;
 
+    @Override
+    public MsgType getMsgType() {
+        return MsgType.DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG;
+    }
 }
diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceNameOrTypeUpdateMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceNameOrTypeUpdateMsg.java
index 9083190..1823e3b 100644
--- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceNameOrTypeUpdateMsg.java
+++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceNameOrTypeUpdateMsg.java
@@ -19,6 +19,7 @@ import lombok.AllArgsConstructor;
 import lombok.Data;
 import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.MsgType;
 
 @Data
 @AllArgsConstructor
@@ -27,4 +28,9 @@ public class DeviceNameOrTypeUpdateMsg implements ToDeviceActorNotificationMsg {
     private final DeviceId deviceId;
     private final String deviceName;
     private final String deviceType;
+
+    @Override
+    public MsgType getMsgType() {
+        return MsgType.DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG;
+    }
 }
diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/ToDeviceActorNotificationMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/ToDeviceActorNotificationMsg.java
index 892d53e..5cd5d82 100644
--- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/ToDeviceActorNotificationMsg.java
+++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/ToDeviceActorNotificationMsg.java
@@ -15,6 +15,7 @@
  */
 package org.thingsboard.server.extensions.api.device;
 
+import org.thingsboard.server.common.msg.TbActorMsg;
 import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
 import org.thingsboard.server.common.msg.aware.TenantAwareMsg;
 
@@ -23,6 +24,6 @@ import java.io.Serializable;
 /**
  * @author Andrew Shvayka
  */
-public interface ToDeviceActorNotificationMsg extends TenantAwareMsg, DeviceAwareMsg, Serializable {
+public interface ToDeviceActorNotificationMsg extends TbActorMsg, TenantAwareMsg, DeviceAwareMsg, Serializable {
 
 }
diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/AbstractPlugin.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/AbstractPlugin.java
index 34f4ce7..6d54f3e 100644
--- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/AbstractPlugin.java
+++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/AbstractPlugin.java
@@ -21,7 +21,7 @@ import org.thingsboard.server.common.msg.cluster.ServerAddress;
 import org.thingsboard.server.extensions.api.plugins.handlers.*;
 import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
 import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
-import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
+import org.thingsboard.server.common.msg.timeout.TimeoutMsg;
 import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
 import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg;
 import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRuleMsgHandler.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRuleMsgHandler.java
index 27b476d..42dbbf9 100644
--- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRuleMsgHandler.java
+++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRuleMsgHandler.java
@@ -18,7 +18,8 @@ package org.thingsboard.server.extensions.api.plugins.handlers;
 import lombok.extern.slf4j.Slf4j;
 import org.thingsboard.server.common.data.id.RuleId;
 import org.thingsboard.server.common.data.id.TenantId;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 import org.thingsboard.server.extensions.api.plugins.PluginContext;
 import org.thingsboard.server.extensions.api.plugins.msg.GetAttributesRequestRuleToPluginMsg;
 import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
@@ -56,8 +57,8 @@ public class DefaultRuleMsgHandler implements RuleMsgHandler {
         msgTypeNotSupported(msg.getPayload().getMsgType());
     }
 
-    private void msgTypeNotSupported(MsgType msgType) {
-        throw new RuntimeException("Not supported msg type: " + msgType + "!");
+    private void msgTypeNotSupported(SessionMsgType sessionMsgType) {
+        throw new RuntimeException("Not supported msg type: " + sessionMsgType + "!");
     }
 
 }
diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/UpdateAttributesRequestRuleToPluginMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/UpdateAttributesRequestRuleToPluginMsg.java
index 991ad8d..a6d40ff 100644
--- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/UpdateAttributesRequestRuleToPluginMsg.java
+++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/UpdateAttributesRequestRuleToPluginMsg.java
@@ -18,13 +18,13 @@ package org.thingsboard.server.extensions.api.plugins.msg;
 import org.thingsboard.server.common.data.id.CustomerId;
 import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.TenantId;
-import org.thingsboard.server.common.msg.core.UpdateAttributesRequest;
+import org.thingsboard.server.common.msg.core.AttributesUpdateRequest;
 
-public class UpdateAttributesRequestRuleToPluginMsg extends AbstractRuleToPluginMsg<UpdateAttributesRequest> {
+public class UpdateAttributesRequestRuleToPluginMsg extends AbstractRuleToPluginMsg<AttributesUpdateRequest> {
 
     private static final long serialVersionUID = 1L;
 
-    public UpdateAttributesRequestRuleToPluginMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, UpdateAttributesRequest payload) {
+    public UpdateAttributesRequestRuleToPluginMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, AttributesUpdateRequest payload) {
         super(tenantId, customerId, deviceId, payload);
     }
 
diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/Plugin.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/Plugin.java
index a41f3c1..20d74c3 100644
--- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/Plugin.java
+++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/Plugin.java
@@ -21,7 +21,7 @@ import org.thingsboard.server.common.msg.cluster.ServerAddress;
 import org.thingsboard.server.extensions.api.component.ConfigurableComponent;
 import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
 import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
-import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
+import org.thingsboard.server.common.msg.timeout.TimeoutMsg;
 import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
 import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg;
 import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginAction.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginAction.java
index 3c19571..69bee5c 100644
--- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginAction.java
+++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginAction.java
@@ -15,7 +15,7 @@
  */
 package org.thingsboard.server.extensions.api.plugins;
 
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.session.ToDeviceMsg;
 import org.thingsboard.server.extensions.api.component.ConfigurableComponent;
 import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg;
@@ -28,7 +28,7 @@ import java.util.Optional;
 
 public interface PluginAction<T> extends ConfigurableComponent<T>, RuleLifecycleComponent {
 
-    Optional<RuleToPluginMsg> convert(RuleContext ctx, ToDeviceActorMsg toDeviceActorMsg, RuleProcessingMetaData deviceMsgMd);
+    Optional<RuleToPluginMsg> convert(RuleContext ctx, DeviceToDeviceActorMsg deviceToDeviceActorMsg, RuleProcessingMetaData deviceMsgMd);
 
     Optional<ToDeviceMsg> convert(PluginToRuleMsg<?> response);
 
diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginContext.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginContext.java
index 37f4c4a..42fd747 100644
--- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginContext.java
+++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginContext.java
@@ -22,8 +22,10 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry;
 import org.thingsboard.server.common.data.kv.TsKvEntry;
 import org.thingsboard.server.common.data.kv.TsKvQuery;
 import org.thingsboard.server.common.data.relation.EntityRelation;
-import org.thingsboard.server.common.data.relation.RelationTypeGroup;
+import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
 import org.thingsboard.server.common.msg.cluster.ServerAddress;
+import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
+import org.thingsboard.server.common.msg.timeout.TimeoutMsg;
 import org.thingsboard.server.extensions.api.plugins.msg.*;
 import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg;
 import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef;
diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleFilter.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleFilter.java
index 9489a53..f0e986e 100644
--- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleFilter.java
+++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleFilter.java
@@ -15,7 +15,7 @@
  */
 package org.thingsboard.server.extensions.api.rules;
 
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.extensions.api.component.ConfigurableComponent;
 
 /**
@@ -23,6 +23,6 @@ import org.thingsboard.server.extensions.api.component.ConfigurableComponent;
  */
 public interface RuleFilter<T> extends ConfigurableComponent<T>, RuleLifecycleComponent {
 
-    boolean filter(RuleContext ctx, ToDeviceActorMsg msg);
+    boolean filter(RuleContext ctx, DeviceToDeviceActorMsg msg);
     
 }
diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleProcessor.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleProcessor.java
index 3fd5e96..5e72dbe 100644
--- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleProcessor.java
+++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleProcessor.java
@@ -15,15 +15,13 @@
  */
 package org.thingsboard.server.extensions.api.rules;
 
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.extensions.api.component.ConfigurableComponent;
 
-import javax.script.ScriptException;
-
 /**
  * @author Andrew Shvayka
  */
 public interface RuleProcessor<T> extends ConfigurableComponent<T>, RuleLifecycleComponent {
 
-    RuleProcessingMetaData process(RuleContext ctx, ToDeviceActorMsg msg) throws RuleException;
+    RuleProcessingMetaData process(RuleContext ctx, DeviceToDeviceActorMsg msg) throws RuleException;
 }
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/mail/SendMailAction.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/mail/SendMailAction.java
index f765a12..0258cf1 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/mail/SendMailAction.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/mail/SendMailAction.java
@@ -20,7 +20,7 @@ import org.apache.velocity.Template;
 import org.apache.velocity.VelocityContext;
 import org.apache.velocity.runtime.parser.ParseException;
 import org.springframework.util.StringUtils;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.session.ToDeviceMsg;
 import org.thingsboard.server.extensions.api.component.Action;
 import org.thingsboard.server.extensions.api.plugins.PluginAction;
@@ -74,7 +74,7 @@ public class SendMailAction extends SimpleRuleLifecycleComponent implements Plug
     }
 
     @Override
-    public Optional<RuleToPluginMsg> convert(RuleContext ctx, ToDeviceActorMsg toDeviceActorMsg, RuleProcessingMetaData metadata) {
+    public Optional<RuleToPluginMsg> convert(RuleContext ctx, DeviceToDeviceActorMsg deviceToDeviceActorMsg, RuleProcessingMetaData metadata) {
         String sendFlag = configuration.getSendFlag();
         if (StringUtils.isEmpty(sendFlag) || (Boolean) metadata.get(sendFlag).orElse(Boolean.FALSE)) {
             VelocityContext context = VelocityUtils.createContext(metadata);
@@ -86,7 +86,7 @@ public class SendMailAction extends SimpleRuleLifecycleComponent implements Plug
             bccTemplate.ifPresent(t -> builder.bcc(VelocityUtils.merge(t, context)));
             subjectTemplate.ifPresent(t -> builder.subject(VelocityUtils.merge(t, context)));
             bodyTemplate.ifPresent(t -> builder.body(VelocityUtils.merge(t, context)));
-            return Optional.of(new SendMailRuleToPluginActionMsg(toDeviceActorMsg.getTenantId(), toDeviceActorMsg.getCustomerId(), toDeviceActorMsg.getDeviceId(),
+            return Optional.of(new SendMailRuleToPluginActionMsg(deviceToDeviceActorMsg.getTenantId(), deviceToDeviceActorMsg.getCustomerId(), deviceToDeviceActorMsg.getDeviceId(),
                     builder.build()));
         } else {
             return Optional.empty();
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/rpc/RpcPluginAction.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/rpc/RpcPluginAction.java
index b195982..8bd9e25 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/rpc/RpcPluginAction.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/rpc/RpcPluginAction.java
@@ -16,9 +16,10 @@
 package org.thingsboard.server.extensions.core.action.rpc;
 
 import org.thingsboard.server.common.msg.core.ToServerRpcRequestMsg;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.session.FromDeviceMsg;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 import org.thingsboard.server.common.msg.session.ToDeviceMsg;
 import org.thingsboard.server.extensions.api.component.Action;
 import org.thingsboard.server.extensions.api.component.EmptyComponentConfiguration;
@@ -41,12 +42,12 @@ public class RpcPluginAction extends SimpleRuleLifecycleComponent implements Plu
     }
 
     @Override
-    public Optional<RuleToPluginMsg> convert(RuleContext ctx, ToDeviceActorMsg toDeviceActorMsg, RuleProcessingMetaData deviceMsgMd) {
-        FromDeviceMsg msg = toDeviceActorMsg.getPayload();
-        if (msg.getMsgType() == MsgType.TO_SERVER_RPC_REQUEST) {
+    public Optional<RuleToPluginMsg> convert(RuleContext ctx, DeviceToDeviceActorMsg deviceToDeviceActorMsg, RuleProcessingMetaData deviceMsgMd) {
+        FromDeviceMsg msg = deviceToDeviceActorMsg.getPayload();
+        if (msg.getMsgType() == SessionMsgType.TO_SERVER_RPC_REQUEST) {
             ToServerRpcRequestMsg payload = (ToServerRpcRequestMsg) msg;
-            return Optional.of(new RpcRequestRuleToPluginMsg(toDeviceActorMsg.getTenantId(), toDeviceActorMsg.getCustomerId(),
-                    toDeviceActorMsg.getDeviceId(), payload));
+            return Optional.of(new RpcRequestRuleToPluginMsg(deviceToDeviceActorMsg.getTenantId(), deviceToDeviceActorMsg.getCustomerId(),
+                    deviceToDeviceActorMsg.getDeviceId(), payload));
         } else {
             return Optional.empty();
         }
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/rpc/ServerSideRpcCallAction.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/rpc/ServerSideRpcCallAction.java
index 80c8efb..4e129db 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/rpc/ServerSideRpcCallAction.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/rpc/ServerSideRpcCallAction.java
@@ -20,7 +20,7 @@ import org.apache.velocity.Template;
 import org.apache.velocity.VelocityContext;
 import org.apache.velocity.runtime.parser.ParseException;
 import org.springframework.util.StringUtils;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.session.ToDeviceMsg;
 import org.thingsboard.server.extensions.api.component.Action;
 import org.thingsboard.server.extensions.api.plugins.PluginAction;
@@ -64,7 +64,7 @@ public class ServerSideRpcCallAction extends SimpleRuleLifecycleComponent implem
     }
 
     @Override
-    public Optional<RuleToPluginMsg> convert(RuleContext ctx, ToDeviceActorMsg toDeviceActorMsg, RuleProcessingMetaData metadata) {
+    public Optional<RuleToPluginMsg> convert(RuleContext ctx, DeviceToDeviceActorMsg deviceToDeviceActorMsg, RuleProcessingMetaData metadata) {
         String sendFlag = configuration.getSendFlag();
         if (StringUtils.isEmpty(sendFlag) || (Boolean) metadata.get(sendFlag).orElse(Boolean.FALSE)) {
             VelocityContext context = VelocityUtils.createContext(metadata);
@@ -77,7 +77,7 @@ public class ServerSideRpcCallAction extends SimpleRuleLifecycleComponent implem
             rpcCallMethodTemplate.ifPresent(t -> builder.rpcCallMethod(VelocityUtils.merge(t, context)));
             rpcCallBodyTemplate.ifPresent(t -> builder.rpcCallBody(VelocityUtils.merge(t, context)));
             builder.rpcCallTimeoutInSec(configuration.getRpcCallTimeoutInSec());
-            return Optional.of(new ServerSideRpcCallRuleToPluginActionMsg(toDeviceActorMsg.getTenantId(), toDeviceActorMsg.getCustomerId(), toDeviceActorMsg.getDeviceId(),
+            return Optional.of(new ServerSideRpcCallRuleToPluginActionMsg(deviceToDeviceActorMsg.getTenantId(), deviceToDeviceActorMsg.getCustomerId(), deviceToDeviceActorMsg.getDeviceId(),
                     builder.build()));
         } else {
             return Optional.empty();
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/telemetry/TelemetryPluginAction.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/telemetry/TelemetryPluginAction.java
index b36aced..b32137e 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/telemetry/TelemetryPluginAction.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/telemetry/TelemetryPluginAction.java
@@ -18,10 +18,10 @@ package org.thingsboard.server.extensions.core.action.telemetry;
 import org.springframework.util.StringUtils;
 import org.thingsboard.server.common.msg.core.GetAttributesRequest;
 import org.thingsboard.server.common.msg.core.TelemetryUploadRequest;
-import org.thingsboard.server.common.msg.core.UpdateAttributesRequest;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.core.AttributesUpdateRequest;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.session.FromDeviceMsg;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 import org.thingsboard.server.common.msg.session.ToDeviceMsg;
 import org.thingsboard.server.extensions.api.component.Action;
 import org.thingsboard.server.extensions.api.plugins.PluginAction;
@@ -50,20 +50,20 @@ public class TelemetryPluginAction extends SimpleRuleLifecycleComponent implemen
     }
 
     @Override
-    public Optional<RuleToPluginMsg> convert(RuleContext ctx, ToDeviceActorMsg toDeviceActorMsg, RuleProcessingMetaData deviceMsgMd) {
-        FromDeviceMsg msg = toDeviceActorMsg.getPayload();
-        if (msg.getMsgType() == MsgType.POST_TELEMETRY_REQUEST) {
+    public Optional<RuleToPluginMsg> convert(RuleContext ctx, DeviceToDeviceActorMsg deviceToDeviceActorMsg, RuleProcessingMetaData deviceMsgMd) {
+        FromDeviceMsg msg = deviceToDeviceActorMsg.getPayload();
+        if (msg.getMsgType() == SessionMsgType.POST_TELEMETRY_REQUEST) {
             TelemetryUploadRequest payload = (TelemetryUploadRequest) msg;
-            return Optional.of(new TelemetryUploadRequestRuleToPluginMsg(toDeviceActorMsg.getTenantId(), toDeviceActorMsg.getCustomerId(),
-                    toDeviceActorMsg.getDeviceId(), payload, ttl));
-        } else if (msg.getMsgType() == MsgType.POST_ATTRIBUTES_REQUEST) {
-            UpdateAttributesRequest payload = (UpdateAttributesRequest) msg;
-            return Optional.of(new UpdateAttributesRequestRuleToPluginMsg(toDeviceActorMsg.getTenantId(), toDeviceActorMsg.getCustomerId(),
-                    toDeviceActorMsg.getDeviceId(), payload));
-        } else if (msg.getMsgType() == MsgType.GET_ATTRIBUTES_REQUEST) {
+            return Optional.of(new TelemetryUploadRequestRuleToPluginMsg(deviceToDeviceActorMsg.getTenantId(), deviceToDeviceActorMsg.getCustomerId(),
+                    deviceToDeviceActorMsg.getDeviceId(), payload, ttl));
+        } else if (msg.getMsgType() == SessionMsgType.POST_ATTRIBUTES_REQUEST) {
+            AttributesUpdateRequest payload = (AttributesUpdateRequest) msg;
+            return Optional.of(new UpdateAttributesRequestRuleToPluginMsg(deviceToDeviceActorMsg.getTenantId(), deviceToDeviceActorMsg.getCustomerId(),
+                    deviceToDeviceActorMsg.getDeviceId(), payload));
+        } else if (msg.getMsgType() == SessionMsgType.GET_ATTRIBUTES_REQUEST) {
             GetAttributesRequest payload = (GetAttributesRequest) msg;
-            return Optional.of(new GetAttributesRequestRuleToPluginMsg(toDeviceActorMsg.getTenantId(), toDeviceActorMsg.getCustomerId(),
-                    toDeviceActorMsg.getDeviceId(), payload));
+            return Optional.of(new GetAttributesRequestRuleToPluginMsg(deviceToDeviceActorMsg.getTenantId(), deviceToDeviceActorMsg.getCustomerId(),
+                    deviceToDeviceActorMsg.getDeviceId(), payload));
         } else {
             return Optional.empty();
         }
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/template/AbstractTemplatePluginAction.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/template/AbstractTemplatePluginAction.java
index 6d7f75a..82efdc5 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/template/AbstractTemplatePluginAction.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/template/AbstractTemplatePluginAction.java
@@ -19,7 +19,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.velocity.Template;
 import org.apache.velocity.VelocityContext;
 import org.apache.velocity.runtime.parser.ParseException;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg;
 import org.thingsboard.server.common.msg.session.ToDeviceMsg;
 import org.thingsboard.server.extensions.api.plugins.PluginAction;
@@ -52,7 +52,7 @@ public abstract class AbstractTemplatePluginAction<T extends TemplateActionConfi
     }
 
     @Override
-    public Optional<RuleToPluginMsg> convert(RuleContext ctx, ToDeviceActorMsg msg, RuleProcessingMetaData deviceMsgMd) {
+    public Optional<RuleToPluginMsg> convert(RuleContext ctx, DeviceToDeviceActorMsg msg, RuleProcessingMetaData deviceMsgMd) {
         FromDeviceRequestMsg payload;
         if (msg.getPayload() instanceof FromDeviceRequestMsg) {
             payload = (FromDeviceRequestMsg) msg.getPayload();
@@ -70,14 +70,14 @@ public abstract class AbstractTemplatePluginAction<T extends TemplateActionConfi
         return Optional.empty();
     }
 
-    protected String getMsgBody(RuleContext ctx, ToDeviceActorMsg msg) {
+    protected String getMsgBody(RuleContext ctx, DeviceToDeviceActorMsg msg) {
         log.trace("Creating context for: {} and payload {}", ctx.getDeviceMetaData(), msg.getPayload());
         VelocityContext context = VelocityUtils.createContext(ctx.getDeviceMetaData(), msg.getPayload());
         return VelocityUtils.merge(template, context);
     }
 
     abstract protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx,
-                                                                         ToDeviceActorMsg msg,
+                                                                         DeviceToDeviceActorMsg msg,
                                                                          FromDeviceRequestMsg payload);
 
     @Override
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/BasicJsFilter.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/BasicJsFilter.java
index 9c092da..469c76a 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/BasicJsFilter.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/BasicJsFilter.java
@@ -16,8 +16,7 @@
 package org.thingsboard.server.extensions.core.filter;
 
 import lombok.extern.slf4j.Slf4j;
-import org.thingsboard.server.common.data.kv.KvEntry;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.extensions.api.rules.RuleContext;
 import org.thingsboard.server.extensions.api.rules.RuleFilter;
 
@@ -39,7 +38,7 @@ public abstract class BasicJsFilter implements RuleFilter<JsFilterConfiguration>
     }
 
     @Override
-    public boolean filter(RuleContext ctx, ToDeviceActorMsg msg) {
+    public boolean filter(RuleContext ctx, DeviceToDeviceActorMsg msg) {
         try {
             return doFilter(ctx, msg);
         } catch (ScriptException e) {
@@ -48,7 +47,7 @@ public abstract class BasicJsFilter implements RuleFilter<JsFilterConfiguration>
         }
     }
 
-    protected abstract boolean doFilter(RuleContext ctx, ToDeviceActorMsg msg) throws ScriptException;
+    protected abstract boolean doFilter(RuleContext ctx, DeviceToDeviceActorMsg msg) throws ScriptException;
 
     @Override
     public void resume() {
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceAttributesFilter.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceAttributesFilter.java
index db7f8c2..5865c1d 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceAttributesFilter.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceAttributesFilter.java
@@ -16,8 +16,8 @@
 package org.thingsboard.server.extensions.core.filter;
 
 import lombok.extern.slf4j.Slf4j;
-import org.thingsboard.server.common.msg.core.UpdateAttributesRequest;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.core.AttributesUpdateRequest;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.session.FromDeviceMsg;
 import org.thingsboard.server.extensions.api.component.Filter;
 import org.thingsboard.server.extensions.api.device.DeviceAttributes;
@@ -34,7 +34,7 @@ import javax.script.ScriptException;
 public class DeviceAttributesFilter extends BasicJsFilter {
 
     @Override
-    protected boolean doFilter(RuleContext ctx, ToDeviceActorMsg msg) throws ScriptException {
+    protected boolean doFilter(RuleContext ctx, DeviceToDeviceActorMsg msg) throws ScriptException {
         return evaluator.execute(toBindings(ctx.getDeviceMetaData().getDeviceAttributes(), msg != null ? msg.getPayload() : null));
     }
 
@@ -44,7 +44,7 @@ public class DeviceAttributesFilter extends BasicJsFilter {
         if (msg != null) {
             switch (msg.getMsgType()) {
                 case POST_ATTRIBUTES_REQUEST:
-                    bindings = NashornJsEvaluator.updateBindings(bindings, (UpdateAttributesRequest) msg);
+                    bindings = NashornJsEvaluator.updateBindings(bindings, (AttributesUpdateRequest) msg);
                     break;
                 default:
                     break;
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceTelemetryFilter.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceTelemetryFilter.java
index cd8f87c..7c2ede5 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceTelemetryFilter.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceTelemetryFilter.java
@@ -18,7 +18,7 @@ package org.thingsboard.server.extensions.core.filter;
 import lombok.extern.slf4j.Slf4j;
 import org.thingsboard.server.common.data.kv.KvEntry;
 import org.thingsboard.server.common.msg.core.TelemetryUploadRequest;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.session.FromDeviceMsg;
 import org.thingsboard.server.extensions.api.component.Filter;
 import org.thingsboard.server.extensions.api.rules.RuleContext;
@@ -34,7 +34,7 @@ import java.util.List;
 public class DeviceTelemetryFilter extends BasicJsFilter {
 
     @Override
-    protected boolean doFilter(RuleContext ctx, ToDeviceActorMsg msg) throws ScriptException {
+    protected boolean doFilter(RuleContext ctx, DeviceToDeviceActorMsg msg) throws ScriptException {
         FromDeviceMsg deviceMsg = msg.getPayload();
         if (deviceMsg instanceof TelemetryUploadRequest) {
             TelemetryUploadRequest telemetryMsg = (TelemetryUploadRequest) deviceMsg;
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceTypeFilter.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceTypeFilter.java
index eb00762..406c9fd 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceTypeFilter.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceTypeFilter.java
@@ -16,8 +16,7 @@
 package org.thingsboard.server.extensions.core.filter;
 
 import lombok.extern.slf4j.Slf4j;
-import org.thingsboard.server.common.msg.core.ToServerRpcRequestMsg;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.extensions.api.component.Filter;
 import org.thingsboard.server.extensions.api.rules.RuleContext;
 import org.thingsboard.server.extensions.api.rules.RuleFilter;
@@ -27,8 +26,6 @@ import java.util.Arrays;
 import java.util.Set;
 import java.util.stream.Collectors;
 
-import static org.thingsboard.server.common.msg.session.MsgType.TO_SERVER_RPC_REQUEST;
-
 /**
  * @author Andrew Shvayka
  */
@@ -46,7 +43,7 @@ public class DeviceTypeFilter extends SimpleRuleLifecycleComponent implements Ru
     }
 
     @Override
-    public boolean filter(RuleContext ctx, ToDeviceActorMsg msg) {
+    public boolean filter(RuleContext ctx, DeviceToDeviceActorMsg msg) {
         return deviceTypes.contains(ctx.getDeviceMetaData().getDeviceType());
     }
 }
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MethodNameFilter.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MethodNameFilter.java
index e5672b8..daacfec 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MethodNameFilter.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MethodNameFilter.java
@@ -17,7 +17,7 @@ package org.thingsboard.server.extensions.core.filter;
 
 import lombok.extern.slf4j.Slf4j;
 import org.thingsboard.server.common.msg.core.ToServerRpcRequestMsg;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.extensions.api.component.Filter;
 import org.thingsboard.server.extensions.api.rules.RuleContext;
 import org.thingsboard.server.extensions.api.rules.RuleFilter;
@@ -27,7 +27,7 @@ import java.util.Arrays;
 import java.util.Set;
 import java.util.stream.Collectors;
 
-import static org.thingsboard.server.common.msg.session.MsgType.TO_SERVER_RPC_REQUEST;
+import static org.thingsboard.server.common.msg.session.SessionMsgType.TO_SERVER_RPC_REQUEST;
 
 /**
  * @author Andrew Shvayka
@@ -46,7 +46,7 @@ public class MethodNameFilter extends SimpleRuleLifecycleComponent implements Ru
     }
 
     @Override
-    public boolean filter(RuleContext ctx, ToDeviceActorMsg msg) {
+    public boolean filter(RuleContext ctx, DeviceToDeviceActorMsg msg) {
         if (msg.getPayload().getMsgType() == TO_SERVER_RPC_REQUEST) {
             return methods.contains(((ToServerRpcRequestMsg) msg.getPayload()).getMethod());
         } else {
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MsgTypeFilter.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MsgTypeFilter.java
index ef0094e..e40abf8 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MsgTypeFilter.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MsgTypeFilter.java
@@ -16,8 +16,9 @@
 package org.thingsboard.server.extensions.core.filter;
 
 import lombok.extern.slf4j.Slf4j;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 import org.thingsboard.server.extensions.api.component.Filter;
 import org.thingsboard.server.extensions.api.rules.RuleContext;
 import org.thingsboard.server.extensions.api.rules.RuleFilter;
@@ -35,30 +36,30 @@ import java.util.stream.Collectors;
 @Slf4j
 public class MsgTypeFilter extends SimpleRuleLifecycleComponent implements RuleFilter<MsgTypeFilterConfiguration> {
 
-    private List<MsgType> msgTypes;
+    private List<SessionMsgType> sessionMsgTypes;
 
     @Override
     public void init(MsgTypeFilterConfiguration configuration) {
-        msgTypes = Arrays.stream(configuration.getMessageTypes()).map(type -> {
+        sessionMsgTypes = Arrays.stream(configuration.getMessageTypes()).map(type -> {
             switch (type) {
                 case "GET_ATTRIBUTES":
-                    return MsgType.GET_ATTRIBUTES_REQUEST;
+                    return SessionMsgType.GET_ATTRIBUTES_REQUEST;
                 case "POST_ATTRIBUTES":
-                    return MsgType.POST_ATTRIBUTES_REQUEST;
+                    return SessionMsgType.POST_ATTRIBUTES_REQUEST;
                 case "POST_TELEMETRY":
-                    return MsgType.POST_TELEMETRY_REQUEST;
+                    return SessionMsgType.POST_TELEMETRY_REQUEST;
                 case "RPC_REQUEST":
-                    return MsgType.TO_SERVER_RPC_REQUEST;
+                    return SessionMsgType.TO_SERVER_RPC_REQUEST;
                 default:
-                    throw new InvalidParameterException("Can't map " + type + " to " + MsgType.class.getName() + "!");
+                    throw new InvalidParameterException("Can't map " + type + " to " + SessionMsgType.class.getName() + "!");
             }
         }).collect(Collectors.toList());
     }
 
     @Override
-    public boolean filter(RuleContext ctx, ToDeviceActorMsg msg) {
-        for (MsgType msgType : msgTypes) {
-            if (msgType == msg.getPayload().getMsgType()) {
+    public boolean filter(RuleContext ctx, DeviceToDeviceActorMsg msg) {
+        for (SessionMsgType sessionMsgType : sessionMsgTypes) {
+            if (sessionMsgType == msg.getPayload().getMsgType()) {
                 return true;
             }
         }
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/NashornJsEvaluator.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/NashornJsEvaluator.java
index 20cb397..6e9fe3f 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/NashornJsEvaluator.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/NashornJsEvaluator.java
@@ -19,7 +19,7 @@ import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
 import lombok.extern.slf4j.Slf4j;
 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
 import org.thingsboard.server.common.data.kv.KvEntry;
-import org.thingsboard.server.common.msg.core.UpdateAttributesRequest;
+import org.thingsboard.server.common.msg.core.AttributesUpdateRequest;
 import org.thingsboard.server.extensions.api.device.DeviceAttributes;
 
 import javax.script.*;
@@ -69,7 +69,7 @@ public class NashornJsEvaluator {
         return bindings;
     }
 
-    public static Bindings updateBindings(Bindings bindings, UpdateAttributesRequest msg) {
+    public static Bindings updateBindings(Bindings bindings, AttributesUpdateRequest msg) {
         Map<String, Object> attrMap = (Map<String, Object>) bindings.get(CLIENT_SIDE);
         for (AttributeKvEntry attr : msg.getAttributes()) {
             if (!CLIENT_SIDE.equalsIgnoreCase(attr.getKey()) && !SERVER_SIDE.equalsIgnoreCase(attr.getKey())
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/messaging/DeviceMessagingRuleMsgHandler.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/messaging/DeviceMessagingRuleMsgHandler.java
index bd18b8b..5ef0650 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/messaging/DeviceMessagingRuleMsgHandler.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/messaging/DeviceMessagingRuleMsgHandler.java
@@ -26,15 +26,24 @@ import org.thingsboard.server.common.data.id.CustomerId;
 import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.RuleId;
 import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
 import org.thingsboard.server.common.msg.core.ToServerRpcRequestMsg;
 import org.thingsboard.server.common.msg.core.ToServerRpcResponseMsg;
+import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
 import org.thingsboard.server.extensions.api.plugins.PluginCallback;
 import org.thingsboard.server.extensions.api.plugins.PluginContext;
 import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler;
-import org.thingsboard.server.extensions.api.plugins.msg.*;
+import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
+import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
+import org.thingsboard.server.extensions.api.plugins.msg.RpcResponsePluginToRuleMsg;
+import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
 import org.thingsboard.server.extensions.api.rules.RuleException;
 
-import java.util.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
 
 /**
  * @author Andrew Shvayka
@@ -152,7 +161,7 @@ public class DeviceMessagingRuleMsgHandler implements RuleMsgHandler {
                         pendingMsgs.put(uid, requestMd);
                         log.trace("[{}] Forwarding {} to [{}]", uid, params, targetDeviceId);
                         ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(ON_MSG_METHOD_NAME, GSON.toJson(params.get("body")));
-                        ctx.sendRpcRequest(new ToDeviceRpcRequest(uid, null, targetDevice.getTenantId(), targetDeviceId, oneWay, System.currentTimeMillis() + timeout, requestBody));
+                        ctx.sendRpcRequest(new ToDeviceRpcRequest(uid, targetDevice.getTenantId(), targetDeviceId, oneWay, System.currentTimeMillis() + timeout, requestBody));
                     } else {
                         replyWithError(ctx, requestMd, RpcError.FORBIDDEN);
                     }
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRuleMsgHandler.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRuleMsgHandler.java
index 9cb67fd..014d149 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRuleMsgHandler.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRuleMsgHandler.java
@@ -28,7 +28,7 @@ import org.thingsboard.server.common.msg.core.BasicGetAttributesResponse;
 import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse;
 import org.thingsboard.server.common.msg.core.GetAttributesRequest;
 import org.thingsboard.server.common.msg.core.TelemetryUploadRequest;
-import org.thingsboard.server.common.msg.core.UpdateAttributesRequest;
+import org.thingsboard.server.common.msg.core.AttributesUpdateRequest;
 import org.thingsboard.server.common.msg.kv.BasicAttributeKVMsg;
 import org.thingsboard.server.extensions.api.plugins.PluginCallback;
 import org.thingsboard.server.extensions.api.plugins.PluginContext;
@@ -132,7 +132,7 @@ public class TelemetryRuleMsgHandler extends DefaultRuleMsgHandler {
 
     @Override
     public void handleUpdateAttributesRequest(PluginContext ctx, TenantId tenantId, RuleId ruleId, UpdateAttributesRequestRuleToPluginMsg msg) {
-        UpdateAttributesRequest request = msg.getPayload();
+        AttributesUpdateRequest request = msg.getPayload();
         ctx.saveAttributes(msg.getTenantId(), msg.getDeviceId(), DataConstants.CLIENT_SCOPE, request.getAttributes().stream().collect(Collectors.toList()),
                 new PluginCallback<Void>() {
                     @Override
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/processor/AlarmDeduplicationProcessor.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/processor/AlarmDeduplicationProcessor.java
index f7ad274..508ab6f 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/processor/AlarmDeduplicationProcessor.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/processor/AlarmDeduplicationProcessor.java
@@ -22,7 +22,7 @@ import org.apache.velocity.VelocityContext;
 import org.apache.velocity.runtime.parser.ParseException;
 import org.thingsboard.server.common.data.DataConstants;
 import org.thingsboard.server.common.data.Event;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.extensions.api.component.Processor;
 import org.thingsboard.server.extensions.api.rules.*;
 import org.thingsboard.server.extensions.core.utils.VelocityUtils;
@@ -57,7 +57,7 @@ public class AlarmDeduplicationProcessor extends SimpleRuleLifecycleComponent
     }
 
     @Override
-    public RuleProcessingMetaData process(RuleContext ctx, ToDeviceActorMsg msg) throws RuleException {
+    public RuleProcessingMetaData process(RuleContext ctx, DeviceToDeviceActorMsg msg) throws RuleException {
         RuleProcessingMetaData md = new RuleProcessingMetaData();
         VelocityContext context = VelocityUtils.createContext(ctx.getDeviceMetaData(), msg.getPayload());
         String alarmId = VelocityUtils.merge(alarmIdTemplate, context);
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/processor/AlarmProcessor.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/processor/AlarmProcessor.java
index b4e3440..df6e45a 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/processor/AlarmProcessor.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/processor/AlarmProcessor.java
@@ -26,8 +26,8 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity;
 import org.thingsboard.server.common.data.alarm.AlarmStatus;
 import org.thingsboard.server.common.data.kv.KvEntry;
 import org.thingsboard.server.common.msg.core.TelemetryUploadRequest;
-import org.thingsboard.server.common.msg.core.UpdateAttributesRequest;
-import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
+import org.thingsboard.server.common.msg.core.AttributesUpdateRequest;
+import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
 import org.thingsboard.server.common.msg.session.FromDeviceMsg;
 import org.thingsboard.server.extensions.api.component.Processor;
 import org.thingsboard.server.extensions.api.rules.RuleContext;
@@ -102,7 +102,7 @@ public class AlarmProcessor implements RuleProcessor<AlarmProcessorConfiguration
     }
 
     @Override
-    public RuleProcessingMetaData process(RuleContext ctx, ToDeviceActorMsg wrapper) throws RuleException {
+    public RuleProcessingMetaData process(RuleContext ctx, DeviceToDeviceActorMsg wrapper) throws RuleException {
         RuleProcessingMetaData md = new RuleProcessingMetaData();
 
         FromDeviceMsg msg = wrapper.getPayload();
@@ -219,7 +219,7 @@ public class AlarmProcessor implements RuleProcessor<AlarmProcessorConfiguration
         if (msg != null) {
             switch (msg.getMsgType()) {
                 case POST_ATTRIBUTES_REQUEST:
-                    bindings = NashornJsEvaluator.updateBindings(bindings, (UpdateAttributesRequest) msg);
+                    bindings = NashornJsEvaluator.updateBindings(bindings, (AttributesUpdateRequest) msg);
                     break;
                 case POST_TELEMETRY_REQUEST:
                     TelemetryUploadRequest telemetryMsg = (TelemetryUploadRequest) msg;
diff --git a/extensions-core/src/main/resources/MsgTypeFilterDescriptor.json b/extensions-core/src/main/resources/MsgTypeFilterDescriptor.json
index a0339c9..6f40df9 100644
--- a/extensions-core/src/main/resources/MsgTypeFilterDescriptor.json
+++ b/extensions-core/src/main/resources/MsgTypeFilterDescriptor.json
@@ -9,19 +9,19 @@
         "minItems" : 1,
         "items": [
           {
-            "value": "GET_ATTRIBUTES",
+            "value": "GET_ATTRIBUTES_REQUEST",
             "label": "Get attributes"
           },
           {
-            "value": "POST_ATTRIBUTES",
+            "value": "POST_ATTRIBUTES_REQUEST",
             "label": "Post attributes"
           },
           {
-            "value": "POST_TELEMETRY",
+            "value": "POST_TELEMETRY_REQUEST",
             "label": "Post telemetry"
           },
           {
-            "value": "RPC_REQUEST",
+            "value": "RPC_REQUEST_REQUEST",
             "label": "RPC Request"
           }
         ],

pom.xml 6(+6 -0)

diff --git a/pom.xml b/pom.xml
index dc58627..67d48b2 100755
--- a/pom.xml
+++ b/pom.xml
@@ -79,6 +79,7 @@
         <dbunit.version>2.5.3</dbunit.version>
         <spring-test-dbunit.version>1.2.1</spring-test-dbunit.version>
         <postgresql.driver.version>9.4.1211</postgresql.driver.version>
+        <netty-mqtt-client.version>2.0.0TB</netty-mqtt-client.version>
         <sonar.exclusions>org/thingsboard/server/gen/**/*,
             org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/*
         </sonar.exclusions>
@@ -819,6 +820,11 @@
                 <scope>provided</scope>
             </dependency>
             <dependency>
+                <groupId>nl.jk5.netty-mqtt</groupId>
+                <artifactId>netty-mqtt</artifactId>
+                <version>${netty-mqtt-client.version}</version>
+            </dependency>
+            <dependency>
                 <groupId>org.elasticsearch.client</groupId>
                 <artifactId>rest</artifactId>
                 <version>${elasticsearch.version}</version>
diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EmptyNodeConfiguration.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EmptyNodeConfiguration.java
new file mode 100644
index 0000000..9553b13
--- /dev/null
+++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EmptyNodeConfiguration.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.api;
+
+import lombok.Data;
+import org.thingsboard.rule.engine.api.NodeConfiguration;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@Data
+public class EmptyNodeConfiguration implements NodeConfiguration<EmptyNodeConfiguration> {
+
+    private int version;
+
+    @Override
+    public EmptyNodeConfiguration defaultConfiguration() {
+        EmptyNodeConfiguration configuration = new EmptyNodeConfiguration();
+        return configuration;
+    }
+}
diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcRequest.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcRequest.java
new file mode 100644
index 0000000..5ea481a
--- /dev/null
+++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcRequest.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.api;
+
+import lombok.Builder;
+import lombok.Data;
+import org.thingsboard.server.common.data.id.DeviceId;
+
+/**
+ * Created by ashvayka on 02.04.18.
+ */
+@Data
+@Builder
+public final class RuleEngineDeviceRpcRequest {
+
+    private final DeviceId deviceId;
+    private final int requestId;
+    private final boolean oneway;
+    private final String method;
+    private final String body;
+    private final long timeout;
+
+}
diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcResponse.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcResponse.java
new file mode 100644
index 0000000..f678317
--- /dev/null
+++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcResponse.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.api;
+
+import lombok.Builder;
+import lombok.Data;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
+
+import java.util.Optional;
+
+/**
+ * Created by ashvayka on 02.04.18.
+ */
+@Data
+@Builder
+public final class RuleEngineDeviceRpcResponse {
+
+    private final DeviceId deviceId;
+    private final int requestId;
+    private final Optional<String> response;
+    private final Optional<RpcError> error;
+
+}
diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java
index 1ba18cd..aa57a02 100644
--- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java
+++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java
@@ -33,4 +33,12 @@ public interface RuleEngineTelemetryService {
 
     void saveAndNotify(EntityId entityId, String scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback);
 
+    void saveAttrAndNotify(EntityId entityId, String scope, String key, long value, FutureCallback<Void> callback);
+
+    void saveAttrAndNotify(EntityId entityId, String scope, String key, String value, FutureCallback<Void> callback);
+
+    void saveAttrAndNotify(EntityId entityId, String scope, String key, double value, FutureCallback<Void> callback);
+
+    void saveAttrAndNotify(EntityId entityId, String scope, String key, boolean value, FutureCallback<Void> callback);
+
 }
diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
index 6038e6d..962f8aa 100644
--- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
+++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
@@ -15,10 +15,12 @@
  */
 package org.thingsboard.rule.engine.api;
 
+import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.id.RuleNodeId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.rule.RuleNode;
 import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.TbMsgMetaData;
 import org.thingsboard.server.common.msg.cluster.ServerAddress;
 import org.thingsboard.server.dao.alarm.AlarmService;
 import org.thingsboard.server.dao.asset.AssetService;
@@ -42,6 +44,8 @@ public interface TbContext {
 
     void tellNext(TbMsg msg, String relationType);
 
+    void tellNext(TbMsg msg, String relationType, Throwable th);
+
     void tellNext(TbMsg msg, Set<String> relationTypes);
 
     void tellSelf(TbMsg msg, long delayMs);
@@ -58,6 +62,10 @@ public interface TbContext {
 
     void updateSelf(RuleNode self);
 
+    TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data);
+
+    TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data);
+
     RuleNodeId getSelfId();
 
     TenantId getTenantId();
@@ -78,6 +86,8 @@ public interface TbContext {
 
     RuleChainService getRuleChainService();
 
+    RuleEngineRpcService getRpcService();
+
     RuleEngineTelemetryService getTelemetryService();
 
     TimeseriesService getTimeseriesService();
@@ -90,6 +100,8 @@ public interface TbContext {
 
     ListeningExecutor getDbCallbackExecutor();
 
+    ListeningExecutor getExternalCallExecutor();
+
     MailService getMailService();
 
     ScriptEngine createJsScriptEngine(String script, String functionName, String... argNames);
diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml
index 419b5f6..a38df28 100644
--- a/rule-engine/rule-engine-components/pom.xml
+++ b/rule-engine/rule-engine-components/pom.xml
@@ -35,6 +35,7 @@
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <main.dir>${basedir}/../..</main.dir>
+        <aws.sdk.version>1.11.323</aws.sdk.version>
     </properties>
 
     <dependencies>
@@ -82,6 +83,32 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.kafka</groupId>
+            <artifactId>kafka_2.10</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.amazonaws</groupId>
+            <artifactId>aws-java-sdk-sns</artifactId>
+            <version>${aws.sdk.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.rabbitmq</groupId>
+            <artifactId>amqp-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>nl.jk5.netty-mqtt</groupId>
+            <artifactId>netty-mqtt</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcpkix-jdk15on</artifactId>
+        </dependency>
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <version>${junit.version}</version>
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmNode.java
index a242093..566a125 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmNode.java
@@ -15,7 +15,6 @@
  */
 package org.thingsboard.rule.engine.action;
 
-import com.datastax.driver.core.utils.UUIDs;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.base.Function;
@@ -32,8 +31,6 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
 import org.thingsboard.server.common.msg.TbMsg;
 import org.thingsboard.server.common.msg.TbMsgMetaData;
 
-import java.util.concurrent.ExecutorService;
-
 import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
 
 @Slf4j
@@ -91,11 +88,11 @@ public class TbAlarmNode implements TbNode {
                     if (alarmResult.alarm == null) {
                         ctx.tellNext(msg, "False");
                     } else if (alarmResult.isCreated) {
-                        ctx.tellNext(toAlarmMsg(alarmResult, msg), "Created");
+                        ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Created");
                     } else if (alarmResult.isUpdated) {
-                        ctx.tellNext(toAlarmMsg(alarmResult, msg), "Updated");
+                        ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Updated");
                     } else if (alarmResult.isCleared) {
-                        ctx.tellNext(toAlarmMsg(alarmResult, msg), "Cleared");
+                        ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Cleared");
                     }
                 },
                 t -> ctx.tellError(msg, t));
@@ -176,7 +173,7 @@ public class TbAlarmNode implements TbNode {
         return ctx.getJsExecutor().executeAsync(() -> buildDetailsJsEngine.executeJson(msg));
     }
 
-    private TbMsg toAlarmMsg(AlarmResult alarmResult, TbMsg originalMsg) {
+    private TbMsg toAlarmMsg(TbContext ctx, AlarmResult alarmResult, TbMsg originalMsg) {
         JsonNode jsonNodes = mapper.valueToTree(alarmResult.alarm);
         String data = jsonNodes.toString();
         TbMsgMetaData metaData = originalMsg.getMetaData().copy();
@@ -187,7 +184,7 @@ public class TbAlarmNode implements TbNode {
         } else if (alarmResult.isCleared) {
             metaData.putValue(IS_CLEARED_ALARM, Boolean.TRUE.toString());
         }
-        return new TbMsg(UUIDs.timeBased(), "ALARM", originalMsg.getOriginator(), metaData, data);
+        return ctx.transformMsg(originalMsg, "ALARM", originalMsg.getOriginator(), metaData, data);
     }
 
 
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java
index aafb7f1..46c8564 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java
@@ -26,7 +26,7 @@ public class TbLogNodeConfiguration implements NodeConfiguration {
     @Override
     public TbLogNodeConfiguration defaultConfiguration() {
         TbLogNodeConfiguration configuration = new TbLogNodeConfiguration();
-        configuration.setJsScript("return 'incoming message = ' + msg + meta;");
+        configuration.setJsScript("return 'Incoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);");
         return configuration;
     }
 }
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java
new file mode 100644
index 0000000..428621c
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java
@@ -0,0 +1,119 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.aws.sns;
+
+import com.amazonaws.auth.AWSCredentials;
+import com.amazonaws.auth.AWSStaticCredentialsProvider;
+import com.amazonaws.auth.BasicAWSCredentials;
+import com.amazonaws.regions.Region;
+import com.amazonaws.regions.Regions;
+import com.amazonaws.services.sns.AmazonSNS;
+import com.amazonaws.services.sns.AmazonSNSClient;
+import com.amazonaws.services.sns.model.PublishRequest;
+import com.amazonaws.services.sns.model.PublishResult;
+import com.google.common.util.concurrent.ListenableFuture;
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.rule.engine.TbNodeUtils;
+import org.thingsboard.rule.engine.api.*;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.TbMsgMetaData;
+
+import java.util.concurrent.ExecutionException;
+
+import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
+
+@Slf4j
+@RuleNode(
+        type = ComponentType.ACTION,
+        name = "aws sns",
+        configClazz = TbSnsNodeConfiguration.class,
+        nodeDescription = "Publish messages to AWS SNS",
+        nodeDetails = "Expects messages with any message type. Will publish message to AWS SNS topic.",
+        uiResources = {"static/rulenode/rulenode-core-config.js"},
+        configDirective = "tbActionNodeSnsConfig"
+)
+public class TbSnsNode implements TbNode {
+
+    private static final String MESSAGE_ID = "messageId";
+    private static final String REQUEST_ID = "requestId";
+    private static final String ERROR = "error";
+
+    private TbSnsNodeConfiguration config;
+    private AmazonSNS snsClient;
+
+    @Override
+    public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
+        this.config = TbNodeUtils.convert(configuration, TbSnsNodeConfiguration.class);
+        AWSCredentials awsCredentials = new BasicAWSCredentials(this.config.getAccessKeyId(), this.config.getSecretAccessKey());
+        AWSStaticCredentialsProvider credProvider = new AWSStaticCredentialsProvider(awsCredentials);
+        try {
+            this.snsClient = AmazonSNSClient.builder()
+                    .withCredentials(credProvider)
+                    .withRegion(this.config.getRegion())
+                    .build();
+        } catch (Exception e) {
+            throw new TbNodeException(e);
+        }
+    }
+
+    @Override
+    public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
+        withCallback(publishMessageAsync(ctx, msg),
+                m -> ctx.tellNext(m, TbRelationTypes.SUCCESS),
+                t -> {
+                    TbMsg next = processException(ctx, msg, t);
+                    ctx.tellNext(next, TbRelationTypes.FAILURE, t);
+                });
+    }
+
+    ListenableFuture<TbMsg> publishMessageAsync(TbContext ctx, TbMsg msg) {
+        return ctx.getExternalCallExecutor().executeAsync(() -> publishMessage(ctx, msg));
+    }
+
+    TbMsg publishMessage(TbContext ctx, TbMsg msg) {
+        String topicArn = TbNodeUtils.processPattern(this.config.getTopicArnPattern(), msg.getMetaData());
+        PublishRequest publishRequest = new PublishRequest()
+                .withTopicArn(topicArn)
+                .withMessage(msg.getData());
+        PublishResult result = this.snsClient.publish(publishRequest);
+        return processPublishResult(ctx, msg, result);
+    }
+
+    private TbMsg processPublishResult(TbContext ctx, TbMsg origMsg, PublishResult result) {
+        TbMsgMetaData metaData = origMsg.getMetaData().copy();
+        metaData.putValue(MESSAGE_ID, result.getMessageId());
+        metaData.putValue(REQUEST_ID, result.getSdkResponseMetadata().getRequestId());
+        return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData());
+    }
+
+    private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable t) {
+        TbMsgMetaData metaData = origMsg.getMetaData().copy();
+        metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage());
+        return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData());
+    }
+
+    @Override
+    public void destroy() {
+        if (this.snsClient != null) {
+            try {
+                this.snsClient.shutdown();
+            } catch (Exception e) {
+                log.error("Failed to shutdown SNS client during destroy()", e);
+            }
+        }
+    }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNodeConfiguration.java
new file mode 100644
index 0000000..a124778
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNodeConfiguration.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.thingsboard.rule.engine.aws.sns;
+
+import lombok.Data;
+import org.thingsboard.rule.engine.api.NodeConfiguration;
+
+@Data
+public class TbSnsNodeConfiguration implements NodeConfiguration<TbSnsNodeConfiguration> {
+
+    private String topicArnPattern;
+    private String accessKeyId;
+    private String secretAccessKey;
+    private String region;
+
+    @Override
+    public TbSnsNodeConfiguration defaultConfiguration() {
+        TbSnsNodeConfiguration configuration = new TbSnsNodeConfiguration();
+        configuration.setTopicArnPattern("arn:aws:sns:us-east-1:123456789012:MyNewTopic");
+        configuration.setRegion("us-east-1");
+        return configuration;
+    }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java
new file mode 100644
index 0000000..f5172c7
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java
@@ -0,0 +1,147 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.thingsboard.rule.engine.aws.sqs;
+
+import com.amazonaws.auth.AWSCredentials;
+import com.amazonaws.auth.AWSStaticCredentialsProvider;
+import com.amazonaws.auth.BasicAWSCredentials;
+import com.amazonaws.services.sqs.AmazonSQS;
+import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
+import com.amazonaws.services.sqs.model.MessageAttributeValue;
+import com.amazonaws.services.sqs.model.SendMessageRequest;
+import com.amazonaws.services.sqs.model.SendMessageResult;
+import com.google.common.util.concurrent.ListenableFuture;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.thingsboard.rule.engine.TbNodeUtils;
+import org.thingsboard.rule.engine.api.*;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.TbMsgMetaData;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
+
+@Slf4j
+@RuleNode(
+        type = ComponentType.ACTION,
+        name = "aws sqs",
+        configClazz = TbSqsNodeConfiguration.class,
+        nodeDescription = "Publish messages to AWS SQS",
+        nodeDetails = "Expects messages with any message type. Will publish message to AWS SQS queue.",
+        uiResources = {"static/rulenode/rulenode-core-config.js"},
+        configDirective = "tbActionNodeSqsConfig"
+)
+public class TbSqsNode implements TbNode {
+
+    private static final String MESSAGE_ID = "messageId";
+    private static final String REQUEST_ID = "requestId";
+    private static final String MESSAGE_BODY_MD5 = "messageBodyMd5";
+    private static final String MESSAGE_ATTRIBUTES_MD5 = "messageAttributesMd5";
+    private static final String SEQUENCE_NUMBER = "sequenceNumber";
+    private static final String ERROR = "error";
+
+    private TbSqsNodeConfiguration config;
+    private AmazonSQS sqsClient;
+
+    @Override
+    public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
+        this.config = TbNodeUtils.convert(configuration, TbSqsNodeConfiguration.class);
+        AWSCredentials awsCredentials = new BasicAWSCredentials(this.config.getAccessKeyId(), this.config.getSecretAccessKey());
+        AWSStaticCredentialsProvider credProvider = new AWSStaticCredentialsProvider(awsCredentials);
+        try {
+            this.sqsClient = AmazonSQSClientBuilder.standard()
+                    .withCredentials(credProvider)
+                    .withRegion(this.config.getRegion())
+                    .build();
+        } catch (Exception e) {
+            throw new TbNodeException(e);
+        }
+    }
+
+    @Override
+    public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
+        withCallback(publishMessageAsync(ctx, msg),
+                m -> ctx.tellNext(m, TbRelationTypes.SUCCESS),
+                t -> {
+                    TbMsg next = processException(ctx, msg, t);
+                    ctx.tellNext(next, TbRelationTypes.FAILURE, t);
+                });
+    }
+
+    ListenableFuture<TbMsg> publishMessageAsync(TbContext ctx, TbMsg msg) {
+        return ctx.getExternalCallExecutor().executeAsync(() -> publishMessage(ctx, msg));
+    }
+
+    TbMsg publishMessage(TbContext ctx, TbMsg msg) {
+        String queueUrl = TbNodeUtils.processPattern(this.config.getQueueUrlPattern(), msg.getMetaData());
+        SendMessageRequest sendMsgRequest =  new SendMessageRequest();
+        sendMsgRequest.withQueueUrl(queueUrl);
+        sendMsgRequest.withMessageBody(msg.getData());
+        Map<String, MessageAttributeValue> messageAttributes = new HashMap<>();
+        this.config.getMessageAttributes().forEach((k,v) -> {
+            String name = TbNodeUtils.processPattern(k, msg.getMetaData());
+            String val = TbNodeUtils.processPattern(v, msg.getMetaData());
+            messageAttributes.put(name, new MessageAttributeValue().withDataType("String").withStringValue(val));
+        });
+        sendMsgRequest.setMessageAttributes(messageAttributes);
+        if (this.config.getQueueType() == TbSqsNodeConfiguration.QueueType.STANDARD) {
+            sendMsgRequest.withDelaySeconds(this.config.getDelaySeconds());
+        } else {
+            sendMsgRequest.withMessageDeduplicationId(msg.getId().toString());
+            sendMsgRequest.withMessageGroupId(msg.getOriginator().toString());
+        }
+        SendMessageResult result = this.sqsClient.sendMessage(sendMsgRequest);
+        return processSendMessageResult(ctx, msg, result);
+    }
+
+    private TbMsg processSendMessageResult(TbContext ctx, TbMsg origMsg, SendMessageResult result) {
+        TbMsgMetaData metaData = origMsg.getMetaData().copy();
+        metaData.putValue(MESSAGE_ID, result.getMessageId());
+        metaData.putValue(REQUEST_ID, result.getSdkResponseMetadata().getRequestId());
+        if (!StringUtils.isEmpty(result.getMD5OfMessageBody())) {
+            metaData.putValue(MESSAGE_BODY_MD5, result.getMD5OfMessageBody());
+        }
+        if (!StringUtils.isEmpty(result.getMD5OfMessageAttributes())) {
+            metaData.putValue(MESSAGE_ATTRIBUTES_MD5, result.getMD5OfMessageAttributes());
+        }
+        if (!StringUtils.isEmpty(result.getSequenceNumber())) {
+            metaData.putValue(SEQUENCE_NUMBER, result.getSequenceNumber());
+        }
+        return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData());
+    }
+
+    private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable t) {
+        TbMsgMetaData metaData = origMsg.getMetaData().copy();
+        metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage());
+        return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData());
+    }
+
+    @Override
+    public void destroy() {
+        if (this.sqsClient != null) {
+            try {
+                this.sqsClient.shutdown();
+            } catch (Exception e) {
+                log.error("Failed to shutdown SQS client during destroy()", e);
+            }
+        }
+    }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNodeConfiguration.java
new file mode 100644
index 0000000..d27af36
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNodeConfiguration.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.thingsboard.rule.engine.aws.sqs;
+
+import lombok.Data;
+import org.thingsboard.rule.engine.api.NodeConfiguration;
+
+import java.util.Collections;
+import java.util.Map;
+
+@Data
+public class TbSqsNodeConfiguration implements NodeConfiguration<TbSqsNodeConfiguration> {
+
+    private QueueType queueType;
+    private String queueUrlPattern;
+    private int delaySeconds;
+    private Map<String, String> messageAttributes;
+    private String accessKeyId;
+    private String secretAccessKey;
+    private String region;
+
+    @Override
+    public TbSqsNodeConfiguration defaultConfiguration() {
+        TbSqsNodeConfiguration configuration = new TbSqsNodeConfiguration();
+        configuration.setQueueType(QueueType.STANDARD);
+        configuration.setQueueUrlPattern("https://sqs.us-east-1.amazonaws.com/123456789012/my-queue-name");
+        configuration.setDelaySeconds(0);
+        configuration.setMessageAttributes(Collections.emptyMap());
+        configuration.setRegion("us-east-1");
+        return configuration;
+    }
+
+    public enum QueueType {
+        STANDARD,
+        FIFO
+    }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java
index 65b1371..04fa728 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java
@@ -15,7 +15,6 @@
  */
 package org.thingsboard.rule.engine.debug;
 
-import com.datastax.driver.core.utils.UUIDs;
 import com.google.common.util.concurrent.ListenableFuture;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.util.StringUtils;
@@ -78,18 +77,18 @@ public class TbMsgGeneratorNode implements TbNode {
     }
 
     private void sentTickMsg(TbContext ctx) {
-        TbMsg tickMsg = new TbMsg(UUIDs.timeBased(), TB_MSG_GENERATOR_NODE_MSG, ctx.getSelfId(), new TbMsgMetaData(), "");
+        TbMsg tickMsg = ctx.newMsg(TB_MSG_GENERATOR_NODE_MSG, ctx.getSelfId(), new TbMsgMetaData(), "");
         nextTickId = tickMsg.getId();
         ctx.tellSelf(tickMsg, delay);
     }
 
-    protected ListenableFuture<TbMsg> generate(TbContext ctx) {
+    private ListenableFuture<TbMsg> generate(TbContext ctx) {
         return ctx.getJsExecutor().executeAsync(() -> {
             if (prevMsg == null) {
-                prevMsg = new TbMsg(UUIDs.timeBased(), "", originatorId, new TbMsgMetaData(), "{}");
+                prevMsg = ctx.newMsg( "", originatorId, new TbMsgMetaData(), "{}");
             }
             TbMsg generated = jsEngine.executeGenerate(prevMsg);
-            prevMsg = new TbMsg(UUIDs.timeBased(), generated.getType(), originatorId, generated.getMetaData(), generated.getData());
+            prevMsg = ctx.newMsg(generated.getType(), originatorId, generated.getMetaData(), generated.getData());
             return prevMsg;
         });
     }
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java
index 3c6704b..4ee8bd5 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java
@@ -61,7 +61,7 @@ public class TbJsSwitchNode implements TbNode {
         ctx.tellNext(msg, nextRelations);
     }
 
-   @Override
+    @Override
     public void destroy() {
         if (jsEngine != null) {
             jsEngine.destroy();
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java
index 225cd99..6426106 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java
@@ -31,8 +31,7 @@ import org.thingsboard.server.common.msg.TbMsg;
         configClazz = TbMsgTypeFilterNodeConfiguration.class,
         relationTypes = {"True", "False"},
         nodeDescription = "Filter incoming messages by Message Type",
-        nodeDetails = "Evaluate incoming Message with configured JS condition. " +
-                "If incoming MessageType is expected - send Message via <b>Success</b> chain, otherwise <b>Failure</b> chain is used.",
+        nodeDetails = "If incoming MessageType is expected - send Message via <b>Success</b> chain, otherwise <b>Failure</b> chain is used.",
         uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
         configDirective = "tbFilterNodeMessageTypeConfig")
 public class TbMsgTypeFilterNode implements TbNode {
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java
new file mode 100644
index 0000000..75329a0
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.filter;
+
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.rule.engine.TbNodeUtils;
+import org.thingsboard.rule.engine.api.*;
+import org.thingsboard.server.common.data.DataConstants;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+
+@Slf4j
+@RuleNode(
+        type = ComponentType.FILTER,
+        name = "message type switch",
+        configClazz = EmptyNodeConfiguration.class,
+        relationTypes = {"Post attributes", "Post telemetry", "RPC Request", "Activity Event", "Inactivity Event", "Connect Event", "Disconnect Event", "Other"},
+        nodeDescription = "Route incoming messages by Message Type",
+        nodeDetails = "Sends messages with message types <b>\"Post attributes\", \"Post telemetry\", \"RPC Request\"</b> via corresponding chain, otherwise <b>Other</b> chain is used.",
+        uiResources = {"static/rulenode/rulenode-core-config.js"},
+        configDirective = "tbNodeEmptyConfig")
+public class TbMsgTypeSwitchNode implements TbNode {
+
+    EmptyNodeConfiguration config;
+
+    @Override
+    public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
+        this.config = TbNodeUtils.convert(configuration, EmptyNodeConfiguration.class);
+    }
+
+    @Override
+    public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException {
+        String relationType;
+        if (msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name())) {
+            relationType = "Post attributes";
+        } else if (msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) {
+            relationType = "Post telemetry";
+        } else if (msg.getType().equals(SessionMsgType.TO_SERVER_RPC_REQUEST.name())) {
+            relationType = "RPC Request";
+        } else if (msg.getType().equals(DataConstants.ACTIVITY_EVENT)) {
+            relationType = "Activity Event";
+        } else if (msg.getType().equals(DataConstants.INACTIVITY_EVENT)) {
+            relationType = "Inactivity Event";
+        } else if (msg.getType().equals(DataConstants.CONNECT_EVENT)) {
+            relationType = "Connect Event";
+        } else if (msg.getType().equals(DataConstants.DISCONNECT_EVENT)) {
+            relationType = "Disconnect Event";
+        } else {
+            relationType = "Other";
+        }
+        ctx.tellNext(msg, relationType);
+    }
+
+    @Override
+    public void destroy() {
+
+    }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java
new file mode 100644
index 0000000..ff54c38
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java
@@ -0,0 +1,117 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.kafka;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.kafka.clients.producer.*;
+import org.thingsboard.rule.engine.TbNodeUtils;
+import org.thingsboard.rule.engine.api.*;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.TbMsgMetaData;
+
+import java.util.Properties;
+import java.util.concurrent.ExecutionException;
+
+@Slf4j
+@RuleNode(
+        type = ComponentType.ACTION,
+        name = "kafka",
+        configClazz = TbKafkaNodeConfiguration.class,
+        nodeDescription = "Publish messages to Kafka server",
+        nodeDetails = "Expects messages with any message type. Will send record via Kafka producer to Kafka server.",
+        uiResources = {"static/rulenode/rulenode-core-config.js"},
+        configDirective = "tbActionNodeKafkaConfig"
+)
+public class TbKafkaNode implements TbNode {
+
+    private static final String OFFSET = "offset";
+    private static final String PARTITION = "partition";
+    private static final String TOPIC = "topic";
+    private static final String ERROR = "error";
+
+    private TbKafkaNodeConfiguration config;
+
+    private Producer<?, String> producer;
+
+    @Override
+    public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
+        this.config = TbNodeUtils.convert(configuration, TbKafkaNodeConfiguration.class);
+        Properties properties = new Properties();
+        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, config.getBootstrapServers());
+        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, config.getValueSerializer());
+        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, config.getKeySerializer());
+        properties.put(ProducerConfig.ACKS_CONFIG, config.getAcks());
+        properties.put(ProducerConfig.RETRIES_CONFIG, config.getRetries());
+        properties.put(ProducerConfig.BATCH_SIZE_CONFIG, config.getBatchSize());
+        properties.put(ProducerConfig.LINGER_MS_CONFIG, config.getLinger());
+        properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, config.getBufferMemory());
+        if (config.getOtherProperties() != null) {
+            config.getOtherProperties()
+                    .forEach((k,v) -> properties.put(k, v));
+        }
+        try {
+            this.producer = new KafkaProducer<>(properties);
+        } catch (Exception e) {
+            throw new TbNodeException(e);
+        }
+    }
+
+    @Override
+    public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
+        String topic = TbNodeUtils.processPattern(config.getTopicPattern(), msg.getMetaData());
+        try {
+            producer.send(new ProducerRecord<>(topic, msg.getData()),
+                    (metadata, e) -> {
+                        if (metadata != null) {
+                            TbMsg next = processResponse(ctx, msg, metadata);
+                            ctx.tellNext(next, TbRelationTypes.SUCCESS);
+                        } else {
+                            TbMsg next = processException(ctx, msg, e);
+                            ctx.tellNext(next, TbRelationTypes.FAILURE, e);
+                        }
+                    });
+        } catch (Exception e) {
+            ctx.tellError(msg, e);
+        }
+    }
+
+    @Override
+    public void destroy() {
+        if (this.producer != null) {
+            try {
+                this.producer.close();
+            } catch (Exception e) {
+                log.error("Failed to close producer during destroy()", e);
+            }
+        }
+    }
+
+    private TbMsg processResponse(TbContext ctx, TbMsg origMsg, RecordMetadata recordMetadata) {
+        TbMsgMetaData metaData = origMsg.getMetaData().copy();
+        metaData.putValue(OFFSET, String.valueOf(recordMetadata.offset()));
+        metaData.putValue(PARTITION, String.valueOf(recordMetadata.partition()));
+        metaData.putValue(TOPIC, recordMetadata.topic());
+        return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData());
+    }
+
+    private TbMsg processException(TbContext ctx, TbMsg origMsg, Exception e) {
+        TbMsgMetaData metaData = origMsg.getMetaData().copy();
+        metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage());
+        return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData());
+    }
+
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNodeConfiguration.java
new file mode 100644
index 0000000..2c16e56
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNodeConfiguration.java
@@ -0,0 +1,55 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.thingsboard.rule.engine.kafka;
+
+import lombok.Data;
+import org.apache.kafka.common.serialization.StringSerializer;
+import org.thingsboard.rule.engine.api.NodeConfiguration;
+
+import java.util.Collections;
+import java.util.Map;
+
+@Data
+public class TbKafkaNodeConfiguration implements NodeConfiguration<TbKafkaNodeConfiguration> {
+
+    private String topicPattern;
+    private String bootstrapServers;
+    private int retries;
+    private int batchSize;
+    private int linger;
+    private int bufferMemory;
+    private String acks;
+    private String keySerializer;
+    private String valueSerializer;
+    private Map<String, String> otherProperties;
+
+    @Override
+    public TbKafkaNodeConfiguration defaultConfiguration() {
+        TbKafkaNodeConfiguration configuration = new TbKafkaNodeConfiguration();
+        configuration.setTopicPattern("my-topic");
+        configuration.setBootstrapServers("localhost:9092");
+        configuration.setRetries(0);
+        configuration.setBatchSize(16384);
+        configuration.setLinger(0);
+        configuration.setBufferMemory(33554432);
+        configuration.setAcks("-1");
+        configuration.setKeySerializer(StringSerializer.class.getName());
+        configuration.setValueSerializer(StringSerializer.class.getName());
+        configuration.setOtherProperties(Collections.emptyMap());
+        return configuration;
+    }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java
index cae2058..69c0901 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java
@@ -15,7 +15,6 @@
  */
 package org.thingsboard.rule.engine.mail;
 
-import com.datastax.driver.core.utils.UUIDs;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.extern.slf4j.Slf4j;
@@ -76,7 +75,7 @@ public class TbMsgToEmailNode implements TbNode {
     public void onMsg(TbContext ctx, TbMsg msg) {
         try {
             EmailPojo email = convert(msg);
-            TbMsg emailMsg = buildEmailMsg(msg, email);
+            TbMsg emailMsg = buildEmailMsg(ctx, msg, email);
             ctx.tellNext(emailMsg);
         } catch (Exception ex) {
             log.warn("Can not convert message to email " + ex.getMessage());
@@ -84,9 +83,9 @@ public class TbMsgToEmailNode implements TbNode {
         }
     }
 
-    private TbMsg buildEmailMsg(TbMsg msg, EmailPojo email) throws JsonProcessingException {
+    private TbMsg buildEmailMsg(TbContext ctx, TbMsg msg, EmailPojo email) throws JsonProcessingException {
         String emailJson = MAPPER.writeValueAsString(email);
-        return new TbMsg(UUIDs.timeBased(), SEND_EMAIL_TYPE, msg.getOriginator(), msg.getMetaData().copy(), emailJson);
+        return ctx.transformMsg(msg, SEND_EMAIL_TYPE, msg.getOriginator(), msg.getMetaData().copy(), emailJson);
     }
 
     private EmailPojo convert(TbMsg msg) throws IOException {
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java
index 407476b..0b74d6e 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java
@@ -63,8 +63,6 @@ public class TbSendEmailNode implements TbNode {
         } catch (Exception ex) {
             ctx.tellError(msg, ex);
         }
-
-
     }
 
     private EmailPojo getEmail(TbMsg msg) throws IOException {
@@ -84,6 +82,5 @@ public class TbSendEmailNode implements TbNode {
 
     @Override
     public void destroy() {
-
     }
 }
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/AnonymousCredentials.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/AnonymousCredentials.java
new file mode 100644
index 0000000..9d5e8df
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/AnonymousCredentials.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.thingsboard.rule.engine.mqtt.credentials;
+
+import io.netty.handler.ssl.SslContext;
+import nl.jk5.mqtt.MqttClientConfig;
+
+import java.util.Optional;
+
+public class AnonymousCredentials implements MqttClientCredentials {
+
+    @Override
+    public Optional<SslContext> initSslContext() {
+        return Optional.empty();
+    }
+
+    @Override
+    public void configure(MqttClientConfig config) {
+
+    }
+}
+
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/BasicCredentials.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/BasicCredentials.java
new file mode 100644
index 0000000..cbbd703
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/BasicCredentials.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.thingsboard.rule.engine.mqtt.credentials;
+
+import io.netty.handler.ssl.SslContext;
+import lombok.Data;
+import nl.jk5.mqtt.MqttClientConfig;
+
+import java.util.Optional;
+
+@Data
+public class BasicCredentials implements MqttClientCredentials {
+
+    private String username;
+    private String password;
+
+    @Override
+    public Optional<SslContext> initSslContext() {
+        return Optional.empty();
+    }
+
+    @Override
+    public void configure(MqttClientConfig config) {
+        config.setUsername(username);
+        config.setPassword(password);
+    }
+
+}
+
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/CertPemClientCredentials.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/CertPemClientCredentials.java
new file mode 100644
index 0000000..c9fb4a3
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/CertPemClientCredentials.java
@@ -0,0 +1,154 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.thingsboard.rule.engine.mqtt.credentials;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import nl.jk5.mqtt.MqttClientConfig;
+import org.apache.commons.codec.binary.Base64;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMDecryptorProvider;
+import org.bouncycastle.openssl.PEMEncryptedKeyPair;
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
+import org.springframework.util.StringUtils;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.TrustManagerFactory;
+import java.io.ByteArrayInputStream;
+import java.security.*;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Optional;
+
+@Data
+@Slf4j
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CertPemClientCredentials implements MqttClientCredentials {
+
+    private static final String TLS_VERSION = "TLSv1.2";
+
+    private String caCert;
+    private String cert;
+    private String privateKey;
+    private String password;
+
+    @Override
+    public Optional<SslContext> initSslContext() {
+        try {
+            Security.addProvider(new BouncyCastleProvider());
+            return Optional.of(SslContextBuilder.forClient()
+                    .keyManager(createAndInitKeyManagerFactory())
+                    .trustManager(createAndInitTrustManagerFactory())
+                    .clientAuth(ClientAuth.REQUIRE)
+                    .build());
+        } catch (Exception e) {
+            log.error("[{}:{}] Creating TLS factory failed!", caCert, cert, e);
+            throw new RuntimeException("Creating TLS factory failed!", e);
+        }
+    }
+
+    @Override
+    public void configure(MqttClientConfig config) {
+
+    }
+
+    private KeyManagerFactory createAndInitKeyManagerFactory() throws Exception {
+        X509Certificate certHolder = readCertFile(cert);
+        Object keyObject = readPrivateKeyFile(privateKey);
+        char[] passwordCharArray = "".toCharArray();
+        if (!StringUtils.isEmpty(password)) {
+            passwordCharArray = password.toCharArray();
+        }
+
+        JcaPEMKeyConverter keyConverter = new JcaPEMKeyConverter().setProvider("BC");
+
+        PrivateKey privateKey;
+        if (keyObject instanceof PEMEncryptedKeyPair) {
+            PEMDecryptorProvider provider = new JcePEMDecryptorProviderBuilder().build(passwordCharArray);
+            KeyPair key = keyConverter.getKeyPair(((PEMEncryptedKeyPair) keyObject).decryptKeyPair(provider));
+            privateKey = key.getPrivate();
+        } else if (keyObject instanceof PEMKeyPair) {
+            KeyPair key = keyConverter.getKeyPair((PEMKeyPair) keyObject);
+            privateKey = key.getPrivate();
+        } else if (keyObject instanceof PrivateKey) {
+            privateKey = (PrivateKey)keyObject;
+        } else {
+            throw new RuntimeException("Unable to get private key from object: " + keyObject.getClass());
+        }
+
+        KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+        clientKeyStore.load(null, null);
+        clientKeyStore.setCertificateEntry("cert", certHolder);
+        clientKeyStore.setKeyEntry("private-key",
+                privateKey,
+                passwordCharArray,
+                new Certificate[]{certHolder});
+
+        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+        keyManagerFactory.init(clientKeyStore, passwordCharArray);
+        return keyManagerFactory;
+    }
+
+    private TrustManagerFactory createAndInitTrustManagerFactory() throws Exception {
+        X509Certificate caCertHolder;
+        caCertHolder = readCertFile(caCert);
+
+        KeyStore caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+        caKeyStore.load(null, null);
+        caKeyStore.setCertificateEntry("caCert-cert", caCertHolder);
+
+        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+        trustManagerFactory.init(caKeyStore);
+        return trustManagerFactory;
+    }
+
+    private X509Certificate readCertFile(String fileContent) throws Exception {
+        X509Certificate certificate = null;
+        if (fileContent != null && !fileContent.trim().isEmpty()) {
+            fileContent = fileContent.replace("-----BEGIN CERTIFICATE-----", "")
+                    .replace("-----END CERTIFICATE-----", "")
+                    .replaceAll("\\s", "");
+            byte[] decoded = Base64.decodeBase64(fileContent);
+            CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+            certificate = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(decoded));
+        }
+        return certificate;
+    }
+
+    private PrivateKey readPrivateKeyFile(String fileContent) throws Exception {
+        RSAPrivateKey privateKey = null;
+        if (fileContent != null && !fileContent.isEmpty()) {
+            fileContent = fileContent.replaceAll(".*BEGIN.*PRIVATE KEY.*", "")
+                    .replaceAll(".*END.*PRIVATE KEY.*", "")
+                    .replaceAll("\\s", "");
+            byte[] decoded = Base64.decodeBase64(fileContent);
+            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+            privateKey = (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(decoded));
+        }
+        return privateKey;
+    }
+
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/MqttClientCredentials.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/MqttClientCredentials.java
new file mode 100644
index 0000000..5c4594f
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/MqttClientCredentials.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.thingsboard.rule.engine.mqtt.credentials;
+
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import io.netty.handler.ssl.SslContext;
+import nl.jk5.mqtt.MqttClientConfig;
+
+import java.util.Optional;
+
+@JsonTypeInfo(
+        use = JsonTypeInfo.Id.NAME,
+        include = JsonTypeInfo.As.PROPERTY,
+        property = "type")
+@JsonSubTypes({
+        @JsonSubTypes.Type(value = AnonymousCredentials.class, name = "anonymous"),
+        @JsonSubTypes.Type(value = BasicCredentials.class, name = "basic"),
+        @JsonSubTypes.Type(value = CertPemClientCredentials.class, name = "cert.PEM")})
+public interface MqttClientCredentials {
+
+    Optional<SslContext> initSslContext();
+
+    void configure(MqttClientConfig config);
+}
+
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java
new file mode 100644
index 0000000..0ceda9c
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java
@@ -0,0 +1,144 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.thingsboard.rule.engine.mqtt;
+
+import io.netty.buffer.Unpooled;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.handler.codec.mqtt.MqttQoS;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.util.concurrent.Future;
+import lombok.extern.slf4j.Slf4j;
+import nl.jk5.mqtt.MqttClient;
+import nl.jk5.mqtt.MqttClientConfig;
+import nl.jk5.mqtt.MqttConnectResult;
+import org.springframework.util.StringUtils;
+import org.thingsboard.rule.engine.TbNodeUtils;
+import org.thingsboard.rule.engine.api.*;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.TbMsgMetaData;
+
+import javax.net.ssl.SSLException;
+import java.nio.charset.Charset;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@Slf4j
+@RuleNode(
+        type = ComponentType.ACTION,
+        name = "mqtt",
+        configClazz = TbMqttNodeConfiguration.class,
+        nodeDescription = "Publish messages to MQTT broker",
+        nodeDetails = "Expects messages with any message type. Will publish message to MQTT broker.",
+        uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
+        configDirective = "tbActionNodeMqttConfig"
+)
+public class TbMqttNode implements TbNode {
+
+    private static final Charset UTF8 = Charset.forName("UTF-8");
+
+    private static final String ERROR = "error";
+
+    private TbMqttNodeConfiguration config;
+
+    private EventLoopGroup eventLoopGroup;
+    private MqttClient mqttClient;
+
+    @Override
+    public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
+        try {
+            this.config = TbNodeUtils.convert(configuration, TbMqttNodeConfiguration.class);
+            this.eventLoopGroup = new NioEventLoopGroup();
+            this.mqttClient = initClient();
+        } catch (Exception e) {
+            throw new TbNodeException(e);
+        }
+    }
+
+    @Override
+    public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
+        String topic = TbNodeUtils.processPattern(this.config.getTopicPattern(), msg.getMetaData());
+        this.mqttClient.publish(topic, Unpooled.wrappedBuffer(msg.getData().getBytes(UTF8)), MqttQoS.AT_LEAST_ONCE)
+                .addListener(future -> {
+                    if (future.isSuccess()) {
+                        TbMsg next = ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), msg.getData());
+                        ctx.tellNext(next, TbRelationTypes.SUCCESS);
+                    } else {
+                        TbMsg next = processException(ctx, msg, future.cause());
+                        ctx.tellNext(next, TbRelationTypes.FAILURE, future.cause());
+                    }
+                }
+        );
+    }
+
+    private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable e) {
+        TbMsgMetaData metaData = origMsg.getMetaData().copy();
+        metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage());
+        return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData());
+    }
+
+    @Override
+    public void destroy() {
+        if (this.mqttClient != null) {
+            this.mqttClient.disconnect();
+        }
+        if (this.eventLoopGroup != null) {
+            this.eventLoopGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS);
+        }
+    }
+
+    private MqttClient initClient() throws Exception {
+        Optional<SslContext> sslContextOpt = initSslContext();
+        MqttClientConfig config = sslContextOpt.isPresent() ? new MqttClientConfig(sslContextOpt.get()) : new MqttClientConfig();
+        if (!StringUtils.isEmpty(this.config.getClientId())) {
+            config.setClientId(this.config.getClientId());
+        }
+        this.config.getCredentials().configure(config);
+        MqttClient client = MqttClient.create(config);
+        client.setEventLoop(this.eventLoopGroup);
+        Future<MqttConnectResult> connectFuture = client.connect(this.config.getHost(), this.config.getPort());
+        MqttConnectResult result;
+        try {
+            result = connectFuture.get(this.config.getConnectTimeoutSec(), TimeUnit.SECONDS);
+        } catch (TimeoutException ex) {
+            connectFuture.cancel(true);
+            client.disconnect();
+            String hostPort = this.config.getHost() + ":" + this.config.getPort();
+            throw new RuntimeException(String.format("Failed to connect to MQTT broker at %s.", hostPort));
+        }
+        if (!result.isSuccess()) {
+            connectFuture.cancel(true);
+            client.disconnect();
+            String hostPort = this.config.getHost() + ":" + this.config.getPort();
+            throw new RuntimeException(String.format("Failed to connect to MQTT broker at %s. Result code is: %s", hostPort, result.getReturnCode()));
+        }
+        return client;
+    }
+
+    private Optional<SslContext> initSslContext() throws SSLException {
+        Optional<SslContext> result = this.config.getCredentials().initSslContext();
+        if (this.config.isSsl() && !result.isPresent()) {
+            result = Optional.of(SslContextBuilder.forClient().build());
+        }
+        return result;
+    }
+
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java
new file mode 100644
index 0000000..f63e201
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.thingsboard.rule.engine.mqtt;
+
+import lombok.Data;
+import org.thingsboard.rule.engine.api.NodeConfiguration;
+import org.thingsboard.rule.engine.mqtt.credentials.AnonymousCredentials;
+import org.thingsboard.rule.engine.mqtt.credentials.MqttClientCredentials;
+
+@Data
+public class TbMqttNodeConfiguration implements NodeConfiguration<TbMqttNodeConfiguration> {
+
+    private String topicPattern;
+    private String host;
+    private int port;
+    private int connectTimeoutSec;
+    private String clientId;
+
+    private boolean ssl;
+    private MqttClientCredentials credentials;
+
+    @Override
+    public TbMqttNodeConfiguration defaultConfiguration() {
+        TbMqttNodeConfiguration configuration = new TbMqttNodeConfiguration();
+        configuration.setTopicPattern("my-topic");
+        configuration.setHost("localhost");
+        configuration.setPort(1883);
+        configuration.setConnectTimeoutSec(10);
+        configuration.setSsl(false);
+        configuration.setCredentials(new AnonymousCredentials());
+        return configuration;
+    }
+
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java
new file mode 100644
index 0000000..c318c58
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java
@@ -0,0 +1,147 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.thingsboard.rule.engine.rabbitmq;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.rabbitmq.client.*;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.thingsboard.rule.engine.TbNodeUtils;
+import org.thingsboard.rule.engine.api.*;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.TbMsgMetaData;
+
+import java.nio.charset.Charset;
+import java.util.concurrent.ExecutionException;
+
+import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
+
+@Slf4j
+@RuleNode(
+        type = ComponentType.ACTION,
+        name = "rabbitmq",
+        configClazz = TbRabbitMqNodeConfiguration.class,
+        nodeDescription = "Publish messages to RabbitMQ",
+        nodeDetails = "Expects messages with any message type. Will publish message to RabbitMQ queue.",
+        uiResources = {"static/rulenode/rulenode-core-config.js"},
+        configDirective = "tbActionNodeRabbitMqConfig"
+)
+public class TbRabbitMqNode implements TbNode {
+
+    private static final Charset UTF8 = Charset.forName("UTF-8");
+
+    private static final String ERROR = "error";
+
+    private TbRabbitMqNodeConfiguration config;
+
+    private Connection connection;
+    private Channel channel;
+
+    @Override
+    public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
+        this.config = TbNodeUtils.convert(configuration, TbRabbitMqNodeConfiguration.class);
+        ConnectionFactory factory = new ConnectionFactory();
+        factory.setHost(this.config.getHost());
+        factory.setPort(this.config.getPort());
+        factory.setVirtualHost(this.config.getVirtualHost());
+        factory.setUsername(this.config.getUsername());
+        factory.setPassword(this.config.getPassword());
+        factory.setAutomaticRecoveryEnabled(this.config.isAutomaticRecoveryEnabled());
+        factory.setConnectionTimeout(this.config.getConnectionTimeout());
+        factory.setHandshakeTimeout(this.config.getHandshakeTimeout());
+        this.config.getClientProperties().forEach((k,v) -> factory.getClientProperties().put(k,v));
+        try {
+            this.connection = factory.newConnection();
+            this.channel = this.connection.createChannel();
+        } catch (Exception e) {
+            throw new TbNodeException(e);
+        }
+    }
+
+    @Override
+    public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
+        withCallback(publishMessageAsync(ctx, msg),
+                m -> ctx.tellNext(m, TbRelationTypes.SUCCESS),
+                t -> {
+                    TbMsg next = processException(ctx, msg, t);
+                    ctx.tellNext(next, TbRelationTypes.FAILURE, t);
+                });
+    }
+
+    ListenableFuture<TbMsg> publishMessageAsync(TbContext ctx, TbMsg msg) {
+        return ctx.getExternalCallExecutor().executeAsync(() -> publishMessage(ctx, msg));
+    }
+
+    TbMsg publishMessage(TbContext ctx, TbMsg msg) throws Exception {
+        String exchangeName = "";
+        if (!StringUtils.isEmpty(this.config.getExchangeNamePattern())) {
+            exchangeName = TbNodeUtils.processPattern(this.config.getExchangeNamePattern(), msg.getMetaData());
+        }
+        String routingKey = "";
+        if (!StringUtils.isEmpty(this.config.getRoutingKeyPattern())) {
+            routingKey = TbNodeUtils.processPattern(this.config.getRoutingKeyPattern(), msg.getMetaData());
+        }
+        AMQP.BasicProperties properties = null;
+        if (!StringUtils.isEmpty(this.config.getMessageProperties())) {
+            properties = convert(this.config.getMessageProperties());
+        }
+        channel.basicPublish(
+                exchangeName,
+                routingKey,
+                properties,
+                msg.getData().getBytes(UTF8));
+        return ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), msg.getData());
+    }
+
+    private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable t) {
+        TbMsgMetaData metaData = origMsg.getMetaData().copy();
+        metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage());
+        return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData());
+    }
+
+    @Override
+    public void destroy() {
+        if (this.connection != null) {
+            try {
+                this.connection.close();
+            } catch (Exception e) {
+                log.error("Failed to close connection during destroy()", e);
+            }
+        }
+    }
+
+    private static AMQP.BasicProperties convert(String name) throws TbNodeException {
+        switch (name) {
+            case "BASIC":
+                return MessageProperties.BASIC;
+            case "TEXT_PLAIN":
+                return MessageProperties.TEXT_PLAIN;
+            case "MINIMAL_BASIC":
+                return MessageProperties.MINIMAL_BASIC;
+            case "MINIMAL_PERSISTENT_BASIC":
+                return MessageProperties.MINIMAL_PERSISTENT_BASIC;
+            case "PERSISTENT_BASIC":
+                return MessageProperties.PERSISTENT_BASIC;
+            case "PERSISTENT_TEXT_PLAIN":
+                return MessageProperties.PERSISTENT_TEXT_PLAIN;
+            default:
+                throw new TbNodeException("Message Properties: '" + name + "' is undefined!");
+        }
+    }
+}
+
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNodeConfiguration.java
new file mode 100644
index 0000000..1a0a00a
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNodeConfiguration.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.thingsboard.rule.engine.rabbitmq;
+
+import com.rabbitmq.client.ConnectionFactory;
+import lombok.Data;
+import org.thingsboard.rule.engine.api.NodeConfiguration;
+
+import java.util.Collections;
+import java.util.Map;
+
+@Data
+public class TbRabbitMqNodeConfiguration implements NodeConfiguration<TbRabbitMqNodeConfiguration> {
+
+    private String exchangeNamePattern;
+    private String routingKeyPattern;
+    private String messageProperties;
+    private String host;
+    private int port;
+    private String virtualHost;
+    private String username;
+    private String password;
+    private boolean automaticRecoveryEnabled;
+    private int connectionTimeout;
+    private int handshakeTimeout;
+    private Map<String, String> clientProperties;
+
+    @Override
+    public TbRabbitMqNodeConfiguration defaultConfiguration() {
+        TbRabbitMqNodeConfiguration configuration = new TbRabbitMqNodeConfiguration();
+        configuration.setExchangeNamePattern("");
+        configuration.setRoutingKeyPattern("");
+        configuration.setMessageProperties(null);
+        configuration.setHost(ConnectionFactory.DEFAULT_HOST);
+        configuration.setPort(ConnectionFactory.DEFAULT_AMQP_PORT);
+        configuration.setVirtualHost(ConnectionFactory.DEFAULT_VHOST);
+        configuration.setUsername(ConnectionFactory.DEFAULT_USER);
+        configuration.setPassword(ConnectionFactory.DEFAULT_PASS);
+        configuration.setAutomaticRecoveryEnabled(false);
+        configuration.setConnectionTimeout(ConnectionFactory.DEFAULT_CONNECTION_TIMEOUT);
+        configuration.setHandshakeTimeout(ConnectionFactory.DEFAULT_HANDSHAKE_TIMEOUT);
+        configuration.setClientProperties(Collections.emptyMap());
+        return configuration;
+    }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java
new file mode 100644
index 0000000..311f81d
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java
@@ -0,0 +1,152 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.rest;
+
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.handler.ssl.SslContextBuilder;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.Netty4ClientHttpRequestFactory;
+import org.springframework.util.concurrent.ListenableFuture;
+import org.springframework.util.concurrent.ListenableFutureCallback;
+import org.springframework.web.client.*;
+import org.thingsboard.rule.engine.TbNodeUtils;
+import org.thingsboard.rule.engine.api.*;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.TbMsgMetaData;
+
+import javax.net.ssl.SSLException;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@RuleNode(
+        type = ComponentType.ACTION,
+        name = "rest api call",
+        configClazz = TbRestApiCallNodeConfiguration.class,
+        nodeDescription = "Invoke REST API calls to external REST server",
+        nodeDetails = "Expects messages with any message type. Will invoke REST API call to external REST server.",
+        uiResources = {"static/rulenode/rulenode-core-config.js"},
+        configDirective = "tbActionNodeRestApiCallConfig"
+)
+public class TbRestApiCallNode implements TbNode {
+
+    private static final String STATUS = "status";
+    private static final String STATUS_CODE = "statusCode";
+    private static final String STATUS_REASON = "statusReason";
+    private static final String ERROR = "error";
+    private static final String ERROR_BODY = "error_body";
+
+    private TbRestApiCallNodeConfiguration config;
+
+    private EventLoopGroup eventLoopGroup;
+    private AsyncRestTemplate httpClient;
+
+    @Override
+    public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
+        try {
+            this.config = TbNodeUtils.convert(configuration, TbRestApiCallNodeConfiguration.class);
+            this.eventLoopGroup = new NioEventLoopGroup();
+            Netty4ClientHttpRequestFactory nettyFactory = new Netty4ClientHttpRequestFactory(this.eventLoopGroup);
+            nettyFactory.setSslContext(SslContextBuilder.forClient().build());
+            httpClient = new AsyncRestTemplate(nettyFactory);
+        } catch (SSLException e) {
+            throw new TbNodeException(e);
+        }
+    }
+
+    @Override
+    public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
+        String endpointUrl = TbNodeUtils.processPattern(config.getRestEndpointUrlPattern(), msg.getMetaData());
+        HttpHeaders headers = prepareHeaders(msg.getMetaData());
+        HttpMethod method = HttpMethod.valueOf(config.getRequestMethod());
+        HttpEntity<String> entity = new HttpEntity<>(msg.getData(), headers);
+
+        ListenableFuture<ResponseEntity<String>> future = httpClient.exchange(
+                endpointUrl, method, entity, String.class);
+
+        future.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
+            @Override
+            public void onFailure(Throwable throwable) {
+                TbMsg next = processException(ctx, msg, throwable);
+                ctx.tellNext(next, TbRelationTypes.FAILURE, throwable);
+            }
+
+            @Override
+            public void onSuccess(ResponseEntity<String> responseEntity) {
+                if (responseEntity.getStatusCode().is2xxSuccessful()) {
+                    TbMsg next = processResponse(ctx, msg, responseEntity);
+                    ctx.tellNext(next, TbRelationTypes.SUCCESS);
+                } else {
+                    TbMsg next = processFailureResponse(ctx, msg, responseEntity);
+                    ctx.tellNext(next, TbRelationTypes.FAILURE);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void destroy() {
+        if (this.eventLoopGroup != null) {
+            this.eventLoopGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS);
+        }
+    }
+
+    private TbMsg processResponse(TbContext ctx, TbMsg origMsg, ResponseEntity<String> response) {
+        TbMsgMetaData metaData = new TbMsgMetaData();
+        metaData.putValue(STATUS, response.getStatusCode().name());
+        metaData.putValue(STATUS_CODE, response.getStatusCode().value()+"");
+        metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase());
+        response.getHeaders().toSingleValueMap().forEach((k,v) -> metaData.putValue(k,v) );
+        return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, response.getBody());
+    }
+
+    private TbMsg processFailureResponse(TbContext ctx, TbMsg origMsg, ResponseEntity<String> response) {
+        TbMsgMetaData metaData = origMsg.getMetaData().copy();
+        metaData.putValue(STATUS, response.getStatusCode().name());
+        metaData.putValue(STATUS_CODE, response.getStatusCode().value()+"");
+        metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase());
+        metaData.putValue(ERROR_BODY, response.getBody());
+        return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData());
+    }
+
+    private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable e) {
+        TbMsgMetaData metaData = origMsg.getMetaData().copy();
+        metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage());
+        if (e instanceof HttpClientErrorException) {
+            HttpClientErrorException httpClientErrorException = (HttpClientErrorException)e;
+            metaData.putValue(STATUS, httpClientErrorException.getStatusText());
+            metaData.putValue(STATUS_CODE, httpClientErrorException.getRawStatusCode()+"");
+            metaData.putValue(ERROR_BODY, httpClientErrorException.getResponseBodyAsString());
+        }
+        return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData());
+    }
+
+    private HttpHeaders prepareHeaders(TbMsgMetaData metaData) {
+        HttpHeaders headers = new HttpHeaders();
+        config.getHeaders().forEach((k,v) -> {
+            headers.add(TbNodeUtils.processPattern(k, metaData), TbNodeUtils.processPattern(v, metaData));
+        });
+        return headers;
+    }
+
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java
new file mode 100644
index 0000000..812eb77
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.rest;
+
+import lombok.Data;
+import org.thingsboard.rule.engine.api.NodeConfiguration;
+
+import java.util.Collections;
+import java.util.Map;
+
+@Data
+public class TbRestApiCallNodeConfiguration implements NodeConfiguration<TbRestApiCallNodeConfiguration> {
+
+    private String restEndpointUrlPattern;
+    private String requestMethod;
+    private Map<String, String> headers;
+
+    @Override
+    public TbRestApiCallNodeConfiguration defaultConfiguration() {
+        TbRestApiCallNodeConfiguration configuration = new TbRestApiCallNodeConfiguration();
+        configuration.setRestEndpointUrlPattern("http://localhost/api");
+        configuration.setRequestMethod("POST");
+        configuration.setHeaders(Collections.emptyMap());
+        return configuration;
+    }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java
new file mode 100644
index 0000000..ccaac49
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.rpc;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
+import org.thingsboard.rule.engine.TbNodeUtils;
+import org.thingsboard.rule.engine.api.RuleNode;
+import org.thingsboard.rule.engine.api.TbContext;
+import org.thingsboard.rule.engine.api.TbNode;
+import org.thingsboard.rule.engine.api.TbNodeConfiguration;
+import org.thingsboard.rule.engine.api.TbNodeException;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.msg.TbMsg;
+
+@Slf4j
+@RuleNode(
+        type = ComponentType.ACTION,
+        name = "rpc call reply",
+        configClazz = TbSendRpcReplyNodeConfiguration.class,
+        nodeDescription = "Sends reply to the RPC call from device",
+        nodeDetails = "Expects messages with any message type. Will forward message body to the device.",
+        uiResources = {"static/rulenode/rulenode-core-config.js"},
+        configDirective = "tbActionNodeRpcReplyConfig"
+)
+public class TbSendRPCReplyNode implements TbNode {
+
+    private TbSendRpcReplyNodeConfiguration config;
+
+    @Override
+    public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
+        this.config = TbNodeUtils.convert(configuration, TbSendRpcReplyNodeConfiguration.class);
+    }
+
+    @Override
+    public void onMsg(TbContext ctx, TbMsg msg) {
+        String requestIdStr = msg.getMetaData().getValue(config.getRequestIdMetaDataAttribute());
+        if (msg.getOriginator().getEntityType() != EntityType.DEVICE) {
+            ctx.tellError(msg, new RuntimeException("Message originator is not a device entity!"));
+        } else if (StringUtils.isEmpty(requestIdStr)) {
+            ctx.tellError(msg, new RuntimeException("Request id is not present in the metadata!"));
+        } else if (StringUtils.isEmpty(msg.getData())) {
+            ctx.tellError(msg, new RuntimeException("Request body is empty!"));
+        } else {
+            ctx.getRpcService().sendRpcReply(new DeviceId(msg.getOriginator().getId()), Integer.parseInt(requestIdStr), msg.getData());
+        }
+    }
+
+    @Override
+    public void destroy() {
+    }
+
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRpcReplyNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRpcReplyNodeConfiguration.java
new file mode 100644
index 0000000..402a33b
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRpcReplyNodeConfiguration.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.rpc;
+
+import lombok.Data;
+import org.thingsboard.rule.engine.api.NodeConfiguration;
+import org.thingsboard.server.common.data.DataConstants;
+
+@Data
+public class TbSendRpcReplyNodeConfiguration implements NodeConfiguration<TbSendRpcReplyNodeConfiguration> {
+
+    private String requestIdMetaDataAttribute;
+
+    @Override
+    public TbSendRpcReplyNodeConfiguration defaultConfiguration() {
+        TbSendRpcReplyNodeConfiguration configuration = new TbSendRpcReplyNodeConfiguration();
+        configuration.setRequestIdMetaDataAttribute("requestId");
+        return configuration;
+    }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java
new file mode 100644
index 0000000..3d38653
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java
@@ -0,0 +1,103 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.rpc;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.rule.engine.TbNodeUtils;
+import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest;
+import org.thingsboard.rule.engine.api.RuleNode;
+import org.thingsboard.rule.engine.api.TbContext;
+import org.thingsboard.rule.engine.api.TbNode;
+import org.thingsboard.rule.engine.api.TbNodeConfiguration;
+import org.thingsboard.rule.engine.api.TbNodeException;
+import org.thingsboard.rule.engine.api.TbRelationTypes;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.msg.TbMsg;
+
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@RuleNode(
+        type = ComponentType.ACTION,
+        name = "rpc call request",
+        configClazz = TbSendRpcRequestNodeConfiguration.class,
+        nodeDescription = "Sends one-way RPC call to device",
+        nodeDetails = "Expects messages with \"method\" and \"params\". Will forward response from device to next nodes.",
+        uiResources = {"static/rulenode/rulenode-core-config.js"},
+        configDirective = "tbActionNodeRpcRequestConfig"
+)
+public class TbSendRPCRequestNode implements TbNode {
+
+    private Random random = new Random();
+    private Gson gson = new Gson();
+    private JsonParser jsonParser = new JsonParser();
+    private TbSendRpcRequestNodeConfiguration config;
+
+    @Override
+    public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
+        this.config = TbNodeUtils.convert(configuration, TbSendRpcRequestNodeConfiguration.class);
+    }
+
+    @Override
+    public void onMsg(TbContext ctx, TbMsg msg) {
+        JsonObject json = jsonParser.parse(msg.getData()).getAsJsonObject();
+
+        if (msg.getOriginator().getEntityType() != EntityType.DEVICE) {
+            ctx.tellError(msg, new RuntimeException("Message originator is not a device entity!"));
+        } else if (!json.has("method")) {
+            ctx.tellError(msg, new RuntimeException("Method is not present in the message!"));
+        } else if (!json.has("params")) {
+            ctx.tellError(msg, new RuntimeException("Params are not present in the message!"));
+        } else {
+            int requestId = json.has("requestId") ? json.get("requestId").getAsInt() : random.nextInt();
+            RuleEngineDeviceRpcRequest request = RuleEngineDeviceRpcRequest.builder()
+                    .method(gson.toJson(json.get("method")))
+                    .body(gson.toJson(json.get("params")))
+                    .deviceId(new DeviceId(msg.getOriginator().getId()))
+                    .requestId(requestId)
+                    .timeout(TimeUnit.SECONDS.toMillis(config.getTimeoutInSeconds()))
+                    .build();
+
+            ctx.getRpcService().sendRpcRequest(request, ruleEngineDeviceRpcResponse -> {
+                if (!ruleEngineDeviceRpcResponse.getError().isPresent()) {
+                    TbMsg next = ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), ruleEngineDeviceRpcResponse.getResponse().get());
+                    ctx.tellNext(next, TbRelationTypes.SUCCESS);
+                } else {
+                    TbMsg next = ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), wrap("error", ruleEngineDeviceRpcResponse.getError().get().name()));
+                    ctx.tellNext(next, TbRelationTypes.FAILURE);
+                    ctx.tellError(msg, new RuntimeException(ruleEngineDeviceRpcResponse.getError().get().name()));
+                }
+            });
+        }
+    }
+
+    @Override
+    public void destroy() {
+    }
+
+    private String wrap(String name, String body) {
+        JsonObject json = new JsonObject();
+        json.addProperty(name, body);
+        return gson.toJson(json);
+    }
+
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRpcRequestNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRpcRequestNodeConfiguration.java
new file mode 100644
index 0000000..214ce65
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRpcRequestNodeConfiguration.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.rpc;
+
+import lombok.Data;
+import org.thingsboard.rule.engine.api.NodeConfiguration;
+
+@Data
+public class TbSendRpcRequestNodeConfiguration implements NodeConfiguration<TbSendRpcRequestNodeConfiguration> {
+
+    private int timeoutInSeconds;
+
+    @Override
+    public TbSendRpcRequestNodeConfiguration defaultConfiguration() {
+        TbSendRpcRequestNodeConfiguration configuration = new TbSendRpcRequestNodeConfiguration();
+        configuration.setTimeoutInSeconds(60);
+        return configuration;
+    }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/TbNodeUtils.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/TbNodeUtils.java
index d6f4384..19e8013 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/TbNodeUtils.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/TbNodeUtils.java
@@ -19,6 +19,9 @@ import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.thingsboard.rule.engine.api.TbNodeConfiguration;
 import org.thingsboard.rule.engine.api.TbNodeException;
+import org.thingsboard.server.common.msg.TbMsgMetaData;
+
+import java.util.Map;
 
 /**
  * Created by ashvayka on 19.01.18.
@@ -27,6 +30,9 @@ public class TbNodeUtils {
 
     private static final ObjectMapper mapper = new ObjectMapper();
 
+    private static final String VARIABLE_TEMPLATE = "${%s}";
+
+
     public static <T> T convert(TbNodeConfiguration configuration, Class<T> clazz) throws TbNodeException {
         try {
             return mapper.treeToValue(configuration.getData(), clazz);
@@ -35,4 +41,17 @@ public class TbNodeUtils {
         }
     }
 
+    public static String processPattern(String pattern, TbMsgMetaData metaData) {
+        String result = new String(pattern);
+        for (Map.Entry<String,String> keyVal  : metaData.values().entrySet()) {
+            result = processVar(result, keyVal.getKey(), keyVal.getValue());
+        }
+        return result;
+    }
+
+    private static String processVar(String pattern, String key, String val) {
+        String varPattern = String.format(VARIABLE_TEMPLATE, key);
+        return pattern.replace(varPattern, val);
+    }
+
 }
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java
new file mode 100644
index 0000000..8ab57c7
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java
@@ -0,0 +1,78 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.telemetry;
+
+import com.google.gson.JsonParser;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
+import org.thingsboard.rule.engine.TbNodeUtils;
+import org.thingsboard.rule.engine.api.RuleNode;
+import org.thingsboard.rule.engine.api.TbContext;
+import org.thingsboard.rule.engine.api.TbNode;
+import org.thingsboard.rule.engine.api.TbNodeConfiguration;
+import org.thingsboard.rule.engine.api.TbNodeException;
+import org.thingsboard.server.common.data.kv.AttributeKvEntry;
+import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
+import org.thingsboard.server.common.data.kv.KvEntry;
+import org.thingsboard.server.common.data.kv.TsKvEntry;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.core.AttributesUpdateRequest;
+import org.thingsboard.server.common.msg.core.TelemetryUploadRequest;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+import org.thingsboard.server.common.transport.adaptor.JsonConverter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Slf4j
+@RuleNode(
+        type = ComponentType.ACTION,
+        name = "save attributes",
+        configClazz = TbMsgAttributesNodeConfiguration.class,
+        nodeDescription = "Saves attributes data",
+        nodeDetails = "Saves entity attributes based on configurable scope parameter. Expects messages with 'POST_ATTRIBUTES_REQUEST' message type",
+        uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
+        configDirective = "tbActionNodeAttributesConfig"
+)
+public class TbMsgAttributesNode implements TbNode {
+
+    private TbMsgAttributesNodeConfiguration config;
+
+    @Override
+    public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
+        this.config = TbNodeUtils.convert(configuration, TbMsgAttributesNodeConfiguration.class);
+    }
+
+    @Override
+    public void onMsg(TbContext ctx, TbMsg msg) {
+        if (!msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name())) {
+            ctx.tellError(msg, new IllegalArgumentException("Unsupported msg type: " + msg.getType()));
+            return;
+        }
+
+        String src = msg.getData();
+        Set<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(new JsonParser().parse(src)).getAttributes();
+        ctx.getTelemetryService().saveAndNotify(msg.getOriginator(), config.getScope(), new ArrayList<>(attributes), new TelemetryNodeCallback(ctx, msg));
+    }
+
+    @Override
+    public void destroy() {
+    }
+
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java
new file mode 100644
index 0000000..c633917
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.telemetry;
+
+import lombok.Data;
+import org.thingsboard.rule.engine.api.NodeConfiguration;
+import org.thingsboard.server.common.data.DataConstants;
+
+@Data
+public class TbMsgAttributesNodeConfiguration implements NodeConfiguration<TbMsgAttributesNodeConfiguration> {
+
+    private String scope;
+
+    @Override
+    public TbMsgAttributesNodeConfiguration defaultConfiguration() {
+        TbMsgAttributesNodeConfiguration configuration = new TbMsgAttributesNodeConfiguration();
+        configuration.setScope(DataConstants.SERVER_SCOPE);
+        return configuration;
+    }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java
index 05ad49c..c221e03 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java
@@ -20,7 +20,6 @@ import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
 import org.thingsboard.rule.engine.TbNodeUtils;
 import org.thingsboard.rule.engine.api.*;
 import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader;
@@ -60,7 +59,7 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode {
     @Override
     protected ListenableFuture<TbMsg> transform(TbContext ctx, TbMsg msg) {
         ListenableFuture<? extends EntityId> newOriginator = getNewOriginator(ctx, msg.getOriginator());
-        return Futures.transform(newOriginator, (Function<EntityId, TbMsg>) n -> new TbMsg(msg.getId(), msg.getType(), n, msg.getMetaData(), msg.getData()));
+        return Futures.transform(newOriginator, (Function<EntityId, TbMsg>) n -> ctx.transformMsg(msg, msg.getType(), n, msg.getMetaData(), msg.getData()));
     }
 
     private ListenableFuture<? extends EntityId> getNewOriginator(TbContext ctx, EntityId original) {
diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css
index b1728b1..b5109b5 100644
--- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css
+++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css
@@ -1,2 +1,2 @@
-.tb-message-type-autocomplete .tb-not-found{display:block;line-height:1.5;height:48px}.tb-message-type-autocomplete .tb-not-found .tb-no-entries{line-height:48px}.tb-message-type-autocomplete li{height:auto!important;white-space:normal!important}.tb-generator-config tb-json-content.tb-message-body,.tb-generator-config tb-json-object-edit.tb-metadata-json{height:200px;display:block}.tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}.tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}.tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}.tb-kv-map-config .body .row{padding-top:5px;max-height:40px}.tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}.tb-kv-map-config .body md-input-container.cell{margin:0;max-height:40px}.tb-kv-map-config .body .md-button{margin:0}
+.tb-message-type-autocomplete .tb-not-found{display:block;line-height:1.5;height:48px}.tb-message-type-autocomplete .tb-not-found .tb-no-entries{line-height:48px}.tb-message-type-autocomplete li{height:auto!important;white-space:normal!important}.tb-generator-config tb-json-content.tb-message-body,.tb-generator-config tb-json-object-edit.tb-metadata-json{height:200px;display:block}.tb-mqtt-config .tb-credentials-panel-group .tb-panel-title{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;min-width:90px}@media (min-width:960px){.tb-mqtt-config .tb-credentials-panel-group .tb-panel-title{min-width:180px}}.tb-mqtt-config .tb-credentials-panel-group .tb-panel-prompt{font-size:14px;color:rgba(0,0,0,.87);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tb-mqtt-config .tb-credentials-panel-group.disabled .tb-panel-prompt,.tb-mqtt-config .tb-credentials-panel-group.disabled .tb-panel-title{color:rgba(0,0,0,.38)}.tb-mqtt-config .tb-credentials-panel-group md-icon.md-expansion-panel-icon{margin-right:0}.tb-mqtt-config .tb-container{width:100%}.tb-mqtt-config .dropdown-messages .tb-error-message{padding:5px 0 0}.tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}.tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}.tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}.tb-kv-map-config .body .row{padding-top:5px;max-height:40px}.tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}.tb-kv-map-config .body md-input-container.cell{margin:0;max-height:40px}.tb-kv-map-config .body .md-button{margin:0}
 /*# sourceMappingURL=rulenode-core-config.css.map*/
\ No newline at end of file
diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
index edf7ddc..ec7374c 100644
--- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
+++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
@@ -1,3 +1,4 @@
-!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),r=e[t[0]];return function(e,t,a){r.apply(this,[e,t,a].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(40)},function(e,t){},1,1,function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-create-condition</label> <tb-js-func ng-model=configuration.createConditionJs function-name=isAlarm function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=\"testConditionJs($event, true)\" class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-condition-function' | translate }} </md-button> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-clear-condition</label> <tb-js-func ng-model=configuration.clearConditionJs function-name=isCleared function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=\"testConditionJs($event, false)\" class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-condition-function' | translate }} </md-button> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <section layout=column layout-gt-sm=row> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-severity</label> <md-select required name=severity ng-model=configuration.severity> <md-option ng-repeat=\"(severityKey, severity) in types.alarmSeverity\" ng-value=severityKey> {{ severity.name | translate}} </md-option> </md-select> <div ng-messages=alarmConfigForm.severity.$error> <div ng-message=required translate>tb.rulenode.alarm-severity-required</div> </div> </md-input-container> </section> <md-checkbox aria-label=\"{{ 'tb.rulenode.propagate' | translate }}\" ng-model=configuration.propagate>{{ 'tb.rulenode.propagate' | translate }} </md-checkbox> </section> "},function(e,t){e.exports=" <section class=tb-generator-config ng-form name=generatorConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.message-count</label> <input ng-required=true type=number step=1 name=messageCount ng-model=configuration.msgCount min=0> <div ng-messages=generatorConfigForm.messageCount.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.message-count-required</div> <div ng-message=min translate>tb.rulenode.min-message-count-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=1> <div ng-messages=generatorConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-seconds-message</div> </div> </md-input-container> <div layout=column> <label class=tb-small>{{ 'tb.rulenode.originator' | translate }}</label> <tb-entity-select the-form=generatorConfigForm tb-required=false ng-model=originator> </tb-entity-select> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.generate</label> <tb-js-func ng-model=configuration.jsScript function-name=Generate function-args=\"{{ ['prevMsg', 'prevMetadata', 'prevMsgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-generator-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.to-string</label> <tb-js-func ng-model=configuration.jsScript function-name=ToString function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-to-string-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section ng-form name=telemetryConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.default-ttl</label> <input ng-required=true type=number step=1 name=defaultTTL ng-model=configuration.defaultTTL min=0> <div ng-messages=telemetryConfigForm.defaultTTL.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.default-ttl-required</div> <div ng-message=min translate>tb.rulenode.min-default-ttl-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},8,function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" ng-class="{\'tb-required\': required}">tb.rulenode.message-types-filter</label> <md-chips id=message_type_chips ng-required=required readonly=readonly ng-model=messageTypes md-autocomplete-snap md-transform-chip=transformMessageTypeChip($chip) md-require-match=false> <md-autocomplete id=message_type md-no-cache=true md-selected-item=selectedMessageType md-search-text=messageTypeSearchText md-items="item in messageTypesSearch(messageTypeSearchText)" md-item-text=item.name md-min-length=0 placeholder="{{\'tb.rulenode.message-type\' | translate }}" md-menu-class=tb-message-type-autocomplete> <span md-highlight-text=messageTypeSearchText md-highlight-flags=^i>{{item}}</span> <md-not-found> <div class=tb-not-found> <div class=tb-no-entries ng-if="!messageTypeSearchText || !messageTypeSearchText.length"> <span translate>tb.rulenode.no-message-types-found</span> </div> <div ng-if="messageTypeSearchText && messageTypeSearchText.length"> <span translate translate-values=\'{ messageType: "{{messageTypeSearchText | truncate:true:6:&apos;...&apos;}}" }\'>tb.rulenode.no-message-type-matching</span> <span> <a translate ng-click="createMessageType($event, \'#message_type_chips\')">tb.rulenode.create-new-message-type</a> </span> </div> </div> </md-not-found> </md-autocomplete> <md-chip-template> <span>{{$chip.name}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=messageTypes class=tb-error-message>tb.rulenode.message-types-required</div> </div> </section>'},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-filter-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.switch</label> <tb-js-func ng-model=configuration.jsScript function-name=Switch function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-switch-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-kv-map-config layout=column> <div class=header flex layout=row> <span class=cell flex translate>{{ keyText }}</span> <span class=cell flex translate>{{ valText }}</span> <span ng-show=!disabled style=width:52px>&nbsp</span> </div> <div class=body> <div class=row ng-form name=kvForm flex layout=row layout-align="start center" ng-repeat="keyVal in kvList track by $index"> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ keyText | translate }}" ng-required=true name=key ng-model=keyVal.key> <div ng-messages=kvForm.key.$error> <div translate ng-message=required>{{keyRequiredText}}</div> </div> </md-input-container> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ valText | translate }}" ng-required=true name=value ng-model=keyVal.value> <div ng-messages=kvForm.value.$error> <div translate ng-message=required>{{valRequiredText}}</div> </div> </md-input-container> <md-button ng-show=!disabled ng-disabled=loading class="md-icon-button md-primary" ng-click=removeKeyVal($index) aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.remove-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.delete\' | translate }}" class=material-icons> close </md-icon> </md-button> </div> </div> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=kvMap class=tb-error-message>{{requiredText}}</div> </div> <div> <md-button ng-show=!disabled ng-disabled=loading class="md-primary md-raised" ng-click=addKeyVal() aria-label="{{ \'action.add\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.add-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.add\' | translate }}" class=material-icons> add </md-icon> {{ \'action.add\' | translate }} </md-button> </div> </section> '},function(e,t){e.exports=" <section layout=column> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder=\"{{ 'tb.rulenode.unlimited-level' | translate }}\" ng-model=query.maxLevel aria-label=\"{{ 'tb.rulenode.max-relation-level' | translate }}\"> </md-input-container> </div> <div class=md-caption style=padding-bottom:10px;color:rgba(0,0,0,.57) translate>relation.relation-filters</div> <tb-relation-filters ng-model=query.filters> </tb-relation-filters> </section> "},function(e,t){e.exports=' <section layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.originator-source</label> <md-select required ng-model=configuration.originatorSource> <md-option ng-repeat="source in ruleNodeTypes.originatorSource" ng-value=source.value> {{ source.name | translate}} </md-option> </md-select> </md-input-container> <section layout=column ng-if="configuration.originatorSource == ruleNodeTypes.originatorSource.RELATED.value"> <label translate class="tb-title tb-required">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> </section> <md-checkbox aria-label="{{ \'tb.rulenode.clone-message\' | translate }}" ng-model=configuration.startNewChain>{{ \'tb.rulenode.clone-message\' | translate }} </md-checkbox> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.transform</label> <tb-js-func ng-model=configuration.jsScript function-name=Transform function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-transformer-function' | translate }} </md-button> </div> <md-checkbox aria-label=\"{{ 'tb.rulenode.clone-message' | translate }}\" ng-model=configuration.startNewChain>{{ 'tb.rulenode.clone-message' | translate }} </md-checkbox> </section> "},function(e,t){e.exports=" <section ng-form name=toEmailConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.from-template</label> <textarea ng-required=true name=fromTemplate ng-model=configuration.fromTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.fromTemplate.$error> <div ng-message=required translate>tb.rulenode.from-template-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.to-template</label> <textarea ng-required=true name=toTemplate ng-model=configuration.toTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.toTemplate.$error> <div ng-message=required translate>tb.rulenode.to-template-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.cc-template</label> <textarea name=ccTemplate ng-model=configuration.ccTemplate rows=2></textarea> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bcc-template</label> <textarea name=ccTemplate ng-model=configuration.bccTemplate rows=2></textarea> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.subject-template</label> <textarea ng-required=true name=subjectTemplate ng-model=configuration.subjectTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.subjectTemplate.$error> <div ng-message=required translate>tb.rulenode.subject-template-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.body-template</label> <textarea ng-required=true name=bodyTemplate ng-model=configuration.bodyTemplate rows=6></textarea> <div ng-messages=toEmailConfigForm.bodyTemplate.$error> <div ng-message=required translate>tb.rulenode.body-template-required</div> </div> </md-input-container> </section> "},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,r){var a=function(a,i,l,s){var u=o.default;i.html(u),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testConditionJs=function(e,n){var i=angular.copy(n?a.configuration.createConditionJs:a.configuration.clearConditionJs),o={temperature:22.4,humidity:78},l={sensorType:"temperature"};r.testNodeScript(e,i,"filter",t.instant("tb.rulenode.condition")+"",n?"isAlarm":"isCleared",["msg","metadata","msgType"],o,l,"POST_TELEMETRY").then(function(e){n?a.configuration.createConditionJs=e:a.configuration.clearConditionJs=e,s.$setDirty()})},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};r.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(4),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,r){var a=function(a,i,l,s){var u=o.default;i.html(u),a.types=n,a.originator=null,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue,a.configuration.originatorId&&a.configuration.originatorType?a.originator={id:a.configuration.originatorId,entityType:a.configuration.originatorType}:a.originator=null,a.$watch("originator",function(e,t){angular.equals(e,t)||(a.originator?(s.$viewValue.originatorId=a.originator.id,s.$viewValue.originatorType=a.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},a.testScript=function(e){var n=angular.copy(a.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};r.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],i,o,"DebugMsg").then(function(e){a.configuration.jsScript=e,s.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(1);var i=n(5),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(24),i=r(a),o=n(21),l=r(o),s=n(20),u=r(s),d=n(23),c=r(d);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTelemetryConfig",i.default).directive("tbActionNodeGeneratorConfig",l.default).directive("tbActionNodeAlarmConfig",u.default).directive("tbActionNodeLogConfig",c.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,a,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(6),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(7),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(8),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(27),i=r(a),o=n(28),l=r(o),s=n(25),u=r(s),d=n(29),c=r(d);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeRelatedAttributesConfig",l.default).directive("tbEnrichmentNodeCustomerAttributesConfig",u.default).directive("tbEnrichmentNodeTenantAttributesConfig",c.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(9),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(10),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(11),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(32),i=r(a),o=n(31),l=r(o),s=n(33),u=r(s);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){function s(){if(l.$viewValue){for(var e=[],t=0;t<r.messageTypes.length;t++)e.push(r.messageTypes[t].value);l.$viewValue.messageTypes=e,u()}}function u(){if(r.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var d=o.default;a.html(d),r.selectedMessageType=null,r.messageTypeSearchText=null,r.ngModelCtrl=l;var c=[];for(var m in n.messageType){var g={name:n.messageType[m].name,value:n.messageType[m].value};c.push(g)}r.transformMessageTypeChip=function(e){var n,r=t("filter")(c,{name:e},!0);return n=r&&r.length?angular.copy(r[0]):{name:e,value:e}},r.messageTypesSearch=function(e){var n=e?t("filter")(c,{name:e}):c;return n.map(function(e){return e.name})},r.createMessageType=function(e,t){var n=angular.element(t,a)[0].firstElementChild,r=angular.element(n),i=r.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),r.scope().$mdChipsCtrl.appendChip(i.trim()),r.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var a=0;a<e.messageTypes.length;a++){var i=e.messageTypes[a];n.messageType[i]?t.push(angular.copy(n.messageType[i])):t.push({name:i,value:i})}r.messageTypes=t,r.$watch("messageTypes",function(e,t){angular.equals(e,t)||s()},!0)},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:r}}a.$inject=["$compile","$filter","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(2);var i=n(12),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript),i={passed:12,name:"Vit",bigObj:{prop:42}},o={temp:10};n.testNodeScript(e,a,"filter",t.instant("tb.rulenode.filter")+"","Filter",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(13),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,a,"switch",t.instant("tb.rulenode.switch")+"","Switch",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(14),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){function i(e){e>-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),a.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var d=o.default;n.html(d),t.ngModelCtrl=a,t.removeKeyVal=i,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),u()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(15),o=r(i);n(3)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(16),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(17),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(36),i=r(a),o=n(38),l=r(o),s=n(39),u=r(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",u.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,a,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(18),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(19),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{
-default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(43),i=r(a),o=n(30),l=r(o),s=n(26),u=r(s),d=n(37),c=r(d),m=n(22),g=r(m),f=n(35),p=r(f),b=n(34),v=r(b),y=n(42),T=r(y);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,g.default]).directive("tbRelationsQueryConfig",p.default).directive("tbKvMapConfig",v.default).config(T.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","relations-query":"Relations query","max-relation-level":"Max relation level","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","to-template":"To Template","to-template-required":"To Template is required","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","body-template":"Body Template","body-template-required":"Body Template is required"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};angular.merge(e.en_US,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){(0,o.default)(t);for(var n in t){var r=t[n];e.translations(n,r)}}a.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(41),o=r(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{messageType:{POST_ATTRIBUTES:{name:"Post attributes",value:"POST_ATTRIBUTES"},POST_TELEMETRY:{name:"Post telemetry",value:"POST_TELEMETRY"},RPC_REQUEST:{name:"RPC Request",value:"RPC_REQUEST"}},originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"}}}).name}]));
+!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),r=e[t[0]];return function(e,t,a){r.apply(this,[e,t,a].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(60)},function(e,t){},1,1,1,function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-create-condition</label> <tb-js-func ng-model=configuration.createConditionJs function-name=isAlarm function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=\"testConditionJs($event, true)\" class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-condition-function' | translate }} </md-button> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-clear-condition</label> <tb-js-func ng-model=configuration.clearConditionJs function-name=isCleared function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=\"testConditionJs($event, false)\" class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-condition-function' | translate }} </md-button> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <section layout=column layout-gt-sm=row> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-severity</label> <md-select required name=severity ng-model=configuration.severity> <md-option ng-repeat=\"(severityKey, severity) in types.alarmSeverity\" ng-value=severityKey> {{ severity.name | translate}} </md-option> </md-select> <div ng-messages=alarmConfigForm.severity.$error> <div ng-message=required translate>tb.rulenode.alarm-severity-required</div> </div> </md-input-container> </section> <md-checkbox aria-label=\"{{ 'tb.rulenode.propagate' | translate }}\" ng-model=configuration.propagate>{{ 'tb.rulenode.propagate' | translate }} </md-checkbox> </section> "},function(e,t){e.exports=' <section ng-form name=attributesConfigForm layout=column> <md-input-container class=md-block> <label translate>attribute.attributes-scope</label> <md-select ng-model=configuration.scope ng-disabled=$root.loading> <md-option ng-repeat="scope in types.attributesScope" ng-value=scope.value> {{scope.name | translate}} </md-option> </md-select> </md-input-container> </section> '},function(e,t){e.exports=" <section class=tb-generator-config ng-form name=generatorConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.message-count</label> <input ng-required=true type=number step=1 name=messageCount ng-model=configuration.msgCount min=0> <div ng-messages=generatorConfigForm.messageCount.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.message-count-required</div> <div ng-message=min translate>tb.rulenode.min-message-count-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=1> <div ng-messages=generatorConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-seconds-message</div> </div> </md-input-container> <div layout=column> <label class=tb-small>{{ 'tb.rulenode.originator' | translate }}</label> <tb-entity-select the-form=generatorConfigForm tb-required=false ng-model=originator> </tb-entity-select> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.generate</label> <tb-js-func ng-model=configuration.jsScript function-name=Generate function-args=\"{{ ['prevMsg', 'prevMetadata', 'prevMsgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-generator-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section ng-form name=kafkaConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=kafkaConfigForm.topicPattern.$error> <div ng-message=required translate>tb.rulenode.topic-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bootstrap-servers</label> <input ng-required=true name=bootstrapServers ng-model=configuration.bootstrapServers> <div ng-messages=kafkaConfigForm.bootstrapServers.$error> <div ng-message=required translate>tb.rulenode.bootstrap-servers-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.retries</label> <input type=number step=1 name=retries ng-model=configuration.retries min=0> <div ng-messages=kafkaConfigForm.retries.$error> <div ng-message=min translate>tb.rulenode.min-retries-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.batch-size-bytes</label> <input type=number step=1 name=batchSize ng-model=configuration.batchSize min=0> <div ng-messages=kafkaConfigForm.batchSize.$error> <div ng-message=min translate>tb.rulenode.min-batch-size-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.linger-ms</label> <input type=number step=1 name=linger ng-model=configuration.linger min=0> <div ng-messages=kafkaConfigForm.linger.$error> <div ng-message=min translate>tb.rulenode.min-linger-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.buffer-memory-bytes</label> <input type=number step=1 name=bufferMemory ng-model=configuration.bufferMemory min=0> <div ng-messages=kafkaConfigForm.bufferMemory.$error> <div ng-message=min translate>tb.rulenode.min-buffer-memory-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.acks</label> <md-select ng-model=configuration.acks ng-disabled=$root.loading> <md-option ng-repeat="ackValue in ackValues" ng-value=ackValue> {{ ackValue }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.key-serializer</label> <input ng-required=true name=keySerializer ng-model=configuration.keySerializer> <div ng-messages=kafkaConfigForm.keySerializer.$error> <div ng-message=required translate>tb.rulenode.key-serializer-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.value-serializer</label> <input ng-required=true name=valueSerializer ng-model=configuration.valueSerializer> <div ng-messages=kafkaConfigForm.valueSerializer.$error> <div ng-message=required translate>tb.rulenode.value-serializer-required</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.other-properties</label> <tb-kv-map-config ng-model=configuration.otherProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.to-string</label> <tb-js-func ng-model=configuration.jsScript function-name=ToString function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-to-string-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-mqtt-config ng-form name=mqttConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=mqttConfigForm.topicPattern.$error> <div translate ng-message=required>tb.rulenode.topic-pattern-required</div> </div> </md-input-container> <div flex layout=column layout-gt-sm=row> <md-input-container flex=60 class=md-block> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=mqttConfigForm.host.$error> <div translate ng-message=required>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.port> <div ng-messages=mqttConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.port-required</div> <div translate ng-message=min>tb.rulenode.port-range</div> <div translate ng-message=max>tb.rulenode.port-range</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.connect-timeout</label> <input type=number step=1 min=1 max=200 ng-required=true name=connectTimeoutSec ng-model=configuration.connectTimeoutSec> <div ng-messages=mqttConfigForm.connectTimeoutSec.$error> <div translate ng-message=required>tb.rulenode.connect-timeout-required</div> <div translate ng-message=min>tb.rulenode.connect-timeout-range</div> <div translate ng-message=max>tb.rulenode.connect-timeout-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.client-id</label> <input name=clientId ng-model=configuration.clientId> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-ssl\' | translate }}" ng-model=configuration.ssl> {{ \'tb.rulenode.enable-ssl\' | translate }} </md-checkbox> <md-expansion-panel-group class=tb-credentials-panel-group ng-class="{\'disabled\': $root.loading || readonly}" md-component-id=credentialsPanelGroup> <md-expansion-panel md-component-id=credentialsPanel> <md-expansion-panel-collapsed> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-collapsed> <md-expansion-panel-expanded> <md-expansion-panel-header ng-click="$mdExpansionPanel(\'credentialsPanel\').collapse()"> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-header> <md-expansion-panel-content> <div layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.credentials-type</label> <md-select ng-required=true name=credentialsType ng-model=configuration.credentials.type ng-disabled="$root.loading || readonly" ng-change=credentialsTypeChanged()> <md-option ng-repeat="(credentialsType, credentialsValue) in ruleNodeTypes.mqttCredentialTypes" ng-value=credentialsValue.value> {{credentialsValue.name | translate}} </md-option> </md-select> <div ng-messages=mqttConfigForm.credentialsType.$error> <div translate ng-message=required>tb.rulenode.credentials-type-required</div> </div> </md-input-container> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes.basic.value"> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input ng-required=true name=mqttUsername ng-model=configuration.credentials.username> <div ng-messages=mqttConfigForm.mqttUsername.$error> <div translate ng-message=required>tb.rulenode.username-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input type=password ng-required=true name=mqttPassword ng-model=configuration.credentials.password> <div ng-messages=mqttConfigForm.mqttPassword.$error> <div translate ng-message=required>tb.rulenode.password-required</div> </div> </md-input-container> </section> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes[\'cert.PEM\'].value" class=dropdown-section> <div class=tb-container ng-class="configuration.credentials.caCertFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.ca-cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'caCert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'caCert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=caCertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=caCertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.caCertFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.caCertFileName>{{configuration.credentials.caCertFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.certFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'Cert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'Cert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=CertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=CertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.certFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.certFileName>{{configuration.credentials.certFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.privateKeyFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.private-key</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'privateKey\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'privateKey\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=privateKeySelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=privateKeySelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.privateKeyFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.privateKeyFileName>{{configuration.credentials.privateKeyFileName}}</div> </div> <md-input-container class=md-block> <label translate>tb.rulenode.private-key-password</label> <input type=password name=privateKeyPassword ng-model=configuration.credentials.password> </md-input-container> </section> </div> </md-expansion-panel-content> </md-expansion-panel-expanded> </md-expansion-panel> </md-expansion-panel-group> </section>'},function(e,t){e.exports=' <section ng-form name=rabbitMqConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.exchange-name-pattern</label> <input name=exchangeNamePattern ng-model=configuration.exchangeNamePattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.routing-key-pattern</label> <input name=routingKeyPattern ng-model=configuration.routingKeyPattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.message-properties</label> <md-select ng-model=configuration.messageProperties ng-disabled="$root.loading || readonly"> <md-option ng-repeat="property in messageProperties" ng-value=property> {{ property }} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=rabbitMqConfigForm.host.$error> <div ng-message=required translate>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.port</label> <input ng-required=true type=number step=1 name=port ng-model=configuration.port min=0 max=65535> <div ng-messages=rabbitMqConfigForm.port.$error> <div ng-message=required translate>tb.rulenode.port-required</div> <div ng-message=min translate>tb.rulenode.port-range</div> <div ng-message=max translate>tb.rulenode.port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.virtual-host</label> <input name=virtualHost ng-model=configuration.virtualHost> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=virtualHost ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=virtualHost type=password ng-model=configuration.password> </md-input-container> <md-input-container class=md-block> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.automatic-recovery\' | translate }}" ng-model=ruleNode.automaticRecoveryEnabled>{{ \'tb.rulenode.automatic-recovery\' | translate }} </md-checkbox> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.connection-timeout-ms</label> <input type=number step=1 name=connectionTimeout ng-model=configuration.connectionTimeout min=0> <div ng-messages=rabbitMqConfigForm.connectionTimeout.$error> <div ng-message=min translate>tb.rulenode.min-connection-timeout-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.handshake-timeout-ms</label> <input type=number step=1 name=handshakeTimeout ng-model=configuration.handshakeTimeout min=0> <div ng-messages=rabbitMqConfigForm.handshakeTimeout.$error> <div ng-message=min translate>tb.rulenode.min-handshake-timeout-ms-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.client-properties</label> <tb-kv-map-config ng-model=configuration.clientProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=' <section ng-form name=restApiCallConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.endpoint-url-pattern</label> <input ng-required=true name=endpointUrlPattern ng-model=configuration.restEndpointUrlPattern> <div ng-messages=restApiCallConfigForm.endpointUrlPattern.$error> <div ng-message=required translate>tb.rulenode.endpoint-url-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.request-method</label> <md-select ng-model=configuration.requestMethod ng-disabled=$root.loading> <md-option ng-repeat="type in ruleNodeTypes.httpRequestType" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <label translate class=tb-title>tb.rulenode.headers</label> <tb-kv-map-config ng-model=configuration.headers ng-required=false key-text="\'tb.rulenode.header\'" key-required-text="\'tb.rulenode.header-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section ng-form name=rpcReplyConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.request-id-metadata-attribute</label> <input name=requestIdMetaDataAttribute ng-model=configuration.requestIdMetaDataAttribute> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=rpcRequestConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.timeout-sec</label> <input ng-required=true type=number step=1 name=timeoutInSeconds ng-model=configuration.timeoutInSeconds min=0> <div ng-messages=rpcRequestConfigForm.timeoutInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.timeout-required</div> <div ng-message=min translate>tb.rulenode.min-timeout-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=snsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-arn-pattern</label> <input ng-required=true name=topicArnPattern ng-model=configuration.topicArnPattern> <div ng-messages=snsConfigForm.topicArnPattern.$error> <div ng-message=required translate>tb.rulenode.topic-arn-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=sqsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.queue-type</label> <md-select ng-model=configuration.queueType ng-disabled="$root.loading || readonly"> <md-option ng-repeat="type in ruleNodeTypes.sqsQueueType" ng-value=type.value> {{ type.name | translate }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.queue-url-pattern</label> <input ng-required=true name=queueUrlPattern ng-model=configuration.queueUrlPattern> <div ng-messages=sqsConfigForm.queueUrlPattern.$error> <div ng-message=required translate>tb.rulenode.queue-url-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block ng-if="configuration.queueType == ruleNodeTypes.sqsQueueType.STANDARD.value"> <label translate>tb.rulenode.delay-seconds</label> <input type=number step=1 name=delaySeconds ng-model=configuration.delaySeconds min=0 max=900> <div ng-messages=sqsConfigForm.delaySeconds.$error> <div ng-message=min translate>tb.rulenode.min-delay-seconds-message</div> <div ng-message=max translate>tb.rulenode.max-delay-seconds-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.message-attributes</label> <tb-kv-map-config ng-model=configuration.messageAttributes ng-required=false key-text="\'tb.rulenode.name\'" key-required-text="\'tb.rulenode.name-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> '},function(e,t){e.exports=" <section ng-form name=timeseriesConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.default-ttl</label> <input ng-required=true type=number step=1 name=defaultTTL ng-model=configuration.defaultTTL min=0> <div ng-messages=timeseriesConfigForm.defaultTTL.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.default-ttl-required</div> <div ng-message=min translate>tb.rulenode.min-default-ttl-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},18,function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" ng-class="{\'tb-required\': required}">tb.rulenode.message-types-filter</label> <md-chips id=message_type_chips ng-required=required readonly=readonly ng-model=messageTypes md-autocomplete-snap md-transform-chip=transformMessageTypeChip($chip) md-require-match=false> <md-autocomplete id=message_type md-no-cache=true md-selected-item=selectedMessageType md-search-text=messageTypeSearchText md-items="item in messageTypesSearch(messageTypeSearchText)" md-item-text=item.name md-min-length=0 placeholder="{{\'tb.rulenode.message-type\' | translate }}" md-menu-class=tb-message-type-autocomplete> <span md-highlight-text=messageTypeSearchText md-highlight-flags=^i>{{item}}</span> <md-not-found> <div class=tb-not-found> <div class=tb-no-entries ng-if="!messageTypeSearchText || !messageTypeSearchText.length"> <span translate>tb.rulenode.no-message-types-found</span> </div> <div ng-if="messageTypeSearchText && messageTypeSearchText.length"> <span translate translate-values=\'{ messageType: "{{messageTypeSearchText | truncate:true:6:&apos;...&apos;}}" }\'>tb.rulenode.no-message-type-matching</span> <span> <a translate ng-click="createMessageType($event, \'#message_type_chips\')">tb.rulenode.create-new-message-type</a> </span> </div> </div> </md-not-found> </md-autocomplete> <md-chip-template> <span>{{$chip.name}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=messageTypes class=tb-error-message>tb.rulenode.message-types-required</div> </div> </section>'},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-filter-function' | translate }} </md-button> </div> </section> ";
+},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.switch</label> <tb-js-func ng-model=configuration.jsScript function-name=Switch function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-switch-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-kv-map-config layout=column> <div class=header flex layout=row> <span class=cell flex translate>{{ keyText }}</span> <span class=cell flex translate>{{ valText }}</span> <span ng-show=!disabled style=width:52px>&nbsp</span> </div> <div class=body> <div class=row ng-form name=kvForm flex layout=row layout-align="start center" ng-repeat="keyVal in kvList track by $index"> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ keyText | translate }}" ng-required=true name=key ng-model=keyVal.key> <div ng-messages=kvForm.key.$error> <div translate ng-message=required>{{keyRequiredText}}</div> </div> </md-input-container> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ valText | translate }}" ng-required=true name=value ng-model=keyVal.value> <div ng-messages=kvForm.value.$error> <div translate ng-message=required>{{valRequiredText}}</div> </div> </md-input-container> <md-button ng-show=!disabled ng-disabled=loading class="md-icon-button md-primary" ng-click=removeKeyVal($index) aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.remove-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.delete\' | translate }}" class=material-icons> close </md-icon> </md-button> </div> </div> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=kvMap class=tb-error-message>{{requiredText}}</div> </div> <div> <md-button ng-show=!disabled ng-disabled=loading class="md-primary md-raised" ng-click=addKeyVal() aria-label="{{ \'action.add\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.add-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.add\' | translate }}" class=material-icons> add </md-icon> {{ \'action.add\' | translate }} </md-button> </div> </section> '},function(e,t){e.exports=" <section layout=column> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder=\"{{ 'tb.rulenode.unlimited-level' | translate }}\" ng-model=query.maxLevel aria-label=\"{{ 'tb.rulenode.max-relation-level' | translate }}\"> </md-input-container> </div> <div class=md-caption style=padding-bottom:10px;color:rgba(0,0,0,.57) translate>relation.relation-filters</div> <tb-relation-filters ng-model=query.filters> </tb-relation-filters> </section> "},function(e,t){e.exports=' <section layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.originator-source</label> <md-select required ng-model=configuration.originatorSource> <md-option ng-repeat="source in ruleNodeTypes.originatorSource" ng-value=source.value> {{ source.name | translate}} </md-option> </md-select> </md-input-container> <section layout=column ng-if="configuration.originatorSource == ruleNodeTypes.originatorSource.RELATED.value"> <label translate class="tb-title tb-required">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> </section> <md-checkbox aria-label="{{ \'tb.rulenode.clone-message\' | translate }}" ng-model=configuration.startNewChain>{{ \'tb.rulenode.clone-message\' | translate }} </md-checkbox> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.transform</label> <tb-js-func ng-model=configuration.jsScript function-name=Transform function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-transformer-function' | translate }} </md-button> </div> <md-checkbox aria-label=\"{{ 'tb.rulenode.clone-message' | translate }}\" ng-model=configuration.startNewChain>{{ 'tb.rulenode.clone-message' | translate }} </md-checkbox> </section> "},function(e,t){e.exports=" <section ng-form name=toEmailConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.from-template</label> <textarea ng-required=true name=fromTemplate ng-model=configuration.fromTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.fromTemplate.$error> <div ng-message=required translate>tb.rulenode.from-template-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.to-template</label> <textarea ng-required=true name=toTemplate ng-model=configuration.toTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.toTemplate.$error> <div ng-message=required translate>tb.rulenode.to-template-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.cc-template</label> <textarea name=ccTemplate ng-model=configuration.ccTemplate rows=2></textarea> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bcc-template</label> <textarea name=ccTemplate ng-model=configuration.bccTemplate rows=2></textarea> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.subject-template</label> <textarea ng-required=true name=subjectTemplate ng-model=configuration.subjectTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.subjectTemplate.$error> <div ng-message=required translate>tb.rulenode.subject-template-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.body-template</label> <textarea ng-required=true name=bodyTemplate ng-model=configuration.bodyTemplate rows=6></textarea> <div ng-messages=toEmailConfigForm.bodyTemplate.$error> <div ng-message=required translate>tb.rulenode.body-template-required</div> </div> </md-input-container> </section> "},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,r){var a=function(a,i,l,s){var u=o.default;i.html(u),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testConditionJs=function(e,n){var i=angular.copy(n?a.configuration.createConditionJs:a.configuration.clearConditionJs),o={temperature:22.4,humidity:78},l={sensorType:"temperature"};r.testNodeScript(e,i,"filter",t.instant("tb.rulenode.condition")+"",n?"isAlarm":"isCleared",["msg","metadata","msgType"],o,l,"POST_TELEMETRY").then(function(e){n?a.configuration.createConditionJs=e:a.configuration.clearConditionJs=e,s.$setDirty()})},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};r.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(5),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(6),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,r){var a=function(a,i,l,s){var u=o.default;i.html(u),a.types=n,a.originator=null,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue,a.configuration.originatorId&&a.configuration.originatorType?a.originator={id:a.configuration.originatorId,entityType:a.configuration.originatorType}:a.originator=null,a.$watch("originator",function(e,t){angular.equals(e,t)||(a.originator?(s.$viewValue.originatorId=a.originator.id,s.$viewValue.originatorType=a.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},a.testScript=function(e){var n=angular.copy(a.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};r.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],i,o,"DebugMsg").then(function(e){a.configuration.jsScript=e,s.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(1);var i=n(7),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(43),i=r(a),o=n(31),l=r(o),s=n(32),u=r(s),d=n(30),c=r(d),m=n(35),g=r(m),p=n(39),f=r(p),b=n(40),v=r(b),y=n(38),q=r(y),T=n(34),h=r(T),$=n(41),k=r($),w=n(42),C=r(w),x=n(37),_=r(x),M=n(36),S=r(M);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",i.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",u.default).directive("tbActionNodeAlarmConfig",c.default).directive("tbActionNodeLogConfig",g.default).directive("tbActionNodeRpcReplyConfig",f.default).directive("tbActionNodeRpcRequestConfig",v.default).directive("tbActionNodeRestApiCallConfig",q.default).directive("tbActionNodeKafkaConfig",h.default).directive("tbActionNodeSnsConfig",k.default).directive("tbActionNodeSqsConfig",C.default).directive("tbActionNodeRabbitMqConfig",_.default).directive("tbActionNodeMqttConfig",S.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(8),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,a,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(9),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$mdExpansionPanel=t,r.ruleNodeTypes=n,r.credentialsTypeChanged=function(){var e=r.configuration.credentials.type;r.configuration.credentials={},r.configuration.credentials.type=e,r.updateValidity()},r.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){r.$apply(function(){if(n.target.result){l.$setDirty();var a=n.target.result;a&&a.length>0&&("caCert"==t&&(r.configuration.credentials.caCertFileName=e.name,r.configuration.credentials.caCert=a),"privateKey"==t&&(r.configuration.credentials.privateKeyFileName=e.name,r.configuration.credentials.privateKey=a),"Cert"==t&&(r.configuration.credentials.certFileName=e.name,r.configuration.credentials.cert=a)),r.updateValidity()}})},n.readAsText(e.file)},r.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(r.configuration.credentials.caCertFileName=null,r.configuration.credentials.caCert=null),"privateKey"==e&&(r.configuration.credentials.privateKeyFileName=null,r.configuration.credentials.privateKey=null),"Cert"==e&&(r.configuration.credentials.certFileName=null,r.configuration.credentials.cert=null),r.updateValidity()},r.updateValidity=function(){var e=!0,t=r.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:r}}a.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(2);var i=n(10),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(11),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(12),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(13),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(14),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(15),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(16),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(17),o=r(i)},function(e,t){"use strict";function n(e){var t=function(t,n,r,a){n.html("<div></div>"),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(18),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(47),i=r(a),o=n(48),l=r(o),s=n(45),u=r(s),d=n(49),c=r(d);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeRelatedAttributesConfig",l.default).directive("tbEnrichmentNodeCustomerAttributesConfig",u.default).directive("tbEnrichmentNodeTenantAttributesConfig",c.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(19),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(20),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(21),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(52),i=r(a),o=n(51),l=r(o),s=n(53),u=r(s);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){function s(){if(l.$viewValue){for(var e=[],t=0;t<r.messageTypes.length;t++)e.push(r.messageTypes[t].value);l.$viewValue.messageTypes=e,u()}}function u(){if(r.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var d=o.default;a.html(d),r.selectedMessageType=null,r.messageTypeSearchText=null,r.ngModelCtrl=l;var c=[];for(var m in n.messageType){var g={name:n.messageType[m].name,value:n.messageType[m].value};c.push(g)}r.transformMessageTypeChip=function(e){var n,r=t("filter")(c,{name:e},!0);return n=r&&r.length?angular.copy(r[0]):{name:e,value:e}},r.messageTypesSearch=function(e){var n=e?t("filter")(c,{name:e}):c;return n.map(function(e){return e.name})},r.createMessageType=function(e,t){var n=angular.element(t,a)[0].firstElementChild,r=angular.element(n),i=r.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),r.scope().$mdChipsCtrl.appendChip(i.trim()),r.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var a=0;a<e.messageTypes.length;a++){var i=e.messageTypes[a];n.messageType[i]?t.push(angular.copy(n.messageType[i])):t.push({name:i,value:i})}r.messageTypes=t,r.$watch("messageTypes",function(e,t){angular.equals(e,t)||s()},!0)},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:r}}a.$inject=["$compile","$filter","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(3);var i=n(22),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript),i={passed:12,name:"Vit",bigObj:{prop:42}},o={temp:10};n.testNodeScript(e,a,"filter",t.instant("tb.rulenode.filter")+"","Filter",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(23),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,a,"switch",t.instant("tb.rulenode.switch")+"","Switch",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(24),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){function i(e){e>-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),a.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var d=o.default;n.html(d),t.ngModelCtrl=a,t.removeKeyVal=i,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),u()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(25),o=r(i);n(4)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(26),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(27),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(56),i=r(a),o=n(58),l=r(o),s=n(59),u=r(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",u.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,a,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(28),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(29),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(63),i=r(a),o=n(50),l=r(o),s=n(46),u=r(s),d=n(57),c=r(d),m=n(33),g=r(m),p=n(44),f=r(p),b=n(55),v=r(b),y=n(54),q=r(y),T=n(62),h=r(T);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbKvMapConfig",q.default).config(h.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","relations-query":"Relations query","max-relation-level":"Max relation level","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","to-template":"To Template","to-template-required":"To Template is required","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","body-template":"Body Template","body-template-required":"Body Template is required","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","request-method":"Request method",headers:"Headers",header:"Header","header-required":"Header is required",
+value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","message-attributes":"Message attributes","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};angular.merge(e.en_US,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){(0,o.default)(t);for(var n in t){var r=t[n];e.translations(n,r)}}a.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(61),o=r(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{messageType:{POST_ATTRIBUTES_REQUEST:{name:"Post attributes",value:"POST_ATTRIBUTES_REQUEST"},POST_TELEMETRY_REQUEST:{name:"Post telemetry",value:"POST_TELEMETRY_REQUEST"},TO_SERVER_RPC_REQUEST:{name:"RPC Request",value:"TO_SERVER_RPC_REQUEST"}},originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"}},httpRequestType:["GET","POST","PUT","DELETE"],sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}]));
 //# sourceMappingURL=rulenode-core-config.js.map
\ No newline at end of file
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java
index 69c3aca..39a1890 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java
@@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import org.apache.commons.lang3.NotImplementedException;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -30,6 +31,8 @@ import org.thingsboard.rule.engine.api.*;
 import org.thingsboard.server.common.data.alarm.Alarm;
 import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.RuleChainId;
+import org.thingsboard.server.common.data.id.RuleNodeId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.msg.TbMsg;
 import org.thingsboard.server.common.msg.TbMsgMetaData;
@@ -66,16 +69,40 @@ public class TbAlarmNodeTest {
     @Mock
     private ScriptEngine detailsJs;
 
+    private RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased());
+    private RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased());
+
+    private ListeningExecutor dbExecutor;
+
     private EntityId originator = new DeviceId(UUIDs.timeBased());
     private TenantId tenantId = new TenantId(UUIDs.timeBased());
     private TbMsgMetaData metaData = new TbMsgMetaData();
     private String rawJson = "{\"name\": \"Vit\", \"passed\": 5}";
 
+    @Before
+    public void before() {
+        dbExecutor = new ListeningExecutor() {
+            @Override
+            public <T> ListenableFuture<T> executeAsync(Callable<T> task) {
+                try {
+                    return Futures.immediateFuture(task.call());
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
+            @Override
+            public void execute(Runnable command) {
+                command.run();
+            }
+        };
+    }
+
     @Test
     public void newAlarmCanBeCreated() throws ScriptException, IOException {
         initWithScript();
         metaData.putValue("key", "value");
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson);
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L);
 
         when(createJs.executeFilter(msg)).thenReturn(true);
         when(detailsJs.executeJson(msg)).thenReturn(null);
@@ -85,17 +112,22 @@ public class TbAlarmNodeTest {
 
         node.onMsg(ctx, msg);
 
-        ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);
-        verify(ctx).tellNext(captor.capture(), eq("Created"));
-        TbMsg actualMsg = captor.getValue();
+        verify(ctx).tellNext(any(), eq("Created"));
+
+        ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
+        ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class);
+        ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class);
+        ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class);
+        verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture());
 
-        assertEquals("ALARM", actualMsg.getType());
-        assertEquals(originator, actualMsg.getOriginator());
-        assertEquals("value", actualMsg.getMetaData().getValue("key"));
-        assertEquals(Boolean.TRUE.toString(), actualMsg.getMetaData().getValue(IS_NEW_ALARM));
-        assertNotSame(metaData, actualMsg.getMetaData());
+        assertEquals("ALARM", typeCaptor.getValue());
+        assertEquals(originator, originatorCaptor.getValue());
+        assertEquals("value", metadataCaptor.getValue().getValue("key"));
+        assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_NEW_ALARM));
+        assertNotSame(metaData, metadataCaptor.getValue());
 
-        Alarm actualAlarm = new ObjectMapper().readValue(actualMsg.getData().getBytes(), Alarm.class);
+        Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class);
         Alarm expectedAlarm = Alarm.builder()
                 .tenantId(tenantId)
                 .originator(originator)
@@ -115,7 +147,7 @@ public class TbAlarmNodeTest {
     public void shouldCreateScriptThrowsException() throws ScriptException {
         initWithScript();
         metaData.putValue("key", "value");
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson);
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L);
 
         when(createJs.executeFilter(msg)).thenThrow(new NotImplementedException("message"));
 
@@ -128,6 +160,7 @@ public class TbAlarmNodeTest {
         verify(ctx).createJsScriptEngine("CLEAR", "isCleared");
         verify(ctx).createJsScriptEngine("DETAILS", "Details");
         verify(ctx).getJsExecutor();
+        verify(ctx).getDbCallbackExecutor();
 
         verifyNoMoreInteractions(ctx, alarmService, clearJs, detailsJs);
     }
@@ -136,7 +169,7 @@ public class TbAlarmNodeTest {
     public void buildDetailsThrowsException() throws ScriptException, IOException {
         initWithScript();
         metaData.putValue("key", "value");
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson);
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L);
 
         when(createJs.executeFilter(msg)).thenReturn(true);
         when(detailsJs.executeJson(msg)).thenThrow(new NotImplementedException("message"));
@@ -151,6 +184,7 @@ public class TbAlarmNodeTest {
         verify(ctx).createJsScriptEngine("DETAILS", "Details");
         verify(ctx, times(2)).getJsExecutor();
         verify(ctx).getAlarmService();
+        verify(ctx, times(3)).getDbCallbackExecutor();
         verify(ctx).getTenantId();
         verify(alarmService).findLatestByOriginatorAndType(tenantId, originator, "SomeType");
 
@@ -161,7 +195,7 @@ public class TbAlarmNodeTest {
     public void ifAlarmClearedCreateNew() throws ScriptException, IOException {
         initWithScript();
         metaData.putValue("key", "value");
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson);
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L);
 
         Alarm clearedAlarm = Alarm.builder().status(CLEARED_ACK).build();
 
@@ -173,17 +207,23 @@ public class TbAlarmNodeTest {
 
         node.onMsg(ctx, msg);
 
-        ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);
-        verify(ctx).tellNext(captor.capture(), eq("Created"));
-        TbMsg actualMsg = captor.getValue();
+        verify(ctx).tellNext(any(), eq("Created"));
+
+        ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
+        ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class);
+        ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class);
+        ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class);
+        verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture());
 
-        assertEquals("ALARM", actualMsg.getType());
-        assertEquals(originator, actualMsg.getOriginator());
-        assertEquals("value", actualMsg.getMetaData().getValue("key"));
-        assertEquals(Boolean.TRUE.toString(), actualMsg.getMetaData().getValue(IS_NEW_ALARM));
-        assertNotSame(metaData, actualMsg.getMetaData());
+        assertEquals("ALARM", typeCaptor.getValue());
+        assertEquals(originator, originatorCaptor.getValue());
+        assertEquals("value", metadataCaptor.getValue().getValue("key"));
+        assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_NEW_ALARM));
+        assertNotSame(metaData, metadataCaptor.getValue());
 
-        Alarm actualAlarm = new ObjectMapper().readValue(actualMsg.getData().getBytes(), Alarm.class);
+
+        Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class);
         Alarm expectedAlarm = Alarm.builder()
                 .tenantId(tenantId)
                 .originator(originator)
@@ -203,7 +243,7 @@ public class TbAlarmNodeTest {
     public void alarmCanBeUpdated() throws ScriptException, IOException {
         initWithScript();
         metaData.putValue("key", "value");
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson);
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L);
 
         long oldEndDate = System.currentTimeMillis();
         Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).status(ACTIVE_UNACK).severity(WARNING).endTs(oldEndDate).build();
@@ -217,17 +257,22 @@ public class TbAlarmNodeTest {
 
         node.onMsg(ctx, msg);
 
-        ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);
-        verify(ctx).tellNext(captor.capture(), eq("Updated"));
-        TbMsg actualMsg = captor.getValue();
+        verify(ctx).tellNext(any(), eq("Updated"));
+
+        ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
+        ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class);
+        ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class);
+        ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class);
+        verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture());
 
-        assertEquals("ALARM", actualMsg.getType());
-        assertEquals(originator, actualMsg.getOriginator());
-        assertEquals("value", actualMsg.getMetaData().getValue("key"));
-        assertEquals(Boolean.TRUE.toString(), actualMsg.getMetaData().getValue(IS_EXISTING_ALARM));
-        assertNotSame(metaData, actualMsg.getMetaData());
+        assertEquals("ALARM", typeCaptor.getValue());
+        assertEquals(originator, originatorCaptor.getValue());
+        assertEquals("value", metadataCaptor.getValue().getValue("key"));
+        assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_EXISTING_ALARM));
+        assertNotSame(metaData, metadataCaptor.getValue());
 
-        Alarm actualAlarm = new ObjectMapper().readValue(actualMsg.getData().getBytes(), Alarm.class);
+        Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class);
         assertTrue(activeAlarm.getEndTs() > oldEndDate);
         Alarm expectedAlarm = Alarm.builder()
                 .tenantId(tenantId)
@@ -249,7 +294,7 @@ public class TbAlarmNodeTest {
     public void alarmCanBeCleared() throws ScriptException, IOException {
         initWithScript();
         metaData.putValue("key", "value");
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson);
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L);
 
         long oldEndDate = System.currentTimeMillis();
         Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).status(ACTIVE_UNACK).severity(WARNING).endTs(oldEndDate).build();
@@ -263,17 +308,22 @@ public class TbAlarmNodeTest {
 
         node.onMsg(ctx, msg);
 
-        ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);
-        verify(ctx).tellNext(captor.capture(), eq("Cleared"));
-        TbMsg actualMsg = captor.getValue();
+        verify(ctx).tellNext(any(), eq("Cleared"));
+
+        ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
+        ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class);
+        ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class);
+        ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class);
+        verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture());
 
-        assertEquals("ALARM", actualMsg.getType());
-        assertEquals(originator, actualMsg.getOriginator());
-        assertEquals("value", actualMsg.getMetaData().getValue("key"));
-        assertEquals(Boolean.TRUE.toString(), actualMsg.getMetaData().getValue(IS_CLEARED_ALARM));
-        assertNotSame(metaData, actualMsg.getMetaData());
+        assertEquals("ALARM", typeCaptor.getValue());
+        assertEquals(originator, originatorCaptor.getValue());
+        assertEquals("value", metadataCaptor.getValue().getValue("key"));
+        assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_CLEARED_ALARM));
+        assertNotSame(metaData, metadataCaptor.getValue());
 
-        Alarm actualAlarm = new ObjectMapper().readValue(actualMsg.getData().getBytes(), Alarm.class);
+        Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class);
         Alarm expectedAlarm = Alarm.builder()
                 .tenantId(tenantId)
                 .originator(originator)
@@ -307,6 +357,7 @@ public class TbAlarmNodeTest {
             when(ctx.getTenantId()).thenReturn(tenantId);
             when(ctx.getJsExecutor()).thenReturn(executor);
             when(ctx.getAlarmService()).thenReturn(alarmService);
+            when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor);
 
             mockJsExecutor();
 
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java
index 08a22f0..d0b13eb 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java
@@ -27,6 +27,8 @@ import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.mockito.stubbing.Answer;
 import org.thingsboard.rule.engine.api.*;
+import org.thingsboard.server.common.data.id.RuleChainId;
+import org.thingsboard.server.common.data.id.RuleNodeId;
 import org.thingsboard.server.common.msg.TbMsg;
 import org.thingsboard.server.common.msg.TbMsgMetaData;
 
@@ -48,10 +50,13 @@ public class TbJsFilterNodeTest {
     @Mock
     private ScriptEngine scriptEngine;
 
+    private RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased());
+    private RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased());
+
     @Test
     public void falseEvaluationDoNotSendMsg() throws TbNodeException, ScriptException {
         initWithScript();
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}");
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L);
         mockJsExecutor();
         when(scriptEngine.executeFilter(msg)).thenReturn(false);
 
@@ -64,7 +69,7 @@ public class TbJsFilterNodeTest {
     public void exceptionInJsThrowsException() throws TbNodeException, ScriptException {
         initWithScript();
         TbMsgMetaData metaData = new TbMsgMetaData();
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}");
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}", ruleChainId, ruleNodeId, 0L);
         mockJsExecutor();
         when(scriptEngine.executeFilter(msg)).thenThrow(new ScriptException("error"));
 
@@ -77,7 +82,7 @@ public class TbJsFilterNodeTest {
     public void metadataConditionCanBeTrue() throws TbNodeException, ScriptException {
         initWithScript();
         TbMsgMetaData metaData = new TbMsgMetaData();
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}");
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}", ruleChainId, ruleNodeId, 0L);
         mockJsExecutor();
         when(scriptEngine.executeFilter(msg)).thenReturn(true);
 
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java
index a495124..cc4c297 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java
@@ -28,6 +28,8 @@ import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.mockito.stubbing.Answer;
 import org.thingsboard.rule.engine.api.*;
+import org.thingsboard.server.common.data.id.RuleChainId;
+import org.thingsboard.server.common.data.id.RuleNodeId;
 import org.thingsboard.server.common.msg.TbMsg;
 import org.thingsboard.server.common.msg.TbMsgMetaData;
 
@@ -51,6 +53,9 @@ public class TbJsSwitchNodeTest {
     @Mock
     private ScriptEngine scriptEngine;
 
+    private RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased());
+    private RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased());
+
     @Test
     public void multipleRoutesAreAllowed() throws TbNodeException, ScriptException {
         initWithScript();
@@ -59,7 +64,7 @@ public class TbJsSwitchNodeTest {
         metaData.putValue("humidity", "99");
         String rawJson = "{\"name\": \"Vit\", \"passed\": 5}";
 
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, ruleChainId, ruleNodeId, 0L);
         mockJsExecutor();
         when(scriptEngine.executeSwitch(msg)).thenReturn(Sets.newHashSet("one", "three"));
 
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java
index 877047c..6280827 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java
@@ -27,6 +27,8 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration;
 import org.thingsboard.rule.engine.api.TbNodeException;
 import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.RuleChainId;
+import org.thingsboard.server.common.data.id.RuleNodeId;
 import org.thingsboard.server.common.msg.TbMsg;
 import org.thingsboard.server.common.msg.TbMsgMetaData;
 
@@ -34,6 +36,7 @@ import java.io.IOException;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotSame;
+import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.verify;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -48,26 +51,32 @@ public class TbMsgToEmailNodeTest {
     private TbMsgMetaData metaData = new TbMsgMetaData();
     private String rawJson = "{\"name\": \"temp\", \"passed\": 5 , \"complex\": {\"val\":12, \"count\":100}}";
 
+    private RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased());
+    private RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased());
+
     @Test
     public void msgCanBeConverted() throws IOException {
         initWithScript();
         metaData.putValue("username", "oreo");
         metaData.putValue("userEmail", "user@email.io");
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson);
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L);
 
         emailNode.onMsg(ctx, msg);
 
-        ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);
-        verify(ctx).tellNext(captor.capture());
-        TbMsg actualMsg = captor.getValue();
+        ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
+        ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class);
+        ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class);
+        ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class);
+        verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture());
 
-        assertEquals("SEND_EMAIL", actualMsg.getType());
-        assertEquals(originator, actualMsg.getOriginator());
-        assertEquals("oreo", actualMsg.getMetaData().getValue("username"));
-        assertNotSame(metaData, actualMsg.getMetaData());
 
+        assertEquals("SEND_EMAIL", typeCaptor.getValue());
+        assertEquals(originator, originatorCaptor.getValue());
+        assertEquals("oreo", metadataCaptor.getValue().getValue("username"));
+        assertNotSame(metaData, metadataCaptor.getValue());
 
-        EmailPojo actual = new ObjectMapper().readValue(actualMsg.getData().getBytes(), EmailPojo.class);
+        EmailPojo actual = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), EmailPojo.class);
 
         EmailPojo expected = new EmailPojo.EmailPojoBuilder()
                 .from("test@mail.org")
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java
index e26312b..158b7cd 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java
@@ -34,6 +34,8 @@ import org.thingsboard.server.common.data.asset.Asset;
 import org.thingsboard.server.common.data.id.AssetId;
 import org.thingsboard.server.common.data.id.CustomerId;
 import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.RuleChainId;
+import org.thingsboard.server.common.data.id.RuleNodeId;
 import org.thingsboard.server.common.data.id.UserId;
 import org.thingsboard.server.common.data.kv.*;
 import org.thingsboard.server.common.msg.TbMsg;
@@ -77,6 +79,9 @@ public class TbGetCustomerAttributeNodeTest {
 
     private TbMsg msg;
 
+    private RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased());
+    private RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased());
+
     @Before
     public void init() throws TbNodeException {
         TbGetEntityAttrNodeConfiguration config = new TbGetEntityAttrNodeConfiguration();
@@ -98,7 +103,8 @@ public class TbGetCustomerAttributeNodeTest {
         User user = new User();
         user.setCustomerId(customerId);
 
-        msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}");
+
+        msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L);
 
         when(ctx.getUserService()).thenReturn(userService);
         when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user));
@@ -123,7 +129,7 @@ public class TbGetCustomerAttributeNodeTest {
         User user = new User();
         user.setCustomerId(customerId);
 
-        msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}");
+        msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L);
 
         when(ctx.getUserService()).thenReturn(userService);
         when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user));
@@ -148,7 +154,7 @@ public class TbGetCustomerAttributeNodeTest {
         User user = new User();
         user.setCustomerId(customerId);
 
-        msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}");
+        msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L);
 
         when(ctx.getUserService()).thenReturn(userService);
         when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(null));
@@ -166,7 +172,7 @@ public class TbGetCustomerAttributeNodeTest {
     @Test
     public void customerAttributeAddedInMetadata() {
         CustomerId customerId = new CustomerId(UUIDs.timeBased());
-        msg = new TbMsg(UUIDs.timeBased(), "CUSTOMER", customerId, new TbMsgMetaData(), "{}");
+        msg = new TbMsg(UUIDs.timeBased(), "CUSTOMER", customerId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L);
         entityAttributeFetched(customerId);
     }
 
@@ -177,7 +183,7 @@ public class TbGetCustomerAttributeNodeTest {
         User user = new User();
         user.setCustomerId(customerId);
 
-        msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}");
+        msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L);
 
         when(ctx.getUserService()).thenReturn(userService);
         when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user));
@@ -192,7 +198,7 @@ public class TbGetCustomerAttributeNodeTest {
         Asset asset = new Asset();
         asset.setCustomerId(customerId);
 
-        msg = new TbMsg(UUIDs.timeBased(), "USER", assetId, new TbMsgMetaData(), "{}");
+        msg = new TbMsg(UUIDs.timeBased(), "USER", assetId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L);
 
         when(ctx.getAssetService()).thenReturn(assetService);
         when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFuture(asset));
@@ -207,7 +213,7 @@ public class TbGetCustomerAttributeNodeTest {
         Device device = new Device();
         device.setCustomerId(customerId);
 
-        msg = new TbMsg(UUIDs.timeBased(), "USER", deviceId, new TbMsgMetaData(), "{}");
+        msg = new TbMsg(UUIDs.timeBased(), "USER", deviceId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L);
 
         when(ctx.getDeviceService()).thenReturn(deviceService);
         when(deviceService.findDeviceByIdAsync(deviceId)).thenReturn(Futures.immediateFuture(device));
@@ -234,7 +240,7 @@ public class TbGetCustomerAttributeNodeTest {
         Device device = new Device();
         device.setCustomerId(customerId);
 
-        msg = new TbMsg(UUIDs.timeBased(), "USER", deviceId, new TbMsgMetaData(), "{}");
+        msg = new TbMsg(UUIDs.timeBased(), "USER", deviceId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L);
 
         when(ctx.getDeviceService()).thenReturn(deviceService);
         when(deviceService.findDeviceByIdAsync(deviceId)).thenReturn(Futures.immediateFuture(device));
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java
index 1b66433..b61589d 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java
@@ -29,6 +29,9 @@ import org.thingsboard.rule.engine.api.TbNodeException;
 import org.thingsboard.server.common.data.asset.Asset;
 import org.thingsboard.server.common.data.id.AssetId;
 import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.RuleChainId;
+import org.thingsboard.server.common.data.id.RuleNodeId;
 import org.thingsboard.server.common.msg.TbMsg;
 import org.thingsboard.server.common.msg.TbMsgMetaData;
 import org.thingsboard.server.dao.asset.AssetService;
@@ -57,17 +60,24 @@ public class TbChangeOriginatorNodeTest {
         Asset asset = new Asset();
         asset.setCustomerId(customerId);
 
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}");
+        RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased());
+        RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased());
+
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L);
 
         when(ctx.getAssetService()).thenReturn(assetService);
         when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFuture(asset));
 
         node.onMsg(ctx, msg);
-        ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);
-        verify(ctx).tellNext(captor.capture());
-        TbMsg actualMsg = captor.getValue();
-        assertEquals(customerId, actualMsg.getOriginator());
-        assertEquals(msg.getId(), actualMsg.getId());
+
+        ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
+        ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class);
+        ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class);
+        ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class);
+        verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture());
+
+        assertEquals(customerId, originatorCaptor.getValue());
     }
 
     @Test
@@ -78,17 +88,23 @@ public class TbChangeOriginatorNodeTest {
         Asset asset = new Asset();
         asset.setCustomerId(customerId);
 
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}");
+        RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased());
+        RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased());
+
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L);
 
         when(ctx.getAssetService()).thenReturn(assetService);
         when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFuture(asset));
 
         node.onMsg(ctx, msg);
-        ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);
-        verify(ctx).spawn(captor.capture());
-        TbMsg actualMsg = captor.getValue();
-        assertEquals(customerId, actualMsg.getOriginator());
-        assertEquals(msg.getId(), actualMsg.getId());
+        ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
+        ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class);
+        ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class);
+        ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class);
+        verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture());
+
+        assertEquals(customerId, originatorCaptor.getValue());
     }
 
     @Test
@@ -99,7 +115,10 @@ public class TbChangeOriginatorNodeTest {
         Asset asset = new Asset();
         asset.setCustomerId(customerId);
 
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}");
+        RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased());
+        RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased());
+
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L);
 
         when(ctx.getAssetService()).thenReturn(assetService);
         when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFailedFuture(new IllegalStateException("wrong")));
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java
index b904d7e..a29bc90 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java
@@ -27,6 +27,8 @@ import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.mockito.stubbing.Answer;
 import org.thingsboard.rule.engine.api.*;
+import org.thingsboard.server.common.data.id.RuleChainId;
+import org.thingsboard.server.common.data.id.RuleNodeId;
 import org.thingsboard.server.common.msg.TbMsg;
 import org.thingsboard.server.common.msg.TbMsgMetaData;
 
@@ -56,8 +58,10 @@ public class TbTransformMsgNodeTest {
         metaData.putValue("temp", "7");
         String rawJson = "{\"passed\": 5}";
 
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
-        TbMsg transformedMsg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{new}");
+        RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased());
+        RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased());
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, ruleChainId, ruleNodeId, 0L);
+        TbMsg transformedMsg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{new}", ruleChainId, ruleNodeId, 0L);
         mockJsExecutor();
         when(scriptEngine.executeUpdate(msg)).thenReturn(transformedMsg);
 
@@ -77,8 +81,10 @@ public class TbTransformMsgNodeTest {
         metaData.putValue("temp", "7");
         String rawJson = "{\"passed\": 5";
 
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
-        TbMsg transformedMsg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{new}");
+        RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased());
+        RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased());
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, ruleChainId, ruleNodeId, 0L);
+        TbMsg transformedMsg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{new}", ruleChainId, ruleNodeId, 0L);
         mockJsExecutor();
         when(scriptEngine.executeUpdate(msg)).thenReturn(transformedMsg);
 
@@ -97,7 +103,9 @@ public class TbTransformMsgNodeTest {
         metaData.putValue("temp", "7");
         String rawJson = "{\"passed\": 5";
 
-        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
+        RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased());
+        RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased());
+        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, ruleChainId, ruleNodeId, 0L);
         mockJsExecutor();
         when(scriptEngine.executeUpdate(msg)).thenThrow(new IllegalStateException("error"));
 
diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java
index e3ef2cc..1c96311 100644
--- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java
+++ b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java
@@ -28,7 +28,7 @@ import org.thingsboard.server.common.msg.kv.AttributesKVMsg;
 import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg;
 import org.thingsboard.server.common.msg.session.BasicAdaptorToSessionActorMsg;
 import org.thingsboard.server.common.msg.session.FromDeviceMsg;
-import org.thingsboard.server.common.msg.session.MsgType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
 import org.thingsboard.server.common.msg.session.SessionActorToAdaptorMsg;
 import org.thingsboard.server.common.msg.session.SessionContext;
 import org.thingsboard.server.common.msg.session.ToDeviceMsg;
@@ -48,7 +48,7 @@ import org.thingsboard.server.transport.coap.session.CoapSessionCtx;
 public class JsonCoapAdaptor implements CoapTransportAdaptor {
 
     @Override
-    public AdaptorToSessionActorMsg convertToActorMsg(CoapSessionCtx ctx, MsgType type, Request inbound) throws AdaptorException {
+    public AdaptorToSessionActorMsg convertToActorMsg(CoapSessionCtx ctx, SessionMsgType type, Request inbound) throws AdaptorException {
         FromDeviceMsg msg = null;
         switch (type) {
             case POST_TELEMETRY_REQUEST:
@@ -104,7 +104,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor {
     @Override
     public Optional<Response> convertToAdaptorMsg(CoapSessionCtx ctx, SessionActorToAdaptorMsg source) throws AdaptorException {
         ToDeviceMsg msg = source.getMsg();
-        switch (msg.getMsgType()) {
+        switch (msg.getSessionMsgType()) {
             case STATUS_CODE_RESPONSE:
             case TO_DEVICE_RPC_RESPONSE_ACK:
                 return Optional.of(convertStatusCodeResponse((StatusCodeResponse) msg));
@@ -119,19 +119,19 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor {
             case RULE_ENGINE_ERROR:
                 return Optional.of(convertToRuleEngineErrorResponse(ctx, (RuleEngineErrorMsg) msg));
             default:
-                log.warn("[{}] Unsupported msg type: {}!", source.getSessionId(), msg.getMsgType());
-                throw new AdaptorException(new IllegalArgumentException("Unsupported msg type: " + msg.getMsgType() + "!"));
+                log.warn("[{}] Unsupported msg type: {}!", source.getSessionId(), msg.getSessionMsgType());
+                throw new AdaptorException(new IllegalArgumentException("Unsupported msg type: " + msg.getSessionMsgType() + "!"));
         }
     }
 
     private Response convertToRuleEngineErrorResponse(CoapSessionCtx ctx, RuleEngineErrorMsg msg) {
         ResponseCode status = ResponseCode.INTERNAL_SERVER_ERROR;
         switch (msg.getError()) {
-            case PLUGIN_TIMEOUT:
+            case QUEUE_PUT_TIMEOUT:
                 status = ResponseCode.GATEWAY_TIMEOUT;
                 break;
             default:
-                if (msg.getInMsgType() == MsgType.TO_SERVER_RPC_REQUEST) {
+                if (msg.getInSessionMsgType() == SessionMsgType.TO_SERVER_RPC_REQUEST) {
                     status = ResponseCode.BAD_REQUEST;
                 }
                 break;
@@ -156,7 +156,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor {
         return response;
     }
 
-    private UpdateAttributesRequest convertToUpdateAttributesRequest(SessionContext ctx, Request inbound) throws AdaptorException {
+    private AttributesUpdateRequest convertToUpdateAttributesRequest(SessionContext ctx, Request inbound) throws AdaptorException {
         String payload = validatePayload(ctx, inbound);
         try {
             return JsonConverter.convertToAttributes(new JsonParser().parse(payload));
diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
index af07737..d445aba 100644
--- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
+++ b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
@@ -93,7 +93,7 @@ public class CoapTransportResource extends CoapResource {
         } else if (exchange.getRequestOptions().hasObserve()) {
             processExchangeGetRequest(exchange, featureType.get());
         } else if (featureType.get() == FeatureType.ATTRIBUTES) {
-            processRequest(exchange, MsgType.GET_ATTRIBUTES_REQUEST);
+            processRequest(exchange, SessionMsgType.GET_ATTRIBUTES_REQUEST);
         } else {
             log.trace("Invalid feature type parameter");
             exchange.respond(ResponseCode.BAD_REQUEST);
@@ -102,13 +102,13 @@ public class CoapTransportResource extends CoapResource {
 
     private void processExchangeGetRequest(CoapExchange exchange, FeatureType featureType) {
         boolean unsubscribe = exchange.getRequestOptions().getObserve() == 1;
-        MsgType msgType;
+        SessionMsgType sessionMsgType;
         if (featureType == FeatureType.RPC) {
-            msgType = unsubscribe ? MsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST : MsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST;
+            sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST : SessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST;
         } else {
-            msgType = unsubscribe ? MsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST : MsgType.SUBSCRIBE_ATTRIBUTES_REQUEST;
+            sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST : SessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST;
         }
-        Optional<SessionId> sessionId = processRequest(exchange, msgType);
+        Optional<SessionId> sessionId = processRequest(exchange, sessionMsgType);
         if (sessionId.isPresent()) {
             if (exchange.getRequestOptions().getObserve() == 1) {
                 exchange.respond(ResponseCode.VALID);
@@ -125,24 +125,24 @@ public class CoapTransportResource extends CoapResource {
         } else {
             switch (featureType.get()) {
                 case ATTRIBUTES:
-                    processRequest(exchange, MsgType.POST_ATTRIBUTES_REQUEST);
+                    processRequest(exchange, SessionMsgType.POST_ATTRIBUTES_REQUEST);
                     break;
                 case TELEMETRY:
-                    processRequest(exchange, MsgType.POST_TELEMETRY_REQUEST);
+                    processRequest(exchange, SessionMsgType.POST_TELEMETRY_REQUEST);
                     break;
                 case RPC:
                     Optional<Integer> requestId = getRequestId(exchange.advanced().getRequest());
                     if (requestId.isPresent()) {
-                        processRequest(exchange, MsgType.TO_DEVICE_RPC_RESPONSE);
+                        processRequest(exchange, SessionMsgType.TO_DEVICE_RPC_RESPONSE);
                     } else {
-                        processRequest(exchange, MsgType.TO_SERVER_RPC_REQUEST);
+                        processRequest(exchange, SessionMsgType.TO_SERVER_RPC_REQUEST);
                     }
                     break;
             }
         }
     }
 
-    private Optional<SessionId> processRequest(CoapExchange exchange, MsgType type) {
+    private Optional<SessionId> processRequest(CoapExchange exchange, SessionMsgType type) {
         log.trace("Processing {}", exchange.advanced().getRequest());
         exchange.accept();
         Exchange advanced = exchange.advanced();
diff --git a/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java b/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java
index fd0346a..df8545e 100644
--- a/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java
+++ b/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java
@@ -116,9 +116,9 @@ public class CoapServerTest {
                         try {
                             FromDeviceMsg deviceMsg = sessionMsg.getMsg();
                             ToDeviceMsg toDeviceMsg = null;
-                            if (deviceMsg.getMsgType() == MsgType.POST_TELEMETRY_REQUEST) {
+                            if (deviceMsg.getMsgType() == SessionMsgType.POST_TELEMETRY_REQUEST) {
                                 toDeviceMsg = BasicStatusCodeResponse.onSuccess(deviceMsg.getMsgType(), BasicRequest.DEFAULT_REQUEST_ID);
-                            } else if (deviceMsg.getMsgType() == MsgType.GET_ATTRIBUTES_REQUEST) {
+                            } else if (deviceMsg.getMsgType() == SessionMsgType.GET_ATTRIBUTES_REQUEST) {
                                 List<AttributeKvEntry> data = new ArrayList<>();
                                 data.add(new BaseAttributeKvEntry(new StringDataEntry("key1", "value1"), System.currentTimeMillis()));
                                 data.add(new BaseAttributeKvEntry(new LongDataEntry("key2", 42L), System.currentTimeMillis()));
diff --git a/transport/http/src/main/java/org/thingsboard/server/transport/http/session/HttpSessionCtx.java b/transport/http/src/main/java/org/thingsboard/server/transport/http/session/HttpSessionCtx.java
index 743b3e7..4732785 100644
--- a/transport/http/src/main/java/org/thingsboard/server/transport/http/session/HttpSessionCtx.java
+++ b/transport/http/src/main/java/org/thingsboard/server/transport/http/session/HttpSessionCtx.java
@@ -57,7 +57,7 @@ public class HttpSessionCtx extends DeviceAwareSessionContext {
     @Override
     public void onMsg(SessionActorToAdaptorMsg source) throws SessionException {
         ToDeviceMsg msg = source.getMsg();
-        switch (msg.getMsgType()) {
+        switch (msg.getSessionMsgType()) {
             case GET_ATTRIBUTES_RESPONSE:
                 reply((GetAttributesResponse) msg);
                 return;
@@ -84,11 +84,11 @@ public class HttpSessionCtx extends DeviceAwareSessionContext {
     private void reply(RuleEngineErrorMsg msg) {
         HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
         switch (msg.getError()) {
-            case PLUGIN_TIMEOUT:
+            case QUEUE_PUT_TIMEOUT:
                 status = HttpStatus.REQUEST_TIMEOUT;
                 break;
             default:
-                if (msg.getInMsgType() == MsgType.TO_SERVER_RPC_REQUEST) {
+                if (msg.getInSessionMsgType() == SessionMsgType.TO_SERVER_RPC_REQUEST) {
                     status = HttpStatus.BAD_REQUEST;
                 }
                 break;
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java
index 64df6bc..f0b29cb 100644
--- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java
@@ -53,7 +53,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
     private static final ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false);
 
     @Override
-    public AdaptorToSessionActorMsg convertToActorMsg(DeviceSessionCtx ctx, MsgType type, MqttMessage inbound) throws AdaptorException {
+    public AdaptorToSessionActorMsg convertToActorMsg(DeviceSessionCtx ctx, SessionMsgType type, MqttMessage inbound) throws AdaptorException {
         FromDeviceMsg msg;
         switch (type) {
             case POST_TELEMETRY_REQUEST:
@@ -94,7 +94,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
     public Optional<MqttMessage> convertToAdaptorMsg(DeviceSessionCtx ctx, SessionActorToAdaptorMsg sessionMsg) throws AdaptorException {
         MqttMessage result = null;
         ToDeviceMsg msg = sessionMsg.getMsg();
-        switch (msg.getMsgType()) {
+        switch (msg.getSessionMsgType()) {
             case STATUS_CODE_RESPONSE:
             case GET_ATTRIBUTES_RESPONSE:
                 ResponseMsg<?> responseMsg = (ResponseMsg) msg;
@@ -134,12 +134,12 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
     private MqttMessage convertResponseMsg(DeviceSessionCtx ctx, ToDeviceMsg msg,
                                            ResponseMsg<?> responseMsg, Optional<Exception> responseError) throws AdaptorException {
         MqttMessage result = null;
-        MsgType requestMsgType = responseMsg.getRequestMsgType();
+        SessionMsgType requestMsgType = responseMsg.getRequestMsgType();
         Integer requestId = responseMsg.getRequestId();
         if (requestId >= 0) {
-            if (requestMsgType == MsgType.POST_ATTRIBUTES_REQUEST || requestMsgType == MsgType.POST_TELEMETRY_REQUEST) {
+            if (requestMsgType == SessionMsgType.POST_ATTRIBUTES_REQUEST || requestMsgType == SessionMsgType.POST_TELEMETRY_REQUEST) {
                 result = MqttTransportHandler.createMqttPubAckMsg(requestId);
-            } else if (requestMsgType == MsgType.GET_ATTRIBUTES_REQUEST) {
+            } else if (requestMsgType == SessionMsgType.GET_ATTRIBUTES_REQUEST) {
                 GetAttributesResponse response = (GetAttributesResponse) msg;
                 Optional<AttributesKVMsg> responseData = response.getData();
                 if (response.isSuccess() && responseData.isPresent()) {
@@ -219,7 +219,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
         }
     }
 
-    private UpdateAttributesRequest convertToUpdateAttributesRequest(SessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
+    private AttributesUpdateRequest convertToUpdateAttributesRequest(SessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
         String payload = validatePayload(ctx.getSessionId(), inbound.payload());
         try {
             return JsonConverter.convertToAttributes(new JsonParser().parse(payload), inbound.variableHeader().messageId());
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
index 8d475a4..4fa32c6 100644
--- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
@@ -54,7 +54,7 @@ import java.util.List;
 import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.*;
 import static io.netty.handler.codec.mqtt.MqttMessageType.*;
 import static io.netty.handler.codec.mqtt.MqttQoS.*;
-import static org.thingsboard.server.common.msg.session.MsgType.*;
+import static org.thingsboard.server.common.msg.session.SessionMsgType.*;
 import static org.thingsboard.server.transport.mqtt.MqttTopics.*;
 
 /**
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java
index 632ab28..6377fad 100644
--- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java
@@ -80,13 +80,13 @@ public class GatewayDeviceSessionCtx extends DeviceAwareSessionContext {
 
     private Optional<MqttMessage> getToDeviceMsg(SessionActorToAdaptorMsg sessionMsg) {
         ToDeviceMsg msg = sessionMsg.getMsg();
-        switch (msg.getMsgType()) {
+        switch (msg.getSessionMsgType()) {
             case STATUS_CODE_RESPONSE:
                 ResponseMsg<?> responseMsg = (ResponseMsg) msg;
                 if (responseMsg.isSuccess()) {
-                    MsgType requestMsgType = responseMsg.getRequestMsgType();
+                    SessionMsgType requestMsgType = responseMsg.getRequestMsgType();
                     Integer requestId = responseMsg.getRequestId();
-                    if (requestId >= 0 && requestMsgType == MsgType.POST_ATTRIBUTES_REQUEST || requestMsgType == MsgType.POST_TELEMETRY_REQUEST) {
+                    if (requestId >= 0 && requestMsgType == SessionMsgType.POST_ATTRIBUTES_REQUEST || requestMsgType == SessionMsgType.POST_TELEMETRY_REQUEST) {
                         return Optional.of(MqttTransportHandler.createMqttPubAckMsg(requestId));
                     }
                 }
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java
index b4dd8db..f35434a 100644
--- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java
@@ -179,7 +179,7 @@ public class GatewaySessionCtx {
                     throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
                 }
                 long ts = System.currentTimeMillis();
-                BasicUpdateAttributesRequest request = new BasicUpdateAttributesRequest(requestId);
+                BasicAttributesUpdateRequest request = new BasicAttributesUpdateRequest(requestId);
                 JsonObject deviceData = deviceEntry.getValue().getAsJsonObject();
                 request.add(JsonConverter.parseValues(deviceData).stream().map(kv -> new BaseAttributeKvEntry(kv, ts)).collect(Collectors.toList()));
                 GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName);
diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js
index 03b3a82..1b417dd 100644
--- a/ui/src/app/api/rule-chain.service.js
+++ b/ui/src/app/api/rule-chain.service.js
@@ -184,6 +184,15 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
                             ruleNodeComponents.push(
                                 types.ruleChainNodeComponent
                             );
+                            ruleNodeComponents.sort(
+                                (comp1, comp2) => {
+                                    var result = comp1.type.localeCompare(comp2.type);
+                                    if (result == 0) {
+                                        result = comp1.name.localeCompare(comp2.name);
+                                    }
+                                    return result;
+                                }
+                            );
                             deferred.resolve(ruleNodeComponents);
                         },
                         () => {
diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js
index 88c6353..f04b036 100644
--- a/ui/src/app/import-export/import-export.service.js
+++ b/ui/src/app/import-export/import-export.service.js
@@ -316,6 +316,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
         delete ruleChainMetaData.ruleChainId;
         for (var i=0;i<ruleChainMetaData.nodes.length;i++) {
             var node = ruleChainMetaData.nodes[i];
+            delete node.ruleChainId;
             ruleChainMetaData.nodes[i] = prepareExport(node);
         }
         return ruleChainMetaData;