thingsboard-memoizeit

Changes

application/pom.xml 19(+10 -9)

application/src/main/java/org/thingsboard/server/actors/device/PendingSessionMsgData.java 40(+0 -40)

application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java 122(+0 -122)

application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java 156(+0 -156)

application/src/main/java/org/thingsboard/server/actors/session/SessionActor.java 143(+0 -143)

application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java 180(+0 -180)

application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java 95(+0 -95)

application/src/main/java/org/thingsboard/server/service/queue/DefaultMsgQueueService.java 111(+0 -111)

application/src/main/java/org/thingsboard/server/service/queue/MsgQueueService.java 32(+0 -32)

common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateNotification.java 47(+0 -47)

common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateRequest.java 28(+0 -28)

common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicActorSystemToDeviceSessionActorMsg.java 52(+0 -52)

common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicAttributesUpdateRequest.java 63(+0 -63)

common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicCommandAckResponse.java 45(+0 -45)

common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesRequest.java 59(+0 -59)

common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesResponse.java 40(+0 -40)

common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicResponseMsg.java 79(+0 -79)

common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicStatusCodeResponse.java 42(+0 -42)

common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicTelemetryUploadRequest.java 66(+0 -66)

common/message/src/main/java/org/thingsboard/server/common/msg/core/GetAttributesRequest.java 29(+0 -29)

common/message/src/main/java/org/thingsboard/server/common/msg/core/ResponseMsg.java 33(+0 -33)

common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineErrorMsg.java 53(+0 -53)

common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseNotification.java 38(+0 -38)

common/message/src/main/java/org/thingsboard/server/common/msg/core/TelemetryUploadRequest.java 29(+0 -29)

common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcRequestMsg.java 41(+0 -41)

common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcResponseMsg.java 36(+0 -36)

common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java 36(+0 -36)

common/message/src/main/java/org/thingsboard/server/common/msg/device/BasicDeviceToDeviceActorMsg.java 107(+0 -107)

common/message/src/main/java/org/thingsboard/server/common/msg/device/DeviceToDeviceActorMsg.java 41(+0 -41)

common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicSessionActorToAdaptorMsg.java 32(+0 -32)

common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicSessionMsg.java 44(+0 -44)

common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicTransportToDeviceSessionActorMsg.java 74(+0 -74)

common/message/src/main/java/org/thingsboard/server/common/msg/session/ctrl/SessionCloseMsg.java 68(+0 -68)

common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceMsg.java 24(+0 -24)

common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionCtrlMsg.java 23(+0 -23)

common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsg.java 24(+0 -24)

common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceMsg.java 26(+0 -26)

common/message/src/main/java/org/thingsboard/server/common/msg/session/TransportToDeviceSessionActorMsg.java 28(+0 -28)

common/pom.xml 3(+1 -2)

common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java 216(+0 -216)

common/transport/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java 78(+0 -78)

common/transport/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java 32(+0 -32)

dao/src/main/java/org/thingsboard/server/dao/queue/memory/InMemoryMsgQueue.java 123(+0 -123)

dao/src/main/java/org/thingsboard/server/dao/queue/MsgQueue.java 34(+0 -34)

dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java 152(+0 -152)

msa/docker/.env 5(+4 -1)

msa/pom.xml 1(+1 -0)

pom.xml 25(+18 -7)

transport/coap/pom.xml 299(+278 -21)

transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java 265(+0 -265)

transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapExchangeObserverProxy.java 38(+0 -38)

transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapSessionCtx.java 135(+0 -135)

transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapSessionId.java 77(+0 -77)

transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java 208(+0 -208)

transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTestConfiguration.java 35(+0 -35)

transport/coap/src/test/resources/coap-transport-test.properties 4(+0 -4)

transport/http/pom.xml 296(+278 -18)

transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java 235(+0 -235)

transport/http/src/main/java/org/thingsboard/server/transport/http/session/HttpSessionCtx.java 164(+0 -164)

transport/mqtt/pom.xml 303(+278 -25)

transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java 273(+0 -273)

transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java 119(+0 -119)

transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java 208(+0 -208)

transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java 277(+0 -277)

transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttSessionId.java 63(+0 -63)

transport/pom.xml 18(+1 -17)

Details

application/pom.xml 19(+10 -9)

diff --git a/application/pom.xml b/application/pom.xml
index c4766f2..d99f607 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -57,20 +57,20 @@
             <artifactId>rule-engine-components</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.thingsboard.common</groupId>
-            <artifactId>transport</artifactId>
+            <groupId>org.thingsboard.common.transport</groupId>
+            <artifactId>transport-api</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.thingsboard.transport</groupId>
-            <artifactId>http</artifactId>
+            <groupId>org.thingsboard.common.transport</groupId>
+            <artifactId>mqtt</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.thingsboard.transport</groupId>
-            <artifactId>coap</artifactId>
+            <groupId>org.thingsboard.common.transport</groupId>
+            <artifactId>http</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.thingsboard.transport</groupId>
-            <artifactId>mqtt</artifactId>
+            <groupId>org.thingsboard.common.transport</groupId>
+            <artifactId>coap</artifactId>
         </dependency>
         <dependency>
             <groupId>org.thingsboard</groupId>
@@ -542,7 +542,8 @@
                     <args>
                         <arg>-PprojectBuildDir=${project.build.directory}</arg>
                         <arg>-PprojectVersion=${project.version}</arg>
-                        <arg>-PmainJar=${project.build.directory}/${project.build.finalName}-boot.${project.packaging}</arg>
+                        <arg>-PmainJar=${project.build.directory}/${project.build.finalName}-boot.${project.packaging}
+                        </arg>
                         <arg>-PpkgName=${pkg.name}</arg>
                         <arg>-PpkgInstallFolder=${pkg.installFolder}</arg>
                         <arg>-PpkgLogFolder=${pkg.unixLogFolder}</arg>
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 8930f84..5e19d69 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
@@ -31,6 +31,7 @@ import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Component;
 import org.thingsboard.rule.engine.api.MailService;
 import org.thingsboard.server.actors.service.ActorService;
@@ -63,12 +64,12 @@ import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
 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.queue.MsgQueueService;
 import org.thingsboard.server.service.rpc.DeviceRpcService;
 import org.thingsboard.server.service.script.JsExecutorService;
 import org.thingsboard.server.service.script.JsInvokeService;
 import org.thingsboard.server.service.state.DeviceStateService;
 import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
+import org.thingsboard.server.service.transport.RuleEngineTransportService;
 
 import javax.annotation.Nullable;
 import java.io.IOException;
@@ -198,11 +199,12 @@ public class ActorSystemContext {
 
     @Autowired
     @Getter
-    private MsgQueueService msgQueueService;
+    private DeviceStateService deviceStateService;
 
+    @Lazy
     @Autowired
     @Getter
-    private DeviceStateService deviceStateService;
+    private RuleEngineTransportService ruleEngineTransportService;
 
     @Value("${cluster.partition_id}")
     @Getter
@@ -262,10 +264,6 @@ public class ActorSystemContext {
 
     @Getter
     @Setter
-    private ActorRef sessionManagerActor;
-
-    @Getter
-    @Setter
     private ActorRef statsActor;
 
     @Getter
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 6a78f78..f4373db 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
@@ -38,8 +38,6 @@ import org.thingsboard.server.common.msg.TbActorMsg;
 import org.thingsboard.server.common.msg.aware.TenantAwareMsg;
 import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
 import org.thingsboard.server.common.msg.cluster.ServerAddress;
-import org.thingsboard.server.common.msg.core.BasicActorSystemToDeviceSessionActorMsg;
-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;
@@ -105,7 +103,7 @@ public class AppActor extends RuleChainManagerActor {
             case SERVICE_TO_RULE_ENGINE_MSG:
                 onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg);
                 break;
-            case DEVICE_SESSION_TO_DEVICE_ACTOR_MSG:
+            case TRANSPORT_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:
@@ -114,19 +112,12 @@ public class AppActor extends RuleChainManagerActor {
             case REMOTE_TO_RULE_CHAIN_TELL_NEXT_MSG:
                 onToDeviceActorMsg((TenantAwareMsg) msg);
                 break;
-            case ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG:
-                onToDeviceSessionMsg((BasicActorSystemToDeviceSessionActorMsg) msg);
-                break;
             default:
                 return false;
         }
         return true;
     }
 
-    private void onToDeviceSessionMsg(BasicActorSystemToDeviceSessionActorMsg msg) {
-        systemContext.getSessionManagerActor().tell(msg, self());
-    }
-
     private void onPossibleClusterMsg(SendToClusterMsg msg) {
         Optional<ServerAddress> address = systemContext.getRoutingService().resolveById(msg.getEntityId());
         if (address.isPresent()) {
@@ -169,16 +160,6 @@ public class AppActor extends RuleChainManagerActor {
         getOrCreateTenantActor(msg.getTenantId()).tell(msg, ActorRef.noSender());
     }
 
-    private void processDeviceMsg(DeviceToDeviceActorMsg deviceToDeviceActorMsg) {
-        TenantId tenantId = deviceToDeviceActorMsg.getTenantId();
-        ActorRef tenantActor = getOrCreateTenantActor(tenantId);
-        if (deviceToDeviceActorMsg.getPayload().getMsgType().requiresRulesProcessing()) {
-//            tenantActor.tell(new RuleChainDeviceMsg(deviceToDeviceActorMsg, ruleManager.getRuleChain(this.context())), context().self());
-        } else {
-            tenantActor.tell(deviceToDeviceActorMsg, context().self());
-        }
-    }
-
     private ActorRef getOrCreateTenantActor(TenantId tenantId) {
         return tenantActors.computeIfAbsent(tenantId, k -> context().actorOf(Props.create(new TenantActor.ActorCreator(systemContext, tenantId))
                 .withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), tenantId.toString()));
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 99d0045..bd2a0f4 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
@@ -26,12 +26,11 @@ 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.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.service.rpc.ToDeviceRpcRequestActorMsg;
 import org.thingsboard.server.service.rpc.ToServerRpcResponseActorMsg;
+import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
 
 public class DeviceActor extends ContextAwareActor {
 
@@ -50,8 +49,8 @@ public class DeviceActor extends ContextAwareActor {
             case CLUSTER_EVENT_MSG:
                 processor.processClusterEventMsg((ClusterEventMsg) msg);
                 break;
-            case DEVICE_SESSION_TO_DEVICE_ACTOR_MSG:
-                processor.process(context(), (DeviceToDeviceActorMsg) msg);
+            case TRANSPORT_TO_DEVICE_ACTOR_MSG:
+                processor.process(context(), (TransportToDeviceActorMsgWrapper) msg);
                 break;
             case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG:
                 processor.processAttributesUpdate(context(), (DeviceAttributesEventNotificationMsg) msg);
@@ -74,12 +73,6 @@ public class DeviceActor extends ContextAwareActor {
             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;
         }
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 a2ea048..05902a6 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
@@ -16,7 +16,6 @@
 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;
@@ -33,7 +32,6 @@ 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;
@@ -44,36 +42,34 @@ 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.ActorSystemToDeviceSessionActorMsg;
-import org.thingsboard.server.common.msg.core.AttributesUpdateNotification;
-import org.thingsboard.server.common.msg.core.AttributesUpdateRequest;
-import org.thingsboard.server.common.msg.core.BasicActorSystemToDeviceSessionActorMsg;
-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.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.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.SessionMsgType;
-import org.thingsboard.server.common.msg.session.SessionType;
-import org.thingsboard.server.common.msg.session.ToDeviceMsg;
 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.gen.transport.TransportProtos;
+import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.DeviceActorToTransportMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto;
+import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
+import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto;
+import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent;
+import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
+import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto;
+import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
 import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
 import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
 import org.thingsboard.server.service.rpc.ToServerRpcResponseActorMsg;
+import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
 
 import javax.annotation.Nullable;
 import java.util.ArrayList;
@@ -87,9 +83,7 @@ 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;
 import java.util.stream.Collectors;
 
 /**
@@ -99,12 +93,11 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
 
     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<UUID, SessionInfo> sessions;
+    private final Map<UUID, SessionInfo> attributeSubscriptions;
+    private final Map<UUID, SessionInfo> rpcSubscriptions;
     private final Map<Integer, ToDeviceRpcRequestMetadata> toDeviceRpcPendingMap;
     private final Map<Integer, ToServerRpcRequestMetadata> toServerRpcPendingMap;
-    private final Map<UUID, PendingSessionMsgData> pendingMsgs;
 
     private final Gson gson = new Gson();
     private final JsonParser jsonParser = new JsonParser();
@@ -123,7 +116,6 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
         this.rpcSubscriptions = new HashMap<>();
         this.toDeviceRpcPendingMap = new HashMap<>();
         this.toServerRpcPendingMap = new HashMap<>();
-        this.pendingMsgs = new HashMap<>();
         initAttributes();
     }
 
@@ -139,11 +131,8 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
     void processRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg) {
         ToDeviceRpcRequest request = msg.getMsg();
         ToDeviceRpcRequestBody body = request.getBody();
-        ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg(
-                rpcSeq++,
-                body.getMethod(),
-                body.getParams()
-        );
+        ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder().setRequestId(
+                rpcSeq++).setMethodName(body.getMethod()).setParams(body.getParams()).build();
 
         long timeout = request.getExpirationTime() - System.currentTimeMillis();
         if (timeout <= 0) {
@@ -152,11 +141,10 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
         }
 
         boolean sent = rpcSubscriptions.size() > 0;
-        Set<SessionId> syncSessionSet = new HashSet<>();
+        Set<UUID> syncSessionSet = new HashSet<>();
         rpcSubscriptions.entrySet().forEach(sub -> {
-            ActorSystemToDeviceSessionActorMsg response = new BasicActorSystemToDeviceSessionActorMsg(rpcRequest, sub.getKey());
-            sendMsgToSessionActor(response, sub.getValue().getServer());
-            if (SessionType.SYNC == sub.getValue().getType()) {
+            sendToTransport(rpcRequest, sub.getKey(), sub.getValue().getNodeId());
+            if (TransportProtos.SessionType.SYNC == sub.getValue().getType()) {
                 syncSessionSet.add(sub.getKey());
             }
         });
@@ -191,32 +179,11 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
         }
     }
 
-    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 BasicActorSystemToDeviceSessionActorMsg(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 BasicActorSystemToDeviceSessionActorMsg(toDeviceMsg, data.getSessionId()), data.getServerAddress());
-            }
-        }
-    }
-
-    private void sendPendingRequests(ActorContext context, SessionId sessionId, SessionType type, Optional<ServerAddress> server) {
+    private void sendPendingRequests(ActorContext context, UUID sessionId, SessionInfoProto sessionInfo) {
+        TransportProtos.SessionType sessionType = getSessionType(sessionId);
         if (!toDeviceRpcPendingMap.isEmpty()) {
             logger.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, toDeviceRpcPendingMap.size(), sessionId);
-            if (type == SessionType.SYNC) {
+            if (sessionType == TransportProtos.SessionType.SYNC) {
                 logger.debug("[{}] Cleanup sync rpc session [{}]", deviceId, sessionId);
                 rpcSubscriptions.remove(sessionId);
             }
@@ -224,16 +191,16 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
             logger.debug("[{}] No pending RPC messages for new async session [{}]", deviceId, sessionId);
         }
         Set<Integer> sentOneWayIds = new HashSet<>();
-        if (type == SessionType.ASYNC) {
-            toDeviceRpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, server, sentOneWayIds));
+        if (sessionType == TransportProtos.SessionType.ASYNC) {
+            toDeviceRpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, sessionInfo.getNodeId(), sentOneWayIds));
         } else {
-            toDeviceRpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, server, sentOneWayIds));
+            toDeviceRpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, sessionInfo.getNodeId(), sentOneWayIds));
         }
 
         sentOneWayIds.forEach(toDeviceRpcPendingMap::remove);
     }
 
-    private Consumer<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> processPendingRpc(ActorContext context, SessionId sessionId, Optional<ServerAddress> server, Set<Integer> sentOneWayIds) {
+    private Consumer<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> processPendingRpc(ActorContext context, UUID sessionId, String nodeId, Set<Integer> sentOneWayIds) {
         return entry -> {
             ToDeviceRpcRequestActorMsg requestActorMsg = entry.getValue().getMsg();
             ToDeviceRpcRequest request = entry.getValue().getMsg().getMsg();
@@ -242,40 +209,40 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
                 sentOneWayIds.add(entry.getKey());
                 systemContext.getDeviceRpcService().processRpcResponseFromDevice(new FromDeviceRpcResponse(request.getId(), requestActorMsg.getServerAddress(), null, null));
             }
-            ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg(
-                    entry.getKey(),
-                    body.getMethod(),
-                    body.getParams()
-            );
-            ActorSystemToDeviceSessionActorMsg response = new BasicActorSystemToDeviceSessionActorMsg(rpcRequest, sessionId);
-            sendMsgToSessionActor(response, server);
+            ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder().setRequestId(
+                    entry.getKey()).setMethodName(body.getMethod()).setParams(body.getParams()).build();
+            sendToTransport(rpcRequest, sessionId, nodeId);
         };
     }
 
-    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;
-            }
+    void process(ActorContext context, TransportToDeviceActorMsgWrapper wrapper) {
+        TransportToDeviceActorMsg msg = wrapper.getMsg();
+        if (msg.hasSessionEvent()) {
+            processSessionStateMsgs(msg.getSessionInfo(), msg.getSessionEvent());
+        }
+        if (msg.hasSubscribeToAttributes()) {
+            processSubscriptionCommands(context, msg.getSessionInfo(), msg.getSubscribeToAttributes());
+        }
+        if (msg.hasSubscribeToRPC()) {
+            processSubscriptionCommands(context, msg.getSessionInfo(), msg.getSubscribeToRPC());
+        }
+        if (msg.hasPostAttributes()) {
+            handlePostAttributesRequest(context, msg.getSessionInfo(), msg.getPostAttributes());
+            reportActivity();
+        }
+        if (msg.hasPostTelemetry()) {
+            handlePostTelemetryRequest(context, msg.getSessionInfo(), msg.getPostTelemetry());
+            reportActivity();
+        }
+        if (msg.hasGetAttributes()) {
+            handleGetAttributesRequest(context, msg.getSessionInfo(), msg.getGetAttributes());
+        }
+        if (msg.hasToDeviceRPCCallResponse()) {
+            processRpcResponses(context, msg.getSessionInfo(), msg.getToDeviceRPCCallResponse());
+        }
+        if (msg.hasToServerRPCCallRequest()) {
+            handleClientSideRPCRequest(context, msg.getSessionInfo(), msg.getToServerRPCCallRequest());
+            reportActivity();
         }
     }
 
@@ -291,27 +258,27 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
         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.getSharedAttributeNames());
-
+    private void handleGetAttributesRequest(ActorContext context, SessionInfoProto sessionInfo, GetAttributeRequestMsg request) {
+        ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.CLIENT_SCOPE, toOptionalSet(request.getClientAttributeNamesList()));
+        ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.SHARED_SCOPE, toOptionalSet(request.getSharedAttributeNamesList()));
+        int requestId = request.getRequestId();
         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 BasicActorSystemToDeviceSessionActorMsg(response, src.getSessionId()), src.getServerAddress());
+                GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder()
+                        .setRequestId(requestId)
+                        .addAllClientAttributeList(toTsKvProtos(result.get(0)))
+                        .addAllSharedAttributeList(toTsKvProtos(result.get(1)))
+                        .build();
+                sendToTransport(responseMsg, sessionInfo);
             }
 
             @Override
             public void onFailure(Throwable t) {
-                if (t instanceof Exception) {
-                    ToDeviceMsg toDeviceMsg = BasicStatusCodeResponse.onError(SessionMsgType.GET_ATTRIBUTES_REQUEST, request.getRequestId(), (Exception) t);
-                    sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(toDeviceMsg, src.getSessionId()), src.getServerAddress());
-                } else {
-                    logger.error("[{}] Failed to process attributes request", deviceId, t);
-                }
+                GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder()
+                        .setError(t.getMessage())
+                        .build();
+                sendToTransport(responseMsg, sessionInfo);
             }
         });
     }
@@ -328,115 +295,98 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
         }
     }
 
-    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.copy(), 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 handlePostAttributesRequest(ActorContext context, SessionInfoProto sessionInfo, PostAttributeMsg postAttributes) {
+        JsonObject json = getJsonObject(postAttributes.getKvList());
+        TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), deviceId, defaultMetaData.copy(),
+                TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
+        pushToRuleEngine(context, tbMsg);
     }
 
-    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();
-            for (KvEntry kv : entry.getValue()) {
-                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));
-            }
+    private void handlePostTelemetryRequest(ActorContext context, SessionInfoProto sessionInfo, PostTelemetryMsg postTelemetry) {
+        for (TsKvListProto tsKv : postTelemetry.getTsKvListList()) {
+            JsonObject json = getJsonObject(tsKv.getKvList());
             TbMsgMetaData metaData = defaultMetaData.copy();
-            metaData.putValue("ts", entry.getKey() + "");
+            metaData.putValue("ts", tsKv.getTs() + "");
             TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, metaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
-            pushToRuleEngineWithTimeout(context, tbMsg, msgData);
+            pushToRuleEngine(context, tbMsg);
         }
     }
 
-    private void handleClientSideRPCRequest(ActorContext context, DeviceToDeviceActorMsg src) {
-        ToServerRpcRequestMsg request = (ToServerRpcRequestMsg) src.getPayload();
-
+    private void handleClientSideRPCRequest(ActorContext context, SessionInfoProto sessionInfo, TransportProtos.ToServerRpcRequestMsg request) {
+        UUID sessionId = getSessionId(sessionInfo);
         JsonObject json = new JsonObject();
-        json.addProperty("method", request.getMethod());
+        json.addProperty("method", request.getMethodName());
         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);
+        context.parent().tell(new DeviceActorToRuleEngineMsg(context.self(), tbMsg), context.self());
 
         scheduleMsgWithDelay(context, new DeviceActorClientSideRpcTimeoutMsg(request.getRequestId(), systemContext.getClientSideRpcTimeout()), systemContext.getClientSideRpcTimeout());
-        toServerRpcPendingMap.put(request.getRequestId(), new ToServerRpcRequestMetadata(src.getSessionId(), src.getSessionType(), src.getServerAddress()));
+        toServerRpcPendingMap.put(request.getRequestId(), new ToServerRpcRequestMetadata(sessionId, getSessionType(sessionId), sessionInfo.getNodeId()));
+    }
+
+    private TransportProtos.SessionType getSessionType(UUID sessionId) {
+        return sessions.containsKey(sessionId) ? TransportProtos.SessionType.ASYNC : TransportProtos.SessionType.SYNC;
     }
 
-    public void processClientSideRpcTimeout(ActorContext context, DeviceActorClientSideRpcTimeoutMsg msg) {
+    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 BasicActorSystemToDeviceSessionActorMsg(toDeviceMsg, data.getSessionId()), data.getServer());
+            sendToTransport(TransportProtos.ToServerRpcResponseMsg.newBuilder()
+                            .setRequestId(msg.getId()).setError("timeout").build()
+                    , data.getSessionId(), data.getNodeId());
         }
     }
 
     void processToServerRPCResponse(ActorContext context, ToServerRpcResponseActorMsg msg) {
-        ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(msg.getMsg().getRequestId());
+        int requestId = msg.getMsg().getRequestId();
+        ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(requestId);
         if (data != null) {
-            sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(msg.getMsg(), data.getSessionId()), data.getServer());
+            sendToTransport(TransportProtos.ToServerRpcResponseMsg.newBuilder()
+                            .setRequestId(requestId).setPayload(msg.getMsg().getData()).build()
+                    , data.getSessionId(), data.getNodeId());
         }
     }
 
-    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 {
-            ActorSystemToDeviceSessionActorMsg response = new BasicActorSystemToDeviceSessionActorMsg(BasicStatusCodeResponse.onSuccess(sessionMsgType, requestId), pendingMsgData.getSessionId());
-            sendMsgToSessionActor(response, pendingMsgData.getServerAddress());
-        }
+    private void pushToRuleEngine(ActorContext context, TbMsg tbMsg) {
         context.parent().tell(new DeviceActorToRuleEngineMsg(context.self(), tbMsg), context.self());
     }
 
     void processAttributesUpdate(ActorContext context, DeviceAttributesEventNotificationMsg msg) {
         if (attributeSubscriptions.size() > 0) {
-            ToDeviceMsg notification = null;
+            boolean hasNotificationData = false;
+            AttributeUpdateNotificationMsg.Builder notification = AttributeUpdateNotificationMsg.newBuilder();
             if (msg.isDeleted()) {
-                List<AttributeKey> sharedKeys = msg.getDeletedKeys().stream()
+                List<String> sharedKeys = msg.getDeletedKeys().stream()
                         .filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope()))
+                        .map(AttributeKey::getAttributeKey)
                         .collect(Collectors.toList());
-                notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromDeleted(sharedKeys));
+                if (!sharedKeys.isEmpty()) {
+                    notification.addAllSharedDeleted(sharedKeys);
+                    hasNotificationData = true;
+                }
             } else {
                 if (DataConstants.SHARED_SCOPE.equals(msg.getScope())) {
                     List<AttributeKvEntry> attributes = new ArrayList<>(msg.getValues());
                     if (attributes.size() > 0) {
-                        notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromShared(attributes));
+                        List<TsKvProto> sharedUpdated = msg.getValues().stream().map(this::toTsKvProto)
+                                .collect(Collectors.toList());
+                        if (!sharedUpdated.isEmpty()) {
+                            notification.addAllSharedUpdated(sharedUpdated);
+                            hasNotificationData = true;
+                        }
                     } else {
                         logger.debug("[{}] No public server side attributes changed!", deviceId);
                     }
                 }
             }
-            if (notification != null) {
-                ToDeviceMsg finalNotification = notification;
+            if (hasNotificationData) {
+                AttributeUpdateNotificationMsg finalNotification = notification.build();
                 attributeSubscriptions.entrySet().forEach(sub -> {
-                    ActorSystemToDeviceSessionActorMsg response = new BasicActorSystemToDeviceSessionActorMsg(finalNotification, sub.getKey());
-                    sendMsgToSessionActor(response, sub.getValue().getServer());
+                    sendToTransport(finalNotification, sub.getKey(), sub.getValue().getNodeId());
                 });
             }
         } else {
@@ -444,75 +394,83 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
         }
     }
 
-    private void processRpcResponses(ActorContext context, DeviceToDeviceActorMsg msg) {
-        SessionId sessionId = msg.getSessionId();
-        FromDeviceMsg inMsg = msg.getPayload();
-        if (inMsg.getMsgType() == SessionMsgType.TO_DEVICE_RPC_RESPONSE) {
-            logger.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId);
-            ToDeviceRpcResponseMsg responseMsg = (ToDeviceRpcResponseMsg) inMsg;
-            ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
-            boolean success = requestMd != null;
-            if (success) {
-                systemContext.getDeviceRpcService().processRpcResponseFromDevice(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
-                        requestMd.getMsg().getServerAddress(), responseMsg.getData(), null));
-            } else {
-                logger.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId());
-            }
-            if (msg.getSessionType() == SessionType.SYNC) {
-                BasicCommandAckResponse response = success
-                        ? BasicCommandAckResponse.onSuccess(SessionMsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId())
-                        : BasicCommandAckResponse.onError(SessionMsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId(), new TimeoutException());
-                sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(response, msg.getSessionId()), msg.getServerAddress());
-            }
+    private void processRpcResponses(ActorContext context, SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg responseMsg) {
+        UUID sessionId = getSessionId(sessionInfo);
+        logger.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId);
+        ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
+        boolean success = requestMd != null;
+        if (success) {
+            systemContext.getDeviceRpcService().processRpcResponseFromDevice(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
+                    requestMd.getMsg().getServerAddress(), responseMsg.getPayload(), null));
+        } else {
+            logger.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId());
         }
     }
 
     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()
-                    .map(serverAddress -> serverAddress.equals(msg.getServerAddress())).orElse(false);
-            attributeSubscriptions.entrySet().removeIf(filter);
-            rpcSubscriptions.entrySet().removeIf(filter);
-        }
+//        if (!msg.isAdded()) {
+//            logger.debug("[{}] Clearing attributes/rpc subscription for server [{}]", deviceId, msg.getServerAddress());
+//            Predicate<Map.Entry<SessionId, SessionInfo>> filter = e -> e.getValue().getServer()
+//                    .map(serverAddress -> serverAddress.equals(msg.getServerAddress())).orElse(false);
+//            attributeSubscriptions.entrySet().removeIf(filter);
+//            rpcSubscriptions.entrySet().removeIf(filter);
+//        }
     }
 
-    private void processSubscriptionCommands(ActorContext context, DeviceToDeviceActorMsg msg) {
-        SessionId sessionId = msg.getSessionId();
-        SessionType sessionType = msg.getSessionType();
-        FromDeviceMsg inMsg = msg.getPayload();
-        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() == SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST) {
+    private void processSubscriptionCommands(ActorContext context, SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg subscribeCmd) {
+        UUID sessionId = getSessionId(sessionInfo);
+        if (subscribeCmd.getUnsubscribe()) {
             logger.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId);
             attributeSubscriptions.remove(sessionId);
-        } 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() == SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST) {
-            logger.debug("[{}] Canceling rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType);
+        } else {
+            SessionInfo session = sessions.get(sessionId);
+            if (session == null) {
+                session = new SessionInfo(TransportProtos.SessionType.SYNC, sessionInfo.getNodeId());
+            }
+            logger.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId);
+            attributeSubscriptions.put(sessionId, session);
+        }
+    }
+
+    private UUID getSessionId(SessionInfoProto sessionInfo) {
+        return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB());
+    }
+
+    private void processSubscriptionCommands(ActorContext context, SessionInfoProto sessionInfo, SubscribeToRPCMsg subscribeCmd) {
+        UUID sessionId = getSessionId(sessionInfo);
+        if (subscribeCmd.getUnsubscribe()) {
+            logger.debug("[{}] Canceling rpc subscription for session [{}]", deviceId, sessionId);
             rpcSubscriptions.remove(sessionId);
+        } else {
+            SessionInfo session = sessions.get(sessionId);
+            if (session == null) {
+                session = new SessionInfo(TransportProtos.SessionType.SYNC, sessionInfo.getNodeId());
+            }
+            logger.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId);
+            rpcSubscriptions.put(sessionId, session);
+            sendPendingRequests(context, sessionId, sessionInfo);
         }
     }
 
-    private void processSessionStateMsgs(DeviceToDeviceActorMsg msg) {
-        SessionId sessionId = msg.getSessionId();
-        FromDeviceMsg inMsg = msg.getPayload();
-        if (inMsg instanceof SessionOpenMsg) {
+    private void processSessionStateMsgs(SessionInfoProto sessionInfo, SessionEventMsg msg) {
+        UUID sessionId = getSessionId(sessionInfo);
+        if (msg.getEvent() == SessionEvent.OPEN) {
+            if(sessions.containsKey(sessionId)){
+                logger.debug("[{}] Received duplicate session open event [{}]", deviceId, sessionId);
+                return;
+            }
             logger.debug("[{}] Processing new session [{}]", deviceId, sessionId);
             if (sessions.size() >= systemContext.getMaxConcurrentSessionsPerDevice()) {
-                SessionId sessionIdToRemove = sessions.keySet().stream().findFirst().orElse(null);
+                UUID sessionIdToRemove = sessions.keySet().stream().findFirst().orElse(null);
                 if (sessionIdToRemove != null) {
                     closeSession(sessionIdToRemove, sessions.remove(sessionIdToRemove));
                 }
             }
-            sessions.put(sessionId, new SessionInfo(SessionType.ASYNC, msg.getServerAddress()));
+            sessions.put(sessionId, new SessionInfo(TransportProtos.SessionType.ASYNC, sessionInfo.getNodeId()));
             if (sessions.size() == 1) {
                 reportSessionOpen();
             }
-        } else if (inMsg instanceof SessionCloseMsg) {
+        } else if (msg.getEvent() == SessionEvent.CLOSED) {
             logger.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId);
             sessions.remove(sessionId);
             attributeSubscriptions.remove(sessionId);
@@ -523,25 +481,18 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
         }
     }
 
-    private void sendMsgToSessionActor(ActorSystemToDeviceSessionActorMsg response, Optional<ServerAddress> sessionAddress) {
-        if (sessionAddress.isPresent()) {
-            ServerAddress address = sessionAddress.get();
-            logger.debug("{} Forwarding msg: {}", address, response);
-            systemContext.getRpcService().tell(systemContext.getEncodingService()
-                    .convertToProtoDataMessage(sessionAddress.get(), response));
-        } else {
-            systemContext.getSessionManagerActor().tell(response, ActorRef.noSender());
-        }
-    }
-
     void processCredentialsUpdate() {
         sessions.forEach(this::closeSession);
         attributeSubscriptions.clear();
         rpcSubscriptions.clear();
     }
 
-    private void closeSession(SessionId sessionId, SessionInfo sessionInfo) {
-        sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(new SessionCloseNotification(), sessionId), sessionInfo.getServer());
+    private void closeSession(UUID sessionId, SessionInfo sessionInfo) {
+        DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder()
+                .setSessionIdMSB(sessionId.getMostSignificantBits())
+                .setSessionIdLSB(sessionId.getLeastSignificantBits())
+                .setSessionCloseNotification(SessionCloseNotificationProto.getDefaultInstance()).build();
+        systemContext.getRuleEngineTransportService().process(sessionInfo.getNodeId(), msg);
     }
 
     void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) {
@@ -552,4 +503,107 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
         this.defaultMetaData.putValue("deviceType", deviceType);
     }
 
+    private JsonObject getJsonObject(List<KeyValueProto> tsKv) {
+        JsonObject json = new JsonObject();
+        for (KeyValueProto kv : tsKv) {
+            switch (kv.getType()) {
+                case BOOLEAN_V:
+                    json.addProperty(kv.getKey(), kv.getBoolV());
+                    break;
+                case LONG_V:
+                    json.addProperty(kv.getKey(), kv.getLongV());
+                    break;
+                case DOUBLE_V:
+                    json.addProperty(kv.getKey(), kv.getDoubleV());
+                    break;
+                case STRING_V:
+                    json.addProperty(kv.getKey(), kv.getStringV());
+                    break;
+            }
+        }
+        return json;
+    }
+
+    private Optional<Set<String>> toOptionalSet(List<String> strings) {
+        if (strings == null || strings.isEmpty()) {
+            return Optional.empty();
+        } else {
+            return Optional.of(new HashSet<>(strings));
+        }
+    }
+
+    private void sendToTransport(GetAttributeResponseMsg responseMsg, SessionInfoProto sessionInfo) {
+        DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder()
+                .setSessionIdMSB(sessionInfo.getSessionIdMSB())
+                .setSessionIdLSB(sessionInfo.getSessionIdLSB())
+                .setGetAttributesResponse(responseMsg).build();
+        systemContext.getRuleEngineTransportService().process(sessionInfo.getNodeId(), msg);
+    }
+
+    private void sendToTransport(AttributeUpdateNotificationMsg notificationMsg, UUID sessionId, String nodeId) {
+        DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder()
+                .setSessionIdMSB(sessionId.getMostSignificantBits())
+                .setSessionIdLSB(sessionId.getLeastSignificantBits())
+                .setAttributeUpdateNotification(notificationMsg).build();
+        systemContext.getRuleEngineTransportService().process(nodeId, msg);
+    }
+
+    private void sendToTransport(ToDeviceRpcRequestMsg rpcMsg, UUID sessionId, String nodeId) {
+        DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder()
+                .setSessionIdMSB(sessionId.getMostSignificantBits())
+                .setSessionIdLSB(sessionId.getLeastSignificantBits())
+                .setToDeviceRequest(rpcMsg).build();
+        systemContext.getRuleEngineTransportService().process(nodeId, msg);
+    }
+
+    private void sendToTransport(TransportProtos.ToServerRpcResponseMsg rpcMsg, UUID sessionId, String nodeId) {
+        DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder()
+                .setSessionIdMSB(sessionId.getMostSignificantBits())
+                .setSessionIdLSB(sessionId.getLeastSignificantBits())
+                .setToServerResponse(rpcMsg).build();
+        systemContext.getRuleEngineTransportService().process(nodeId, msg);
+    }
+
+
+    private List<TsKvProto> toTsKvProtos(@Nullable List<AttributeKvEntry> result) {
+        List<TsKvProto> clientAttributes;
+        if (result == null || result.isEmpty()) {
+            clientAttributes = Collections.emptyList();
+        } else {
+            clientAttributes = new ArrayList<>(result.size());
+            for (AttributeKvEntry attrEntry : result) {
+                clientAttributes.add(toTsKvProto(attrEntry));
+            }
+        }
+        return clientAttributes;
+    }
+
+    private TsKvProto toTsKvProto(AttributeKvEntry attrEntry) {
+        return TsKvProto.newBuilder().setTs(attrEntry.getLastUpdateTs())
+                .setKv(toKeyValueProto(attrEntry)).build();
+    }
+
+    private KeyValueProto toKeyValueProto(KvEntry kvEntry) {
+        KeyValueProto.Builder builder = KeyValueProto.newBuilder();
+        builder.setKey(kvEntry.getKey());
+        switch (kvEntry.getDataType()) {
+            case BOOLEAN:
+                builder.setType(KeyValueType.BOOLEAN_V);
+                builder.setBoolV(kvEntry.getBooleanValue().get());
+                break;
+            case DOUBLE:
+                builder.setType(KeyValueType.DOUBLE_V);
+                builder.setDoubleV(kvEntry.getDoubleValue().get());
+                break;
+            case LONG:
+                builder.setType(KeyValueType.LONG_V);
+                builder.setLongV(kvEntry.getLongValue().get());
+                break;
+            case STRING:
+                builder.setType(KeyValueType.STRING_V);
+                builder.setStringV(kvEntry.getStrValue().get());
+                break;
+        }
+        return builder.build();
+    }
 }
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java
index 04c457c..43ae592 100644
--- a/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java
+++ b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java
@@ -16,10 +16,7 @@
 package org.thingsboard.server.actors.device;
 
 import lombok.Data;
-import org.thingsboard.server.common.msg.cluster.ServerAddress;
-import org.thingsboard.server.common.msg.session.SessionType;
-
-import java.util.Optional;
+import org.thingsboard.server.gen.transport.TransportProtos.SessionType;
 
 /**
  * @author Andrew Shvayka
@@ -27,5 +24,5 @@ import java.util.Optional;
 @Data
 public class SessionInfo {
     private final SessionType type;
-    private final Optional<ServerAddress> server;
+    private final String nodeId;
 }
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
index f82a8c2..669d94b 100644
--- a/application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java
+++ b/application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java
@@ -16,18 +16,16 @@
 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 org.thingsboard.server.gen.transport.TransportProtos;
 
-import java.util.Optional;
+import java.util.UUID;
 
 /**
  * @author Andrew Shvayka
  */
 @Data
 public class ToServerRpcRequestMetadata {
-    private final SessionId sessionId;
-    private final SessionType type;
-    private final Optional<ServerAddress> server;
+    private final UUID sessionId;
+    private final TransportProtos.SessionType type;
+    private final String nodeId;
 }
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 fe02335..3da90d1 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
@@ -25,7 +25,6 @@ import java.util.Optional;
 
 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;
@@ -90,26 +89,12 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
                 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) {
-            for (TbMsg tbMsg : queue.findUnprocessed(tenantId, ruleNode.getId().getId(), systemContext.getQueuePartitionId())) {
-                pushMsgToNode(nodeActors.get(ruleNode.getId()), tbMsg, "");
-            }
-        }
-        if (firstNode != null) {
-            for (TbMsg tbMsg : queue.findUnprocessed(tenantId, entityId.getId(), systemContext.getQueuePartitionId())) {
-                pushMsgToNode(firstNode, tbMsg, "");
-            }
-        }
-    }
-
     @Override
     public void onUpdate(ActorContext context) throws Exception {
         RuleChain ruleChain = service.findRuleChainById(entityId);
@@ -134,7 +119,6 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
         });
 
         initRoutes(ruleChain, ruleNodeList);
-        reprocess(ruleNodeList);
     }
 
     @Override
@@ -188,17 +172,14 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
     void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg envelope) {
         checkActive();
         if (firstNode != null) {
-            putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> pushMsgToNode(firstNode, msg, ""));
+            pushMsgToNode(firstNode, enrichWithRuleChainId(envelope.getTbMsg()), "");
         }
     }
 
     void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg envelope) {
         checkActive();
         if (firstNode != null) {
-            putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> {
-                pushMsgToNode(firstNode, msg, "");
-                envelope.getCallbackRef().tell(new RuleEngineQueuePutAckMsg(msg.getId()), self);
-            });
+            pushMsgToNode(firstNode, enrichWithRuleChainId(envelope.getTbMsg()), "");
         }
     }
 
@@ -206,15 +187,16 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
         checkActive();
         if (envelope.isEnqueue()) {
             if (firstNode != null) {
-                putToQueue(enrichWithRuleChainId(envelope.getMsg()), msg -> pushMsgToNode(firstNode, msg, envelope.getFromRelationType()));
+                pushMsgToNode(firstNode, enrichWithRuleChainId(envelope.getMsg()), envelope.getFromRelationType());
             }
         } else {
             if (firstNode != null) {
                 pushMsgToNode(firstNode, envelope.getMsg(), envelope.getFromRelationType());
             } else {
-                TbMsg msg = envelope.getMsg();
-                EntityId ackId = msg.getRuleNodeId() != null ? msg.getRuleNodeId() : msg.getRuleChainId();
-                queue.ack(tenantId, envelope.getMsg(), ackId.getId(), msg.getClusterPartition());
+//                TODO: Ack this message in Kafka
+//                TbMsg msg = envelope.getMsg();
+//                EntityId ackId = msg.getRuleNodeId() != null ? msg.getRuleNodeId() : msg.getRuleChainId();
+//                queue.ack(tenantId, envelope.getMsg(), ackId.getId(), msg.getClusterPartition());
             }
         }
     }
@@ -249,7 +231,8 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
         EntityId ackId = msg.getRuleNodeId() != null ? msg.getRuleNodeId() : msg.getRuleChainId();
         if (relationsCount == 0) {
             if (ackId != null) {
-                queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
+//                TODO: Ack this message in Kafka
+//                queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
             }
         } else if (relationsCount == 1) {
             for (RuleNodeRelation relation : relations) {
@@ -269,7 +252,8 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
             }
             //TODO: Ideally this should happen in async way when all targets confirm that the copied messages are successfully written to corresponding target queues.
             if (ackId != null) {
-                queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
+//                TODO: Ack this message in Kafka
+//                queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
             }
         }
     }
@@ -296,7 +280,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
         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, fromRelationType));
+        pushMsgToNode(targetNodeCtx, copy, fromRelationType);
     }
 
     private void pushToTarget(TbMsg msg, EntityId target, String fromRelationType) {
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 1e9e23b..c2e8fd0 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
@@ -30,7 +30,6 @@ import org.thingsboard.server.actors.app.AppActor;
 import org.thingsboard.server.actors.rpc.RpcBroadcastMsg;
 import org.thingsboard.server.actors.rpc.RpcManagerActor;
 import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg;
-import org.thingsboard.server.actors.session.SessionManagerActor;
 import org.thingsboard.server.actors.stats.StatsActor;
 import org.thingsboard.server.common.data.Device;
 import org.thingsboard.server.common.data.id.DeviceId;
@@ -38,7 +37,6 @@ import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
 import org.thingsboard.server.common.msg.TbActorMsg;
-import org.thingsboard.server.common.msg.aware.SessionAwareMsg;
 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
 import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
 import org.thingsboard.server.common.msg.cluster.ServerAddress;
@@ -90,8 +88,6 @@ public class DefaultActorService implements ActorService {
 
     private ActorRef appActor;
 
-    private ActorRef sessionManagerActor;
-
     private ActorRef rpcManagerActor;
 
     @PostConstruct
@@ -104,10 +100,6 @@ public class DefaultActorService implements ActorService {
         appActor = system.actorOf(Props.create(new AppActor.ActorCreator(actorContext)).withDispatcher(APP_DISPATCHER_NAME), "appActor");
         actorContext.setAppActor(appActor);
 
-        sessionManagerActor = system.actorOf(Props.create(new SessionManagerActor.ActorCreator(actorContext)).withDispatcher(CORE_DISPATCHER_NAME),
-                "sessionManagerActor");
-        actorContext.setSessionManagerActor(sessionManagerActor);
-
         rpcManagerActor = system.actorOf(Props.create(new RpcManagerActor.ActorCreator(actorContext)).withDispatcher(CORE_DISPATCHER_NAME),
                 "rpcManagerActor");
 
@@ -135,12 +127,6 @@ public class DefaultActorService implements ActorService {
     }
 
     @Override
-    public void process(SessionAwareMsg msg) {
-        log.debug("Processing session aware msg: {}", msg);
-        sessionManagerActor.tell(msg, ActorRef.noSender());
-    }
-
-    @Override
     public void onServerAdded(ServerInstance server) {
         log.trace("Processing onServerAdded msg: {}", server);
         broadcast(new ClusterEventMsg(server.getServerAddress(), true));
@@ -194,7 +180,6 @@ public class DefaultActorService implements ActorService {
 
     private void broadcast(ClusterEventMsg msg) {
         this.appActor.tell(msg, ActorRef.noSender());
-        this.sessionManagerActor.tell(msg, ActorRef.noSender());
         this.rpcManagerActor.tell(msg, ActorRef.noSender());
     }
 
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 1cf8339..c9dc307 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
@@ -26,7 +26,6 @@ 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.service.queue.MsgQueueService;
 
 import javax.annotation.Nullable;
 import java.util.function.Consumer;
@@ -35,14 +34,12 @@ public abstract class ComponentMsgProcessor<T extends EntityId> extends Abstract
 
     protected final TenantId tenantId;
     protected final T entityId;
-    protected final MsgQueueService 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.getMsgQueueService();
     }
 
     public abstract void start(ActorContext context) throws Exception;
@@ -82,22 +79,9 @@ public abstract class ComponentMsgProcessor<T extends EntityId> extends Abstract
 
     protected void checkActive() {
         if (state != ComponentLifecycleState.ACTIVE) {
-            throw new IllegalStateException("Rule chain is not active!");
+            logger.warning("Rule chain is not active. Current state [{}] for processor [{}] tenant [{}]", state, tenantId, entityId);
+            throw new IllegalStateException("Rule chain is not active! " + entityId + " - " + tenantId);
         }
     }
 
-    protected void putToQueue(final TbMsg tbMsg, final Consumer<TbMsg> onSuccess) {
-        EntityId entityId = tbMsg.getRuleNodeId() != null ? tbMsg.getRuleNodeId() : tbMsg.getRuleChainId();
-        Futures.addCallback(queue.put(this.tenantId, tbMsg, entityId.getId(), tbMsg.getClusterPartition()), 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 460b64c..347483a 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
@@ -87,7 +87,7 @@ public class TenantActor extends RuleChainManagerActor {
             case DEVICE_ACTOR_TO_RULE_ENGINE_MSG:
                 onDeviceActorToRuleEngineMsg((DeviceActorToRuleEngineMsg) msg);
                 break;
-            case DEVICE_SESSION_TO_DEVICE_ACTOR_MSG:
+            case TRANSPORT_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:
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 889630b..582bbed 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -51,6 +51,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
 import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
 import org.thingsboard.server.dao.alarm.AlarmService;
 import org.thingsboard.server.dao.asset.AssetService;
+import org.thingsboard.server.dao.attributes.AttributesService;
 import org.thingsboard.server.dao.audit.AuditLogService;
 import org.thingsboard.server.dao.customer.CustomerService;
 import org.thingsboard.server.dao.dashboard.DashboardService;
@@ -70,6 +71,7 @@ import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
 import org.thingsboard.server.service.component.ComponentDiscoveryService;
 import org.thingsboard.server.service.security.model.SecurityUser;
 import org.thingsboard.server.service.state.DeviceStateService;
+import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
 
 import javax.mail.MessagingException;
 import javax.servlet.http.HttpServletRequest;
@@ -143,6 +145,12 @@ public abstract class BaseController {
     @Autowired
     protected EntityViewService entityViewService;
 
+    @Autowired
+    protected TelemetrySubscriptionService tsSubService;
+
+    @Autowired
+    protected AttributesService attributesService;
+
     @ExceptionHandler(ThingsboardException.class)
     public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) {
         errorResponseHandler.handle(ex, response);
@@ -251,7 +259,6 @@ public abstract class BaseController {
 
     Customer checkCustomerId(CustomerId customerId) throws ThingsboardException {
         try {
-            validateId(customerId, "Incorrect customerId " + customerId);
             SecurityUser authUser = getCurrentUser();
             if (authUser.getAuthority() == Authority.SYS_ADMIN ||
                     (authUser.getAuthority() != Authority.TENANT_ADMIN &&
@@ -259,9 +266,13 @@ public abstract class BaseController {
                 throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
                         ThingsboardErrorCode.PERMISSION_DENIED);
             }
-            Customer customer = customerService.findCustomerById(customerId);
-            checkCustomer(customer);
-            return customer;
+            if (customerId != null && !customerId.isNullUid()) {
+                Customer customer = customerService.findCustomerById(customerId);
+                checkCustomer(customer);
+                return customer;
+            } else {
+                return null;
+            }
         } catch (Exception e) {
             throw handleException(e, false);
         }
@@ -342,9 +353,7 @@ public abstract class BaseController {
     protected void checkDevice(Device device) throws ThingsboardException {
         checkNotNull(device);
         checkTenantId(device.getTenantId());
-        if (device.getCustomerId() != null && !device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
-            checkCustomerId(device.getCustomerId());
-        }
+        checkCustomerId(device.getCustomerId());
     }
 
     protected EntityView checkEntityViewId(EntityViewId entityViewId) throws ThingsboardException {
@@ -361,9 +370,7 @@ public abstract class BaseController {
     protected void checkEntityView(EntityView entityView) throws ThingsboardException {
         checkNotNull(entityView);
         checkTenantId(entityView.getTenantId());
-        if (entityView.getCustomerId() != null && !entityView.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
-            checkCustomerId(entityView.getCustomerId());
-        }
+        checkCustomerId(entityView.getCustomerId());
     }
 
     Asset checkAssetId(AssetId assetId) throws ThingsboardException {
@@ -380,9 +387,7 @@ public abstract class BaseController {
     protected void checkAsset(Asset asset) throws ThingsboardException {
         checkNotNull(asset);
         checkTenantId(asset.getTenantId());
-        if (asset.getCustomerId() != null && !asset.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
-            checkCustomerId(asset.getCustomerId());
-        }
+        checkCustomerId(asset.getCustomerId());
     }
 
     Alarm checkAlarmId(AlarmId alarmId) throws ThingsboardException {
@@ -491,8 +496,7 @@ public abstract class BaseController {
     ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException {
         try {
             log.debug("[{}] Lookup component descriptor", clazz);
-            ComponentDescriptor componentDescriptor = checkNotNull(componentDescriptorService.getComponent(clazz));
-            return componentDescriptor;
+            return checkNotNull(componentDescriptorService.getComponent(clazz));
         } catch (Exception e) {
             throw handleException(e, false);
         }
@@ -556,16 +560,16 @@ public abstract class BaseController {
     }
 
     protected <I extends EntityId> I emptyId(EntityType entityType) {
-        return (I)EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
+        return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
     }
 
     protected <E extends HasName, I extends EntityId> void logEntityAction(I entityId, E entity, CustomerId customerId,
-                                                                 ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException {
+                                                                           ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException {
         logEntityAction(getCurrentUser(), entityId, entity, customerId, actionType, e, additionalInfo);
     }
 
     protected <E extends HasName, I extends EntityId> void logEntityAction(User user, I entityId, E entity, CustomerId customerId,
-                                                                 ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException {
+                                                                           ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException {
         if (customerId == null || customerId.isNullUid()) {
             customerId = user.getCustomerId();
         }
@@ -581,7 +585,7 @@ public abstract class BaseController {
     }
 
     private <E extends HasName, I extends EntityId> void pushEntityActionToRuleEngine(I entityId, E entity, User user, CustomerId customerId,
-                                                                         ActionType actionType, Object... additionalInfo) {
+                                                                                      ActionType actionType, Object... additionalInfo) {
         String msgType = null;
         switch (actionType) {
             case ADDED:
@@ -605,6 +609,12 @@ public abstract class BaseController {
             case ATTRIBUTES_DELETED:
                 msgType = DataConstants.ATTRIBUTES_DELETED;
                 break;
+            case ALARM_ACK:
+                msgType = DataConstants.ALARM_ACK;
+                break;
+            case ALARM_CLEAR:
+                msgType = DataConstants.ALARM_CLEAR;
+                break;
         }
         if (!StringUtils.isEmpty(msgType)) {
             try {
@@ -654,7 +664,7 @@ public abstract class BaseController {
                         String scope = extractParameter(String.class, 0, additionalInfo);
                         List<String> keys = extractParameter(List.class, 1, additionalInfo);
                         metaData.putValue("scope", scope);
-                        ArrayNode attrsArrayNode =  entityNode.putArray("attributes");
+                        ArrayNode attrsArrayNode = entityNode.putArray("attributes");
                         if (keys != null) {
                             keys.forEach(attrsArrayNode::add);
                         }
diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
index a1ba1c6..9b7ffd2 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
@@ -15,7 +15,10 @@
  */
 package org.thingsboard.server.controller;
 
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.HttpStatus;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -26,7 +29,9 @@ 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.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
 import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.DataConstants;
 import org.thingsboard.server.common.data.EntitySubtype;
 import org.thingsboard.server.common.data.EntityType;
 import org.thingsboard.server.common.data.EntityView;
@@ -34,15 +39,24 @@ import org.thingsboard.server.common.data.audit.ActionType;
 import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
 import org.thingsboard.server.common.data.exception.ThingsboardException;
 import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.id.EntityViewId;
 import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.UUIDBased;
+import org.thingsboard.server.common.data.kv.AttributeKvEntry;
 import org.thingsboard.server.common.data.page.TextPageData;
 import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
 import org.thingsboard.server.dao.exception.IncorrectParameterException;
 import org.thingsboard.server.dao.model.ModelConstants;
 import org.thingsboard.server.service.security.model.SecurityUser;
 
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
+import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
 
 import static org.thingsboard.server.controller.CustomerController.CUSTOMER_ID;
@@ -52,6 +66,7 @@ import static org.thingsboard.server.controller.CustomerController.CUSTOMER_ID;
  */
 @RestController
 @RequestMapping("/api")
+@Slf4j
 public class EntityViewController extends BaseController {
 
     public static final String ENTITY_VIEW_ID = "entityViewId";
@@ -75,6 +90,20 @@ public class EntityViewController extends BaseController {
         try {
             entityView.setTenantId(getCurrentUser().getTenantId());
             EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView));
+            List<ListenableFuture<List<Void>>> futures = new ArrayList<>();
+            if (savedEntityView.getKeys() != null && savedEntityView.getKeys().getAttributes() != null) {
+                futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.CLIENT_SCOPE, savedEntityView.getKeys().getAttributes().getCs(), getCurrentUser()));
+                futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SERVER_SCOPE, savedEntityView.getKeys().getAttributes().getSs(), getCurrentUser()));
+                futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SHARED_SCOPE, savedEntityView.getKeys().getAttributes().getSh(), getCurrentUser()));
+            }
+            for (ListenableFuture<List<Void>> future : futures) {
+                try {
+                    future.get();
+                } catch (InterruptedException | ExecutionException e) {
+                    throw new RuntimeException("Failed to copy attributes to entity view", e);
+                }
+            }
+
             logEntityAction(savedEntityView.getId(), savedEntityView, null,
                     entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
             return savedEntityView;
@@ -85,6 +114,56 @@ public class EntityViewController extends BaseController {
         }
     }
 
+    private ListenableFuture<List<Void>> copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection<String> keys, SecurityUser user) throws ThingsboardException {
+        EntityViewId entityId = entityView.getId();
+        if (keys != null && !keys.isEmpty()) {
+            ListenableFuture<List<AttributeKvEntry>> getAttrFuture = attributesService.find(entityView.getEntityId(), scope, keys);
+            return Futures.transform(getAttrFuture, attributeKvEntries -> {
+                List<AttributeKvEntry> attributes;
+                if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) {
+                    attributes =
+                            attributeKvEntries.stream()
+                                    .filter(attributeKvEntry -> {
+                                        long startTime = entityView.getStartTimeMs();
+                                        long endTime = entityView.getEndTimeMs();
+                                        long lastUpdateTs = attributeKvEntry.getLastUpdateTs();
+                                        return startTime == 0 && endTime == 0 ||
+                                                (endTime == 0 && startTime < lastUpdateTs) ||
+                                                (startTime == 0 && endTime > lastUpdateTs)
+                                                ? true : startTime < lastUpdateTs && endTime > lastUpdateTs;
+                                    }).collect(Collectors.toList());
+                    tsSubService.saveAndNotify(entityId, scope, attributes, new FutureCallback<Void>() {
+                        @Override
+                        public void onSuccess(@Nullable Void tmp) {
+                            try {
+                                logAttributesUpdated(user, entityId, scope, attributes, null);
+                            } catch (ThingsboardException e) {
+                                log.error("Failed to log attribute updates", e);
+                            }
+                        }
+
+                        @Override
+                        public void onFailure(Throwable t) {
+                            try {
+                                logAttributesUpdated(user, entityId, scope, attributes, t);
+                            } catch (ThingsboardException e) {
+                                log.error("Failed to log attribute updates", e);
+                            }
+                        }
+                    });
+                }
+                return null;
+            });
+        } else {
+            return Futures.immediateFuture(null);
+        }
+    }
+
+    private void logAttributesUpdated(SecurityUser user, EntityId entityId, String scope, List<AttributeKvEntry> attributes, Throwable e) throws ThingsboardException {
+        logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, ActionType.ATTRIBUTES_UPDATED, toException(e),
+                scope, attributes);
+    }
+
     @PreAuthorize("hasAuthority('TENANT_ADMIN')")
     @RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.DELETE)
     @ResponseStatus(value = HttpStatus.OK)
diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
index 7062ca9..6c37614 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
@@ -59,7 +59,6 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
 import org.thingsboard.server.common.data.kv.StringDataEntry;
 import org.thingsboard.server.common.data.kv.TsKvEntry;
 import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
-import org.thingsboard.server.common.msg.core.TelemetryUploadRequest;
 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
 import org.thingsboard.server.dao.attributes.AttributesService;
 import org.thingsboard.server.dao.timeseries.TimeseriesService;
@@ -94,15 +93,9 @@ import java.util.stream.Collectors;
 public class TelemetryController extends BaseController {
 
     @Autowired
-    private AttributesService attributesService;
-
-    @Autowired
     private TimeseriesService tsService;
 
     @Autowired
-    private TelemetrySubscriptionService tsSubService;
-
-    @Autowired
     private AccessValidator accessValidator;
 
     private ExecutorService executor;
@@ -352,7 +345,7 @@ public class TelemetryController extends BaseController {
     }
 
     private DeferredResult<ResponseEntity> saveTelemetry(EntityId entityIdSrc, String requestBody, long ttl) throws ThingsboardException {
-        TelemetryUploadRequest telemetryRequest;
+        Map<Long, List<KvEntry>> telemetryRequest;
         JsonElement telemetryJson;
         try {
             telemetryJson = new JsonParser().parse(requestBody);
@@ -360,12 +353,12 @@ public class TelemetryController extends BaseController {
             return getImmediateDeferredResult("Unable to parse timeseries payload: Invalid JSON body!", HttpStatus.BAD_REQUEST);
         }
         try {
-            telemetryRequest = JsonConverter.convertToTelemetry(telemetryJson);
+            telemetryRequest = JsonConverter.convertToTelemetry(telemetryJson, System.currentTimeMillis());
         } catch (Exception e) {
             return getImmediateDeferredResult("Unable to parse timeseries payload. Invalid JSON body: " + e.getMessage(), HttpStatus.BAD_REQUEST);
         }
         List<TsKvEntry> entries = new ArrayList<>();
-        for (Map.Entry<Long, List<KvEntry>> entry : telemetryRequest.getData().entrySet()) {
+        for (Map.Entry<Long, List<KvEntry>> entry : telemetryRequest.entrySet()) {
             for (KvEntry kv : entry.getValue()) {
                 entries.add(new BasicTsKvEntry(entry.getKey(), kv));
             }
diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryService.java
index 04c4ced..f9caafa 100644
--- a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryService.java
+++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryService.java
@@ -26,8 +26,6 @@ public interface DiscoveryService {
 
     void unpublishCurrentServer();
 
-    String getNodeId();
-
     ServerInstance getCurrentServer();
 
     List<ServerInstance> getOtherServers();
diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DummyDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DummyDiscoveryService.java
index 859a7af..358c847 100644
--- a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DummyDiscoveryService.java
+++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DummyDiscoveryService.java
@@ -38,17 +38,9 @@ public class DummyDiscoveryService implements DiscoveryService {
     @Autowired
     private ServerInstanceService serverInstance;
 
-    private String nodeId;
-
     @PostConstruct
     public void init() {
         log.info("Initializing...");
-        this.nodeId = RandomStringUtils.randomAlphabetic(10);
-    }
-
-    @Override
-    public String getNodeId() {
-        return nodeId;
     }
 
     @Override
diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java
index b4829f2..bb92642 100644
--- a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java
+++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java
@@ -94,13 +94,10 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
     private CuratorFramework client;
     private PathChildrenCache cache;
     private String nodePath;
-    //TODO: make persistent?
-    private String nodeId;
 
     @PostConstruct
     public void init() {
         log.info("Initializing...");
-        this.nodeId = RandomStringUtils.randomAlphabetic(10);
         Assert.hasLength(zkUrl, MiscUtils.missingProperty("zk.url"));
         Assert.notNull(zkRetryInterval, MiscUtils.missingProperty("zk.retry_interval_ms"));
         Assert.notNull(zkConnectionTimeout, MiscUtils.missingProperty("zk.connection_timeout_ms"));
@@ -184,11 +181,6 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
     }
 
     @Override
-    public String getNodeId() {
-        return nodeId;
-    }
-
-    @Override
     public ServerInstance getCurrentServer() {
         return serverInstance.getSelf();
     }
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
index 201f656..d33a338 100644
--- a/application/src/main/java/org/thingsboard/server/service/rpc/ToServerRpcResponseActorMsg.java
+++ b/application/src/main/java/org/thingsboard/server/service/rpc/ToServerRpcResponseActorMsg.java
@@ -22,11 +22,8 @@ import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg;
 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 java.util.Optional;
-
 /**
  * Created by ashvayka on 16.04.18.
  */
diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java
index 37a6211..db31bda 100644
--- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java
+++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java
@@ -28,6 +28,7 @@ import org.thingsboard.server.kafka.TBKafkaConsumerTemplate;
 import org.thingsboard.server.kafka.TBKafkaProducerTemplate;
 import org.thingsboard.server.kafka.TbKafkaRequestTemplate;
 import org.thingsboard.server.kafka.TbKafkaSettings;
+import org.thingsboard.server.kafka.TbNodeIdProvider;
 import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
 
 import javax.annotation.PostConstruct;
@@ -42,7 +43,7 @@ import java.util.concurrent.ConcurrentHashMap;
 public class RemoteJsInvokeService extends AbstractJsInvokeService {
 
     @Autowired
-    private DiscoveryService discoveryService;
+    private TbNodeIdProvider nodeIdProvider;
 
     @Autowired
     private TbKafkaSettings kafkaSettings;
@@ -97,15 +98,13 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
 
         TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder<JsInvokeProtos.RemoteJsResponse> responseBuilder = TBKafkaConsumerTemplate.builder();
         responseBuilder.settings(kafkaSettings);
-        responseBuilder.topic(responseTopicPrefix + "." + discoveryService.getNodeId());
-        responseBuilder.clientId(discoveryService.getNodeId());
+        responseBuilder.topic(responseTopicPrefix + "." + nodeIdProvider.getNodeId());
+        responseBuilder.clientId("js-" + nodeIdProvider.getNodeId());
         responseBuilder.groupId("rule-engine-node");
         responseBuilder.autoCommit(true);
         responseBuilder.autoCommitIntervalMs(autoCommitInterval);
         responseBuilder.decoder(new RemoteJsResponseDecoder());
-        responseBuilder.requestIdExtractor((response) -> {
-            return new UUID(response.getRequestIdMSB(), response.getRequestIdLSB());
-        });
+        responseBuilder.requestIdExtractor((response) -> new UUID(response.getRequestIdMSB(), response.getRequestIdLSB()));
 
         TbKafkaRequestTemplate.TbKafkaRequestTemplateBuilder
                 <JsInvokeProtos.RemoteJsRequest, JsInvokeProtos.RemoteJsResponse> builder = TbKafkaRequestTemplate.builder();
diff --git a/application/src/main/java/org/thingsboard/server/service/security/ValidationCallback.java b/application/src/main/java/org/thingsboard/server/service/security/ValidationCallback.java
index 81ab9b4..d4ac753 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/ValidationCallback.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/ValidationCallback.java
@@ -36,29 +36,10 @@ public class ValidationCallback<T> implements FutureCallback<ValidationResult> {
 
     @Override
     public void onSuccess(ValidationResult result) {
-        ValidationResultCode resultCode = result.getResultCode();
-        if (resultCode == ValidationResultCode.OK) {
+        if (result.getResultCode() == ValidationResultCode.OK) {
             action.onSuccess(response);
         } else {
-            Exception e;
-            switch (resultCode) {
-                case ENTITY_NOT_FOUND:
-                    e = new EntityNotFoundException(result.getMessage());
-                    break;
-                case UNAUTHORIZED:
-                    e = new UnauthorizedException(result.getMessage());
-                    break;
-                case ACCESS_DENIED:
-                    e = new AccessDeniedException(result.getMessage());
-                    break;
-                case INTERNAL_ERROR:
-                    e = new InternalErrorException(result.getMessage());
-                    break;
-                default:
-                    e = new UnauthorizedException("Permission denied.");
-                    break;
-            }
-            onFailure(e);
+            onFailure(getException(result));
         }
     }
 
@@ -66,4 +47,28 @@ public class ValidationCallback<T> implements FutureCallback<ValidationResult> {
     public void onFailure(Throwable e) {
         action.onFailure(e);
     }
+
+    public static Exception getException(ValidationResult result) {
+        ValidationResultCode resultCode = result.getResultCode();
+        Exception e;
+        switch (resultCode) {
+            case ENTITY_NOT_FOUND:
+                e = new EntityNotFoundException(result.getMessage());
+                break;
+            case UNAUTHORIZED:
+                e = new UnauthorizedException(result.getMessage());
+                break;
+            case ACCESS_DENIED:
+                e = new AccessDeniedException(result.getMessage());
+                break;
+            case INTERNAL_ERROR:
+                e = new InternalErrorException(result.getMessage());
+                break;
+            default:
+                e = new UnauthorizedException("Permission denied.");
+                break;
+        }
+        return e;
+    }
+
 }
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 bcca43c..3b0deea 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
@@ -143,7 +143,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
     public void addLocalWsSubscription(String sessionId, EntityId entityId, SubscriptionState sub) {
         long startTime = 0L;
         long endTime = 0L;
-        if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) {
+        if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW) && TelemetryFeature.TIMESERIES.equals(sub.getType())) {
             EntityView entityView = entityViewService.findEntityViewById(new EntityViewId(entityId.getId()));
             entityId = entityView.getEntityId();
             startTime = entityView.getStartTimeMs();
@@ -165,38 +165,15 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
     }
 
     private SubscriptionState getUpdatedSubscriptionState(EntityId entityId, SubscriptionState sub, EntityView entityView) {
-        boolean allKeys;
         Map<String, Long> keyStates;
-        if (sub.getType().equals(TelemetryFeature.TIMESERIES) && !entityView.getKeys().getTimeseries().isEmpty()) {
-            allKeys = false;
+        if(sub.isAllKeys()) {
+            keyStates = entityView.getKeys().getTimeseries().stream().collect(Collectors.toMap(k -> k, k -> 0L));
+        } else {
             keyStates = sub.getKeyStates().entrySet()
                     .stream().filter(entry -> entityView.getKeys().getTimeseries().contains(entry.getKey()))
                     .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
-        } else if (sub.getType().equals(TelemetryFeature.ATTRIBUTES)) {
-            if (sub.getScope().equals(DataConstants.CLIENT_SCOPE) && !entityView.getKeys().getAttributes().getCs().isEmpty()) {
-                allKeys = false;
-                keyStates = filterMap(sub, entityView.getKeys().getAttributes().getCs());
-            } else if (sub.getScope().equals(DataConstants.SERVER_SCOPE) && !entityView.getKeys().getAttributes().getSs().isEmpty()) {
-                allKeys = false;
-                keyStates = filterMap(sub, entityView.getKeys().getAttributes().getSs());
-            } else if (sub.getScope().equals(DataConstants.SERVER_SCOPE) && !entityView.getKeys().getAttributes().getSh().isEmpty()) {
-                allKeys = false;
-                keyStates = filterMap(sub, entityView.getKeys().getAttributes().getSh());
-            } else {
-                allKeys = sub.isAllKeys();
-                keyStates = sub.getKeyStates();
-            }
-        } else {
-            allKeys = sub.isAllKeys();
-            keyStates = sub.getKeyStates();
         }
-        return new SubscriptionState(sub.getWsSessionId(), sub.getSubscriptionId(), entityId, sub.getType(), allKeys, keyStates, sub.getScope());
-    }
-
-    private Map<String, Long> filterMap(SubscriptionState sub, List<String> allowedKeys) {
-        return sub.getKeyStates().entrySet()
-                .stream().filter(entry -> allowedKeys.contains(entry.getKey()))
-                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+        return new SubscriptionState(sub.getWsSessionId(), sub.getSubscriptionId(), entityId, sub.getType(), false, keyStates, sub.getScope());
     }
 
     @Override
@@ -467,7 +444,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
         onLocalSubUpdate(entityId, s -> TelemetryFeature.ATTRIBUTES == s.getType() && (StringUtils.isEmpty(s.getScope()) || scope.equals(s.getScope())), s -> {
             List<TsKvEntry> subscriptionUpdate = null;
             for (AttributeKvEntry kv : attributes) {
-                if (isInTimeRange(s, kv.getLastUpdateTs()) && (s.isAllKeys() || s.getKeyStates().containsKey(kv.getKey()))) {
+                if (s.isAllKeys() || s.getKeyStates().containsKey(kv.getKey())) {
                     if (subscriptionUpdate == null) {
                         subscriptionUpdate = new ArrayList<>();
                     }
diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
index 2ff8e89..1251ca3 100644
--- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
+++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
@@ -37,13 +37,18 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
 import org.thingsboard.server.dao.attributes.AttributesService;
 import org.thingsboard.server.dao.timeseries.TimeseriesService;
 import org.thingsboard.server.service.security.AccessValidator;
+import org.thingsboard.server.service.security.ValidationCallback;
 import org.thingsboard.server.service.security.ValidationResult;
+import org.thingsboard.server.service.security.ValidationResultCode;
 import org.thingsboard.server.service.telemetry.cmd.AttributesSubscriptionCmd;
 import org.thingsboard.server.service.telemetry.cmd.GetHistoryCmd;
 import org.thingsboard.server.service.telemetry.cmd.SubscriptionCmd;
 import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmd;
 import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
 import org.thingsboard.server.service.telemetry.cmd.TimeseriesSubscriptionCmd;
+import org.thingsboard.server.service.telemetry.exception.AccessDeniedException;
+import org.thingsboard.server.service.telemetry.exception.EntityNotFoundException;
+import org.thingsboard.server.service.telemetry.exception.InternalErrorException;
 import org.thingsboard.server.service.telemetry.exception.UnauthorizedException;
 import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
 import org.thingsboard.server.service.telemetry.sub.SubscriptionState;
@@ -535,11 +540,16 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
         };
     }
 
-    private FutureCallback<ValidationResult> on(Consumer<ValidationResult> success, Consumer<Throwable> failure) {
+    private FutureCallback<ValidationResult> on(Consumer<Void> success, Consumer<Throwable> failure) {
         return new FutureCallback<ValidationResult>() {
             @Override
             public void onSuccess(@Nullable ValidationResult result) {
-                success.accept(result);
+                ValidationResultCode resultCode = result.getResultCode();
+                if (resultCode == ValidationResultCode.OK) {
+                    success.accept(null);
+                } else {
+                    onFailure(ValidationCallback.getException(result));
+                }
             }
 
             @Override
diff --git a/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportApiService.java
new file mode 100644
index 0000000..5c39f8a
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportApiService.java
@@ -0,0 +1,173 @@
+/**
+ * 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.transport;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+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.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.common.data.security.DeviceCredentialsType;
+import org.thingsboard.server.dao.device.DeviceCredentialsService;
+import org.thingsboard.server.dao.device.DeviceService;
+import org.thingsboard.server.dao.relation.RelationService;
+import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
+import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
+import org.thingsboard.server.kafka.TBKafkaConsumerTemplate;
+import org.thingsboard.server.kafka.TBKafkaProducerTemplate;
+import org.thingsboard.server.kafka.TbKafkaResponseTemplate;
+import org.thingsboard.server.kafka.TbKafkaSettings;
+import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
+import org.thingsboard.server.service.executors.DbCallbackExecutorService;
+import org.thingsboard.server.service.state.DeviceStateService;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Created by ashvayka on 05.10.18.
+ */
+@Slf4j
+@Service
+public class LocalTransportApiService implements TransportApiService {
+
+    private static final ObjectMapper mapper = new ObjectMapper();
+
+    @Autowired
+    private DeviceService deviceService;
+
+    @Autowired
+    private RelationService relationService;
+
+    @Autowired
+    private DeviceCredentialsService deviceCredentialsService;
+
+    @Autowired
+    private DeviceStateService deviceStateService;
+
+    @Autowired
+    private DbCallbackExecutorService dbCallbackExecutorService;
+
+    private ReentrantLock deviceCreationLock = new ReentrantLock();
+
+    @Override
+    public ListenableFuture<TransportApiResponseMsg> handle(TransportApiRequestMsg transportApiRequestMsg) {
+        if (transportApiRequestMsg.hasValidateTokenRequestMsg()) {
+            ValidateDeviceTokenRequestMsg msg = transportApiRequestMsg.getValidateTokenRequestMsg();
+            return validateCredentials(msg.getToken(), DeviceCredentialsType.ACCESS_TOKEN);
+        } else if (transportApiRequestMsg.hasValidateX509CertRequestMsg()) {
+            ValidateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateX509CertRequestMsg();
+            return validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE);
+        } else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) {
+            return handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg());
+        }
+        return getEmptyTransportApiResponseFuture();
+    }
+
+    private ListenableFuture<TransportApiResponseMsg> validateCredentials(String credentialsId, DeviceCredentialsType credentialsType) {
+        //TODO: Make async and enable caching
+        DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(credentialsId);
+        if (credentials != null && credentials.getCredentialsType() == credentialsType) {
+            return getDeviceInfo(credentials.getDeviceId());
+        } else {
+            return getEmptyTransportApiResponseFuture();
+        }
+    }
+
+    private ListenableFuture<TransportApiResponseMsg> handle(GetOrCreateDeviceFromGatewayRequestMsg requestMsg) {
+        DeviceId gatewayId = new DeviceId(new UUID(requestMsg.getGatewayIdMSB(), requestMsg.getGatewayIdLSB()));
+        ListenableFuture<Device> gatewayFuture = deviceService.findDeviceByIdAsync(gatewayId);
+        return Futures.transform(gatewayFuture, gateway -> {
+            deviceCreationLock.lock();
+            try {
+                Device device = deviceService.findDeviceByTenantIdAndName(gateway.getTenantId(), requestMsg.getDeviceName());
+                if (device == null) {
+                    device = new Device();
+                    device.setTenantId(gateway.getTenantId());
+                    device.setName(requestMsg.getDeviceName());
+                    device.setType(requestMsg.getDeviceType());
+                    device.setCustomerId(gateway.getCustomerId());
+                    device = deviceService.saveDevice(device);
+                    relationService.saveRelationAsync(new EntityRelation(gateway.getId(), device.getId(), "Created"));
+                    deviceStateService.onDeviceAdded(device);
+                }
+                return TransportApiResponseMsg.newBuilder()
+                        .setGetOrCreateDeviceResponseMsg(GetOrCreateDeviceFromGatewayResponseMsg.newBuilder().setDeviceInfo(getDeviceInfoProto(device)).build()).build();
+            } catch (JsonProcessingException e) {
+                log.warn("[{}] Failed to lookup device by gateway id and name", gatewayId, requestMsg.getDeviceName(), e);
+                throw new RuntimeException(e);
+            } finally {
+                deviceCreationLock.unlock();
+            }
+        }, dbCallbackExecutorService);
+    }
+
+
+    private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId) {
+        return Futures.transform(deviceService.findDeviceByIdAsync(deviceId), device -> {
+            if (device == null) {
+                log.trace("[{}] Failed to lookup device by id", deviceId);
+                return getEmptyTransportApiResponse();
+            }
+            try {
+                return TransportApiResponseMsg.newBuilder()
+                        .setValidateTokenResponseMsg(ValidateDeviceCredentialsResponseMsg.newBuilder().setDeviceInfo(getDeviceInfoProto(device)).build()).build();
+            } catch (JsonProcessingException e) {
+                log.warn("[{}] Failed to lookup device by id", deviceId, e);
+                return getEmptyTransportApiResponse();
+            }
+        });
+    }
+
+    private DeviceInfoProto getDeviceInfoProto(Device device) throws JsonProcessingException {
+        return DeviceInfoProto.newBuilder()
+                .setTenantIdMSB(device.getTenantId().getId().getMostSignificantBits())
+                .setTenantIdLSB(device.getTenantId().getId().getLeastSignificantBits())
+                .setDeviceIdMSB(device.getId().getId().getMostSignificantBits())
+                .setDeviceIdLSB(device.getId().getId().getLeastSignificantBits())
+                .setDeviceName(device.getName())
+                .setDeviceType(device.getType())
+                .setAdditionalInfo(mapper.writeValueAsString(device.getAdditionalInfo()))
+                .build();
+    }
+
+    private ListenableFuture<TransportApiResponseMsg> getEmptyTransportApiResponseFuture() {
+        return Futures.immediateFuture(getEmptyTransportApiResponse());
+    }
+
+    private TransportApiResponseMsg getEmptyTransportApiResponse() {
+        return TransportApiResponseMsg.newBuilder()
+                .setValidateTokenResponseMsg(ValidateDeviceCredentialsResponseMsg.getDefaultInstance()).build();
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportService.java b/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportService.java
new file mode 100644
index 0000000..0f72230
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportService.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.service.transport;
+
+import akka.actor.ActorRef;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Service;
+import org.thingsboard.rule.engine.api.util.DonAsynchron;
+import org.thingsboard.server.actors.ActorSystemContext;
+import org.thingsboard.server.common.msg.cluster.ServerAddress;
+import org.thingsboard.server.common.transport.SessionMsgListener;
+import org.thingsboard.server.common.transport.TransportService;
+import org.thingsboard.server.common.transport.TransportServiceCallback;
+import org.thingsboard.server.common.transport.service.AbstractTransportService;
+import org.thingsboard.server.gen.transport.TransportProtos;
+import org.thingsboard.server.gen.transport.TransportProtos.DeviceActorToTransportMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
+import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
+import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
+import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
+import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
+import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Created by ashvayka on 12.10.18.
+ */
+@Slf4j
+@Service
+@ConditionalOnProperty(prefix = "transport", value = "type", havingValue = "local")
+public class LocalTransportService extends AbstractTransportService implements RuleEngineTransportService {
+
+    @Autowired
+    private TransportApiService transportApiService;
+
+    @Autowired
+    private ActorSystemContext actorContext;
+
+    //TODO: completely replace this routing with the Kafka routing by partition ids.
+    @Autowired
+    private ClusterRoutingService routingService;
+    @Autowired
+    private ClusterRpcService rpcService;
+    @Autowired
+    private DataDecodingEncodingService encodingService;
+
+    @PostConstruct
+    public void init() {
+        super.init();
+    }
+
+    @PreDestroy
+    public void destroy() {
+        super.destroy();
+    }
+
+    @Override
+    public void process(ValidateDeviceTokenRequestMsg msg, TransportServiceCallback<ValidateDeviceCredentialsResponseMsg> callback) {
+        DonAsynchron.withCallback(
+                transportApiService.handle(TransportApiRequestMsg.newBuilder().setValidateTokenRequestMsg(msg).build()),
+                transportApiResponseMsg -> {
+                    if (callback != null) {
+                        callback.onSuccess(transportApiResponseMsg.getValidateTokenResponseMsg());
+                    }
+                },
+                getThrowableConsumer(callback), transportCallbackExecutor);
+    }
+
+    @Override
+    public void process(ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback<ValidateDeviceCredentialsResponseMsg> callback) {
+        DonAsynchron.withCallback(
+                transportApiService.handle(TransportApiRequestMsg.newBuilder().setValidateX509CertRequestMsg(msg).build()),
+                transportApiResponseMsg -> {
+                    if (callback != null) {
+                        callback.onSuccess(transportApiResponseMsg.getValidateTokenResponseMsg());
+                    }
+                },
+                getThrowableConsumer(callback), transportCallbackExecutor);
+    }
+
+    @Override
+    public void process(GetOrCreateDeviceFromGatewayRequestMsg msg, TransportServiceCallback<GetOrCreateDeviceFromGatewayResponseMsg> callback) {
+        DonAsynchron.withCallback(
+                transportApiService.handle(TransportApiRequestMsg.newBuilder().setGetOrCreateDeviceRequestMsg(msg).build()),
+                transportApiResponseMsg -> {
+                    if (callback != null) {
+                        callback.onSuccess(transportApiResponseMsg.getGetOrCreateDeviceResponseMsg());
+                    }
+                },
+                getThrowableConsumer(callback), transportCallbackExecutor);
+    }
+
+    @Override
+    public void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback<Void> callback) {
+        if (checkLimits(sessionInfo, callback)) {
+            forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSessionEvent(msg).build(), callback);
+        }
+    }
+
+    @Override
+    public void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback<Void> callback) {
+        if (checkLimits(sessionInfo, callback)) {
+            forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setPostTelemetry(msg).build(), callback);
+        }
+    }
+
+    @Override
+    public void process(SessionInfoProto sessionInfo, PostAttributeMsg msg, TransportServiceCallback<Void> callback) {
+        if (checkLimits(sessionInfo, callback)) {
+            forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setPostAttributes(msg).build(), callback);
+        }
+    }
+
+    @Override
+    public void process(SessionInfoProto sessionInfo, GetAttributeRequestMsg msg, TransportServiceCallback<Void> callback) {
+        if (checkLimits(sessionInfo, callback)) {
+            forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setGetAttributes(msg).build(), callback);
+        }
+    }
+
+    @Override
+    public void process(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback<Void> callback) {
+        if (checkLimits(sessionInfo, callback)) {
+            forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSubscribeToAttributes(msg).build(), callback);
+        }
+    }
+
+    @Override
+    public void process(SessionInfoProto sessionInfo, SubscribeToRPCMsg msg, TransportServiceCallback<Void> callback) {
+        if (checkLimits(sessionInfo, callback)) {
+            forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSubscribeToRPC(msg).build(), callback);
+        }
+    }
+
+    @Override
+    public void process(SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg msg, TransportServiceCallback<Void> callback) {
+        if (checkLimits(sessionInfo, callback)) {
+            forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setToDeviceRPCCallResponse(msg).build(), callback);
+        }
+    }
+
+    @Override
+    public void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback<Void> callback) {
+        if (checkLimits(sessionInfo, callback)) {
+            forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setToServerRPCCallRequest(msg).build(), callback);
+        }
+    }
+
+    @Override
+    public void process(String nodeId, DeviceActorToTransportMsg msg) {
+        process(nodeId, msg, null, null);
+    }
+
+    @Override
+    public void process(String nodeId, DeviceActorToTransportMsg msg, Runnable onSuccess, Consumer<Throwable> onFailure) {
+        processToTransportMsg(msg);
+        if (onSuccess != null) {
+            onSuccess.run();
+        }
+    }
+
+    private void forwardToDeviceActor(TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback<Void> callback) {
+        TransportToDeviceActorMsgWrapper wrapper = new TransportToDeviceActorMsgWrapper(toDeviceActorMsg);
+        Optional<ServerAddress> address = routingService.resolveById(wrapper.getDeviceId());
+        if (address.isPresent()) {
+            rpcService.tell(encodingService.convertToProtoDataMessage(address.get(), wrapper));
+        } else {
+            actorContext.getAppActor().tell(wrapper, ActorRef.noSender());
+        }
+        if (callback != null) {
+            callback.onSuccess(null);
+        }
+    }
+
+    private <T> Consumer<Throwable> getThrowableConsumer(TransportServiceCallback<T> callback) {
+        return e -> {
+            if (callback != null) {
+                callback.onError(e);
+            }
+        };
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/transport/RemoteRuleEngineTransportService.java b/application/src/main/java/org/thingsboard/server/service/transport/RemoteRuleEngineTransportService.java
new file mode 100644
index 0000000..38db1a4
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/transport/RemoteRuleEngineTransportService.java
@@ -0,0 +1,200 @@
+/**
+ * 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.transport;
+
+import akka.actor.ActorRef;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.kafka.clients.consumer.ConsumerRecords;
+import org.apache.kafka.clients.producer.Callback;
+import org.apache.kafka.clients.producer.RecordMetadata;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.actors.ActorSystemContext;
+import org.thingsboard.server.actors.service.ActorService;
+import org.thingsboard.server.common.msg.cluster.ServerAddress;
+import org.thingsboard.server.gen.transport.TransportProtos.DeviceActorToTransportMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
+import org.thingsboard.server.kafka.TBKafkaConsumerTemplate;
+import org.thingsboard.server.kafka.TBKafkaProducerTemplate;
+import org.thingsboard.server.kafka.TbKafkaSettings;
+import org.thingsboard.server.kafka.TbNodeIdProvider;
+import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
+import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
+import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
+import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
+import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.time.Duration;
+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 09.10.18.
+ */
+@Slf4j
+@Service
+@ConditionalOnProperty(prefix = "transport", value = "type", havingValue = "remote")
+public class RemoteRuleEngineTransportService implements RuleEngineTransportService {
+
+    @Value("${transport.remote.rule_engine.topic}")
+    private String ruleEngineTopic;
+    @Value("${transport.remote.notifications.topic}")
+    private String notificationsTopic;
+    @Value("${transport.remote.rule_engine.poll_interval}")
+    private int pollDuration;
+    @Value("${transport.remote.rule_engine.auto_commit_interval}")
+    private int autoCommitInterval;
+
+    @Autowired
+    private TbKafkaSettings kafkaSettings;
+
+    @Autowired
+    private TbNodeIdProvider nodeIdProvider;
+
+    @Autowired
+    private ActorSystemContext actorContext;
+
+    //TODO: completely replace this routing with the Kafka routing by partition ids.
+    @Autowired
+    private ClusterRoutingService routingService;
+    @Autowired
+    private ClusterRpcService rpcService;
+    @Autowired
+    private DataDecodingEncodingService encodingService;
+
+    private TBKafkaConsumerTemplate<ToRuleEngineMsg> ruleEngineConsumer;
+    private TBKafkaProducerTemplate<ToTransportMsg> notificationsProducer;
+
+    private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor();
+
+    private volatile boolean stopped = false;
+
+    @PostConstruct
+    public void init() {
+        TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder<ToTransportMsg> notificationsProducerBuilder = TBKafkaProducerTemplate.builder();
+        notificationsProducerBuilder.settings(kafkaSettings);
+        notificationsProducerBuilder.defaultTopic(notificationsTopic);
+        notificationsProducerBuilder.encoder(new ToTransportMsgEncoder());
+
+        notificationsProducer = notificationsProducerBuilder.build();
+        notificationsProducer.init();
+
+        TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder<ToRuleEngineMsg> ruleEngineConsumerBuilder = TBKafkaConsumerTemplate.builder();
+        ruleEngineConsumerBuilder.settings(kafkaSettings);
+        ruleEngineConsumerBuilder.topic(ruleEngineTopic);
+        ruleEngineConsumerBuilder.clientId("transport-" + nodeIdProvider.getNodeId());
+        ruleEngineConsumerBuilder.groupId("tb-node");
+        ruleEngineConsumerBuilder.autoCommit(true);
+        ruleEngineConsumerBuilder.autoCommitIntervalMs(autoCommitInterval);
+        ruleEngineConsumerBuilder.decoder(new ToRuleEngineMsgDecoder());
+
+        ruleEngineConsumer = ruleEngineConsumerBuilder.build();
+        ruleEngineConsumer.subscribe();
+
+        mainConsumerExecutor.execute(() -> {
+            while (!stopped) {
+                try {
+                    ConsumerRecords<String, byte[]> records = ruleEngineConsumer.poll(Duration.ofMillis(pollDuration));
+                    records.forEach(record -> {
+                        try {
+                            ToRuleEngineMsg toRuleEngineMsg = ruleEngineConsumer.decode(record);
+                            if (toRuleEngineMsg.hasToDeviceActorMsg()) {
+                                forwardToDeviceActor(toRuleEngineMsg.getToDeviceActorMsg());
+                            }
+                        } catch (Throwable e) {
+                            log.warn("Failed to process the notification.", e);
+                        }
+                    });
+                } catch (Exception e) {
+                    log.warn("Failed to obtain messages from queue.", e);
+                    try {
+                        Thread.sleep(pollDuration);
+                    } catch (InterruptedException e2) {
+                        log.trace("Failed to wait until the server has capacity to handle new requests", e2);
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public void process(String nodeId, DeviceActorToTransportMsg msg) {
+        process(nodeId, msg, null, null);
+    }
+
+    @Override
+    public void process(String nodeId, DeviceActorToTransportMsg msg, Runnable onSuccess, Consumer<Throwable> onFailure) {
+        notificationsProducer.send(notificationsTopic + "." + nodeId,
+                new UUID(msg.getSessionIdMSB(), msg.getSessionIdLSB()).toString(),
+                ToTransportMsg.newBuilder().setToDeviceSessionMsg(msg).build()
+                , new QueueCallbackAdaptor(onSuccess, onFailure));
+    }
+
+    private void forwardToDeviceActor(TransportToDeviceActorMsg toDeviceActorMsg) {
+        TransportToDeviceActorMsgWrapper wrapper = new TransportToDeviceActorMsgWrapper(toDeviceActorMsg);
+        Optional<ServerAddress> address = routingService.resolveById(wrapper.getDeviceId());
+        if (address.isPresent()) {
+            rpcService.tell(encodingService.convertToProtoDataMessage(address.get(), wrapper));
+        } else {
+            actorContext.getAppActor().tell(wrapper, ActorRef.noSender());
+        }
+    }
+
+    @PreDestroy
+    public void destroy() {
+        stopped = true;
+        if (ruleEngineConsumer != null) {
+            ruleEngineConsumer.unsubscribe();
+        }
+        if (mainConsumerExecutor != null) {
+            mainConsumerExecutor.shutdownNow();
+        }
+    }
+
+    private static class QueueCallbackAdaptor implements Callback {
+        private final Runnable onSuccess;
+        private final Consumer<Throwable> onFailure;
+
+        QueueCallbackAdaptor(Runnable onSuccess, Consumer<Throwable> onFailure) {
+            this.onSuccess = onSuccess;
+            this.onFailure = onFailure;
+        }
+
+        @Override
+        public void onCompletion(RecordMetadata metadata, Exception exception) {
+            if (exception == null) {
+                if (onSuccess != null) {
+                    onSuccess.run();
+                }
+            } else {
+                if (onFailure != null) {
+                    onFailure.accept(exception);
+                }
+            }
+        }
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/transport/RemoteTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/RemoteTransportApiService.java
new file mode 100644
index 0000000..6d422f4
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/transport/RemoteTransportApiService.java
@@ -0,0 +1,109 @@
+/**
+ * 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.transport;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
+import org.thingsboard.server.kafka.*;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Created by ashvayka on 05.10.18.
+ */
+@Slf4j
+@Component
+@ConditionalOnProperty(prefix = "transport", value = "type", havingValue = "remote")
+public class RemoteTransportApiService {
+
+    @Value("${transport.remote.transport_api.requests_topic}")
+    private String transportApiRequestsTopic;
+    @Value("${transport.remote.transport_api.responses_topic}")
+    private String transportApiResponsesTopic;
+    @Value("${transport.remote.transport_api.max_pending_requests}")
+    private int maxPendingRequests;
+    @Value("${transport.remote.transport_api.request_timeout}")
+    private long requestTimeout;
+    @Value("${transport.remote.transport_api.request_poll_interval}")
+    private int responsePollDuration;
+    @Value("${transport.remote.transport_api.request_auto_commit_interval}")
+    private int autoCommitInterval;
+
+    @Autowired
+    private TbKafkaSettings kafkaSettings;
+
+    @Autowired
+    private TbNodeIdProvider nodeIdProvider;
+
+    @Autowired
+    private TransportApiService transportApiService;
+
+    private ExecutorService transportCallbackExecutor;
+
+    private TbKafkaResponseTemplate<TransportApiRequestMsg, TransportApiResponseMsg> transportApiTemplate;
+
+    @PostConstruct
+    public void init() {
+        this.transportCallbackExecutor = new ThreadPoolExecutor(0, 100, 60L, TimeUnit.SECONDS, new SynchronousQueue<>());
+
+        TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder<TransportApiResponseMsg> responseBuilder = TBKafkaProducerTemplate.builder();
+        responseBuilder.settings(kafkaSettings);
+        responseBuilder.defaultTopic(transportApiResponsesTopic);
+        responseBuilder.encoder(new TransportApiResponseEncoder());
+
+        TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder<TransportApiRequestMsg> requestBuilder = TBKafkaConsumerTemplate.builder();
+        requestBuilder.settings(kafkaSettings);
+        requestBuilder.topic(transportApiRequestsTopic);
+        requestBuilder.clientId(nodeIdProvider.getNodeId());
+        requestBuilder.groupId("tb-node");
+        requestBuilder.autoCommit(true);
+        requestBuilder.autoCommitIntervalMs(autoCommitInterval);
+        requestBuilder.decoder(new TransportApiRequestDecoder());
+
+        TbKafkaResponseTemplate.TbKafkaResponseTemplateBuilder
+                <TransportApiRequestMsg, TransportApiResponseMsg> builder = TbKafkaResponseTemplate.builder();
+        builder.requestTemplate(requestBuilder.build());
+        builder.responseTemplate(responseBuilder.build());
+        builder.maxPendingRequests(maxPendingRequests);
+        builder.requestTimeout(requestTimeout);
+        builder.pollInterval(responsePollDuration);
+        builder.executor(transportCallbackExecutor);
+        builder.handler(transportApiService);
+        transportApiTemplate = builder.build();
+        transportApiTemplate.init();
+    }
+
+    @PreDestroy
+    public void destroy() {
+        if (transportApiTemplate != null) {
+            transportApiTemplate.stop();
+        }
+        if (transportCallbackExecutor != null) {
+            transportCallbackExecutor.shutdownNow();
+        }
+    }
+
+}
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index 8d75dab..8d65e3d 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -82,47 +82,6 @@ dashboard:
   # Maximum allowed datapoints fetched by widgets
   max_datapoints_limit: "${DASHBOARD_MAX_DATAPOINTS_LIMIT:50000}"
 
-# Device communication protocol parameters
-http:
-  request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}"
-
-# MQTT server parameters
-mqtt:
-  # Enable/disable mqtt transport protocol.
-  enabled: "${MQTT_ENABLED:true}"
-  bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}"
-  bind_port: "${MQTT_BIND_PORT:1883}"
-  adaptor: "${MQTT_ADAPTOR_NAME:JsonMqttAdaptor}"
-  timeout: "${MQTT_TIMEOUT:10000}"
-  netty:
-    leak_detector_level: "${NETTY_LEASK_DETECTOR_LVL:DISABLED}"
-    boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}"
-    worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}"
-    max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}"
-  # MQTT SSL configuration
-  ssl:
-    # Enable/disable SSL support
-    enabled: "${MQTT_SSL_ENABLED:false}"
-    # SSL protocol: See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext
-    protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}"
-    # Path to the key store that holds the SSL certificate
-    key_store: "${MQTT_SSL_KEY_STORE:mqttserver.jks}"
-    # Password used to access the key store
-    key_store_password: "${MQTT_SSL_KEY_STORE_PASSWORD:server_ks_password}"
-    # Password used to access the key
-    key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}"
-    # Type of the key store
-    key_store_type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}"
-
-# CoAP server parameters
-coap:
-  # Enable/disable coap transport protocol.
-  enabled: "${COAP_ENABLED:false}"
-  bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
-  bind_port: "${COAP_BIND_PORT:5683}"
-  adaptor:  "${COAP_ADAPTOR_NAME:JsonCoapAdaptor}"
-  timeout: "${COAP_TIMEOUT:10000}"
-
 #Quota parameters
 quota:
   host:
@@ -154,7 +113,7 @@ quota:
       # Interval for scheduled task that cleans expired records. TTL is used for expiring
       cleanPeriodMs: "${QUOTA_TENANT_CLEAN_PERIOD_MS:300000}"
       # Enable Host API Limits
-      enabled: "${QUOTA_TENANT_ENABLED:false}"
+      enabled: "${QUOTA_TENANT_ENABLED:true}"
       # Array of whitelist tenants
       whitelist: "${QUOTA_TENANT_WHITELIST:}"
       # Array of blacklist tenants
@@ -223,12 +182,6 @@ cassandra:
     permit_max_wait_time: "${PERMIT_MAX_WAIT_TIME:120000}"
     rate_limit_print_interval_ms: "${CASSANDRA_QUERY_RATE_LIMIT_PRINT_MS:30000}"
 
-  queue:
-    msg.ttl: 604800 # 7 days
-    ack.ttl: 604800 # 7 days
-    partitions.ttl: 604800 # 7 days
-    partitioning: "HOURS"
-
 # SQL configuration parameters
 sql:
     # Specify executor service type used to perform timeseries insert tasks: SINGLE FIXED CACHED
@@ -262,13 +215,6 @@ actors:
     node:
       # Errors for particular actor are persisted once per specified amount of milliseconds
       error_persist_frequency: "${ACTORS_RULE_NODE_ERROR_FREQUENCY:3000}"
-    queue:
-      # Message queue type
-      type: "${ACTORS_RULE_QUEUE_TYPE:memory}"
-      # Message queue maximum size (per tenant)
-      max_size: "${ACTORS_RULE_QUEUE_MAX_SIZE:100}"
-      # Message queue cleanup period in seconds
-      cleanup_period: "${ACTORS_RULE_QUEUE_CLEANUP_PERIOD:3600}"
   statistics:
     # Enable/disable actor statistics
     enabled: "${ACTORS_STATISTICS_ENABLED:true}"
@@ -427,6 +373,19 @@ kafka:
   batch.size: "${TB_KAFKA_BATCH_SIZE:16384}"
   linger.ms: "${TB_KAFKA_LINGER_MS:1}"
   buffer.memory: "${TB_BUFFER_MEMORY:33554432}"
+  transport_api:
+    requests_topic: "${TB_TRANSPORT_API_REQUEST_TOPIC:tb.transport.api.requests}"
+    responses_topic: "${TB_TRANSPORT_API_RESPONSE_TOPIC:tb.transport.api.responses}"
+    max_pending_requests: "${TB_TRANSPORT_MAX_PENDING_REQUESTS:10000}"
+    request_timeout: "${TB_TRANSPORT_MAX_REQUEST_TIMEOUT:10000}"
+    request_poll_interval: "${TB_TRANSPORT_REQUEST_POLL_INTERVAL_MS:25}"
+    request_auto_commit_interval: "${TB_TRANSPORT_REQUEST_AUTO_COMMIT_INTERVAL_MS:100}"
+  rule_engine:
+    topic: "${TB_RULE_ENGINE_TOPIC:tb.rule-engine}"
+    poll_interval: "${TB_RULE_ENGINE_POLL_INTERVAL_MS:25}"
+    auto_commit_interval: "${TB_RULE_ENGINE_AUTO_COMMIT_INTERVAL_MS:100}"
+  notifications:
+    topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}"
 
 js:
   evaluator: "${JS_EVALUATOR:local}" # local/remote
@@ -456,3 +415,57 @@ js:
     response_auto_commit_interval: "${REMOTE_JS_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}"
     # Maximum allowed JavaScript execution errors before JavaScript will be blacklisted
     max_errors: "${REMOTE_JS_SANDBOX_MAX_ERRORS:3}"
+
+transport:
+  type: "${TRANSPORT_TYPE:local}" # local or remote
+  remote:
+    transport_api:
+      requests_topic: "${TB_TRANSPORT_API_REQUEST_TOPIC:tb.transport.api.requests}"
+      responses_topic: "${TB_TRANSPORT_API_RESPONSE_TOPIC:tb.transport.api.responses}"
+      max_pending_requests: "${TB_TRANSPORT_MAX_PENDING_REQUESTS:10000}"
+      request_timeout: "${TB_TRANSPORT_MAX_REQUEST_TIMEOUT:10000}"
+      request_poll_interval: "${TB_TRANSPORT_RESPONSE_POLL_INTERVAL_MS:25}"
+      request_auto_commit_interval: "${TB_TRANSPORT_RESPONSE_AUTO_COMMIT_INTERVAL_MS:1000}"
+    rule_engine:
+      topic: "${TB_RULE_ENGINE_TOPIC:tb.rule-engine}"
+      poll_interval: "${TB_RULE_ENGINE_POLL_INTERVAL_MS:25}"
+      auto_commit_interval: "${TB_RULE_ENGINE_AUTO_COMMIT_INTERVAL_MS:100}"
+    notifications:
+      topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}"
+  # Local HTTP transport parameters
+  http:
+    enabled: "${HTTP_ENABLED:true}"
+    request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}"
+  # Local MQTT transport parameters
+  mqtt:
+    # Enable/disable mqtt transport protocol.
+    enabled: "${MQTT_ENABLED:true}"
+    bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}"
+    bind_port: "${MQTT_BIND_PORT:1883}"
+    timeout: "${MQTT_TIMEOUT:10000}"
+    netty:
+      leak_detector_level: "${NETTY_LEAK_DETECTOR_LVL:DISABLED}"
+      boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}"
+      worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}"
+      max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}"
+    # MQTT SSL configuration
+    ssl:
+      # Enable/disable SSL support
+      enabled: "${MQTT_SSL_ENABLED:false}"
+      # SSL protocol: See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext
+      protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}"
+      # Path to the key store that holds the SSL certificate
+      key_store: "${MQTT_SSL_KEY_STORE:mqttserver.jks}"
+      # Password used to access the key store
+      key_store_password: "${MQTT_SSL_KEY_STORE_PASSWORD:server_ks_password}"
+      # Password used to access the key
+      key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}"
+      # Type of the key store
+      key_store_type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}"
+  # Local CoAP transport parameters
+  coap:
+    # Enable/disable coap transport protocol.
+    enabled: "${COAP_ENABLED:true}"
+    bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
+    bind_port: "${COAP_BIND_PORT:5683}"
+    timeout: "${COAP_TIMEOUT:10000}"
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 1b042a8..cb31a4b 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java
@@ -27,9 +27,7 @@ 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 org.thingsboard.server.service.queue.MsgQueueService;
 
 import java.io.IOException;
 import java.util.function.Predicate;
@@ -42,9 +40,6 @@ public class AbstractRuleEngineControllerTest extends AbstractControllerTest {
     @Autowired
     protected RuleChainService ruleChainService;
 
-    @Autowired
-    protected MsgQueueService msgQueueService;
-
     protected RuleChain saveRuleChain(RuleChain ruleChain) throws Exception {
         return doPost("/api/ruleChain", ruleChain, RuleChain.class);
     }
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 c86d496..f050f3b 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
@@ -191,9 +191,6 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
 
         Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText());
         Assert.assertEquals("serverAttributeValue2", getMetadata(outEvent).get("ss_serverAttributeKey2").asText());
-
-        List<TbMsg> unAckMsgList = Lists.newArrayList(msgQueueService.findUnprocessed(savedTenant.getId(), ruleChain.getId().getId(), 0L));
-        Assert.assertEquals(0, unAckMsgList.size());
     }
 
     @Test
@@ -311,12 +308,6 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
 
         Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText());
         Assert.assertEquals("serverAttributeValue2", getMetadata(outEvent).get("ss_serverAttributeKey2").asText());
-
-        List<TbMsg> unAckMsgList = Lists.newArrayList(msgQueueService.findUnprocessed(savedTenant.getId(), rootRuleChain.getId().getId(), 0L));
-        Assert.assertEquals(0, unAckMsgList.size());
-
-        unAckMsgList = Lists.newArrayList(msgQueueService.findUnprocessed(savedTenant.getId(), 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 7ac0789..24db457 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
@@ -162,73 +162,4 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
         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);
-        msgQueueService.put(device.getTenantId(), 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> eventsPage = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000);
-        List<Event> events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList());
-
-        Assert.assertEquals(2, events.size());
-
-        Event inEvent = events.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.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/system/BaseHttpDeviceApiTest.java b/application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java
index 5e6896b..1ebccad 100644
--- a/application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java
+++ b/application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java
@@ -57,7 +57,7 @@ public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest {
     @Test
     public void testGetAttributes() throws Exception {
         doGetAsync("/api/v1/" + "WRONG_TOKEN" + "/attributes?clientKeys=keyA,keyB,keyC").andExpect(status().isUnauthorized());
-        doGetAsync("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC").andExpect(status().isNotFound());
+        doGetAsync("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC").andExpect(status().isOk());
 
         Map<String, String> attrMap = new HashMap<>();
         attrMap.put("keyA", "valueA");
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 34c14de..307c59b 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
@@ -57,6 +57,8 @@ public class DataConstants {
     public static final String ENTITY_UNASSIGNED = "ENTITY_UNASSIGNED";
     public static final String ATTRIBUTES_UPDATED = "ATTRIBUTES_UPDATED";
     public static final String ATTRIBUTES_DELETED = "ATTRIBUTES_DELETED";
+    public static final String ALARM_ACK = "ALARM_ACK";
+    public static final String ALARM_CLEAR = "ALARM_CLEAR";
 
     public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE";
 
diff --git a/common/message/pom.xml b/common/message/pom.xml
index f4f20b0..d914d4f 100644
--- a/common/message/pom.xml
+++ b/common/message/pom.xml
@@ -41,6 +41,10 @@
             <artifactId>data</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15on</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
         </dependency>
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 82f44e9..fa9ef05 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
@@ -16,25 +16,16 @@
 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.SessionMsgType;
-import org.thingsboard.server.common.msg.session.SessionMsgType;
-import org.thingsboard.server.common.msg.session.ToDeviceMsg;
 
 /**
  * @author Andrew Shvayka
  */
 @Data
-public class ToServerRpcResponseMsg implements ToDeviceMsg {
+public class ToServerRpcResponseMsg {
 
     private final int requestId;
     private final String data;
 
-    public SessionMsgType getSessionMsgType() {
-        return SessionMsgType.TO_SERVER_RPC_RESPONSE;
-    }
-
-    @Override
     public boolean isSuccess() {
         return true;
     }
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 60e5469..24758b5 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
@@ -77,11 +77,6 @@ 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,
@@ -96,8 +91,6 @@ public enum MsgType {
 
     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
      */
@@ -106,11 +99,16 @@ public enum MsgType {
     /**
      * Message that is sent from Rule Engine to the Device Actor when message is successfully pushed to queue.
      */
-    RULE_ENGINE_QUEUE_PUT_ACK_MSG,
     ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG,
     TRANSPORT_TO_DEVICE_SESSION_ACTOR_MSG,
     SESSION_TIMEOUT_MSG,
     SESSION_CTRL_MSG,
-    STATS_PERSIST_TICK_MSG;
+    STATS_PERSIST_TICK_MSG,
+
+
+    /**
+     * Message that is sent by TransportRuleEngineService to Device Actor. Represents messages from the device itself.
+     */
+    TRANSPORT_TO_DEVICE_ACTOR_MSG;
 
 }
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java
index 2f2d880..7f7a669 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java
@@ -15,21 +15,11 @@
  */
 package org.thingsboard.server.common.msg.session;
 
-import org.thingsboard.server.common.data.id.SessionId;
-import org.thingsboard.server.common.msg.session.ex.SessionException;
+import java.util.UUID;
 
 public interface SessionContext {
 
-    SessionId getSessionId();
-
-    SessionType getSessionType();
-
-    void onMsg(SessionActorToAdaptorMsg msg) throws SessionException;
-
-    void onMsg(SessionCtrlMsg msg) throws SessionException;
-
-    boolean isClosed();
-
-    long getTimeout();
+    UUID getSessionId();
 
+    int nextMsgId();
 }

common/pom.xml 3(+1 -2)

diff --git a/common/pom.xml b/common/pom.xml
index e3cdabf..e55d71f 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -23,7 +23,6 @@
         <version>2.2.0-SNAPSHOT</version>
         <artifactId>thingsboard</artifactId>
     </parent>
-    <groupId>org.thingsboard</groupId>
     <artifactId>common</artifactId>
     <packaging>pom</packaging>
 
@@ -37,8 +36,8 @@
     <modules>
         <module>data</module>
         <module>message</module>
-        <module>transport</module>
         <module>queue</module>
+        <module>transport</module>
     </modules>
 
 </project>
diff --git a/common/queue/pom.xml b/common/queue/pom.xml
index 11bb139..765960e 100644
--- a/common/queue/pom.xml
+++ b/common/queue/pom.xml
@@ -16,7 +16,7 @@
 
 -->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.thingsboard</groupId>
@@ -65,6 +65,10 @@
             <artifactId>gson</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
         </dependency>
diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/AsyncCallbackTemplate.java b/common/queue/src/main/java/org/thingsboard/server/kafka/AsyncCallbackTemplate.java
new file mode 100644
index 0000000..b8ad758
--- /dev/null
+++ b/common/queue/src/main/java/org/thingsboard/server/kafka/AsyncCallbackTemplate.java
@@ -0,0 +1,66 @@
+/**
+ * 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.kafka;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Created by ashvayka on 05.10.18.
+ */
+public class AsyncCallbackTemplate {
+
+    public static <T> void withCallbackAndTimeout(ListenableFuture<T> future,
+                                                  Consumer<T> onSuccess,
+                                                  Consumer<Throwable> onFailure,
+                                                  long timeoutInMs,
+                                                  ScheduledExecutorService timeoutExecutor,
+                                                  Executor callbackExecutor) {
+        future = Futures.withTimeout(future, timeoutInMs, TimeUnit.MILLISECONDS, timeoutExecutor);
+        withCallback(future, onSuccess, onFailure, callbackExecutor);
+    }
+
+    public static <T> void withCallback(ListenableFuture<T> future, Consumer<T> onSuccess,
+                                        Consumer<Throwable> onFailure, Executor executor) {
+        FutureCallback<T> callback = new FutureCallback<T>() {
+            @Override
+            public void onSuccess(T result) {
+                try {
+                    onSuccess.accept(result);
+                } catch (Throwable th) {
+                    onFailure(th);
+                }
+            }
+
+            @Override
+            public void onFailure(Throwable t) {
+                onFailure.accept(t);
+            }
+        };
+        if (executor != null) {
+            Futures.addCallback(future, callback, executor);
+        } else {
+            Futures.addCallback(future, callback);
+        }
+    }
+
+}
diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaConsumerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaConsumerTemplate.java
index 3972264..86be3a3 100644
--- a/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaConsumerTemplate.java
+++ b/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaConsumerTemplate.java
@@ -43,12 +43,15 @@ public class TBKafkaConsumerTemplate<T> {
     private final String topic;
 
     @Builder
-    private TBKafkaConsumerTemplate(TbKafkaSettings settings, TbKafkaDecoder<T> decoder, TbKafkaRequestIdExtractor<T> requestIdExtractor,
+    private TBKafkaConsumerTemplate(TbKafkaSettings settings, TbKafkaDecoder<T> decoder,
+                                    TbKafkaRequestIdExtractor<T> requestIdExtractor,
                                     String clientId, String groupId, String topic,
                                     boolean autoCommit, int autoCommitIntervalMs) {
         Properties props = settings.toProps();
         props.put(ConsumerConfig.CLIENT_ID_CONFIG, clientId);
-        props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
+        if (groupId != null) {
+            props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
+        }
         props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, autoCommit);
         props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, autoCommitIntervalMs);
         props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaProducerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaProducerTemplate.java
index 7e24ad0..610a490 100644
--- a/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaProducerTemplate.java
+++ b/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaProducerTemplate.java
@@ -20,6 +20,7 @@ import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.kafka.clients.admin.CreateTopicsResult;
 import org.apache.kafka.clients.admin.NewTopic;
+import org.apache.kafka.clients.producer.Callback;
 import org.apache.kafka.clients.producer.KafkaProducer;
 import org.apache.kafka.clients.producer.ProducerConfig;
 import org.apache.kafka.clients.producer.ProducerRecord;
@@ -27,13 +28,12 @@ import org.apache.kafka.clients.producer.RecordMetadata;
 import org.apache.kafka.common.PartitionInfo;
 import org.apache.kafka.common.header.Header;
 
-import java.nio.ByteBuffer;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Properties;
 import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.Future;
-import java.util.function.BiConsumer;
 
 /**
  * Created by ashvayka on 24.09.18.
@@ -48,7 +48,7 @@ public class TBKafkaProducerTemplate<T> {
     private TbKafkaEnricher<T> enricher = ((value, responseTopic, requestId) -> value);
 
     private final TbKafkaPartitioner<T> partitioner;
-    private List<PartitionInfo> partitionInfoList;
+    private ConcurrentMap<String, List<PartitionInfo>> partitionInfoMap;
     @Getter
     private final String defaultTopic;
 
@@ -78,38 +78,51 @@ public class TBKafkaProducerTemplate<T> {
             log.trace("Failed to create topic: {}", e.getMessage(), e);
         }
         //Maybe this should not be cached, but we don't plan to change size of partitions
-        this.partitionInfoList = producer.partitionsFor(defaultTopic);
+        this.partitionInfoMap = new ConcurrentHashMap<>();
+        this.partitionInfoMap.putIfAbsent(defaultTopic, producer.partitionsFor(defaultTopic));
     }
 
-    public T enrich(T value, String responseTopic, UUID requestId) {
-        return enricher.enrich(value, responseTopic, requestId);
+    T enrich(T value, String responseTopic, UUID requestId) {
+        if (enricher != null) {
+            return enricher.enrich(value, responseTopic, requestId);
+        } else {
+            return value;
+        }
+    }
+
+    public Future<RecordMetadata> send(String key, T value, Callback callback) {
+        return send(key, value, null, callback);
+    }
+
+    public Future<RecordMetadata> send(String key, T value, Iterable<Header> headers, Callback callback) {
+        return send(key, value, null, headers, callback);
     }
 
-    public Future<RecordMetadata> send(String key, T value) {
-        return send(key, value, null, null);
+    public Future<RecordMetadata> send(String key, T value, Long timestamp, Iterable<Header> headers, Callback callback) {
+        return send(this.defaultTopic, key, value, timestamp, headers, callback);
     }
 
-    public Future<RecordMetadata> send(String key, T value, Iterable<Header> headers) {
-        return send(key, value, null, headers);
+    public Future<RecordMetadata> send(String topic, String key, T value, Iterable<Header> headers, Callback callback) {
+        return send(topic, key, value, null, headers, callback);
     }
 
-    public Future<RecordMetadata> send(String key, T value, Long timestamp, Iterable<Header> headers) {
-        return send(this.defaultTopic, key, value, timestamp, headers);
+    public Future<RecordMetadata> send(String topic, String key, T value, Callback callback) {
+        return send(topic, key, value, null, null, callback);
     }
 
-    public Future<RecordMetadata> send(String topic, String key, T value, Long timestamp, Iterable<Header> headers) {
+    public Future<RecordMetadata> send(String topic, String key, T value, Long timestamp, Iterable<Header> headers, Callback callback) {
         byte[] data = encoder.encode(value);
         ProducerRecord<String, byte[]> record;
         Integer partition = getPartition(topic, key, value, data);
-        record = new ProducerRecord<>(this.defaultTopic, partition, timestamp, key, data, headers);
-        return producer.send(record);
+        record = new ProducerRecord<>(topic, partition, timestamp, key, data, headers);
+        return producer.send(record, callback);
     }
 
     private Integer getPartition(String topic, String key, T value, byte[] data) {
         if (partitioner == null) {
             return null;
         } else {
-            return partitioner.partition(this.defaultTopic, key, value, data, partitionInfoList);
+            return partitioner.partition(topic, key, value, data, partitionInfoMap.computeIfAbsent(topic, producer::partitionsFor));
         }
     }
 }
diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java
index 8a0f529..77ad033 100644
--- a/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java
+++ b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java
@@ -23,24 +23,25 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.kafka.clients.admin.CreateTopicsResult;
 import org.apache.kafka.clients.admin.NewTopic;
 import org.apache.kafka.clients.consumer.ConsumerRecords;
-import org.apache.kafka.clients.producer.RecordMetadata;
 import org.apache.kafka.common.header.Header;
 import org.apache.kafka.common.header.internals.RecordHeader;
 
 import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
-import java.util.concurrent.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Created by ashvayka on 25.09.18.
  */
 @Slf4j
-public class TbKafkaRequestTemplate<Request, Response> {
+public class TbKafkaRequestTemplate<Request, Response> extends AbstractTbKafkaTemplate {
 
     private final TBKafkaProducerTemplate<Request> requestTemplate;
     private final TBKafkaConsumerTemplate<Response> responseTemplate;
@@ -93,13 +94,12 @@ public class TbKafkaRequestTemplate<Request, Response> {
                 ConsumerRecords<String, byte[]> responses = responseTemplate.poll(Duration.ofMillis(pollInterval));
                 responses.forEach(response -> {
                     Header requestIdHeader = response.headers().lastHeader(TbKafkaSettings.REQUEST_ID_HEADER);
-                    Response decocedResponse = null;
+                    Response decodedResponse = null;
                     UUID requestId = null;
                     if (requestIdHeader == null) {
                         try {
-                            decocedResponse = responseTemplate.decode(response);
-                            requestId = responseTemplate.extractRequestId(decocedResponse);
-
+                            decodedResponse = responseTemplate.decode(response);
+                            requestId = responseTemplate.extractRequestId(decodedResponse);
                         } catch (IOException e) {
                             log.error("Failed to decode response", e);
                         }
@@ -107,17 +107,17 @@ public class TbKafkaRequestTemplate<Request, Response> {
                         requestId = bytesToUuid(requestIdHeader.value());
                     }
                     if (requestId == null) {
-                        log.error("[{}] Missing requestId in header and response", response);
+                        log.error("[{}] Missing requestId in header and body", response);
                     } else {
                         ResponseMetaData<Response> expectedResponse = pendingRequests.remove(requestId);
                         if (expectedResponse == null) {
                             log.trace("[{}] Invalid or stale request", requestId);
                         } else {
                             try {
-                                if (decocedResponse == null) {
-                                    decocedResponse = responseTemplate.decode(response);
+                                if (decodedResponse == null) {
+                                    decodedResponse = responseTemplate.decode(response);
                                 }
-                                expectedResponse.future.set(decocedResponse);
+                                expectedResponse.future.set(decodedResponse);
                             } catch (IOException e) {
                                 expectedResponse.future.setException(e);
                             }
@@ -160,28 +160,10 @@ public class TbKafkaRequestTemplate<Request, Response> {
         SettableFuture<Response> future = SettableFuture.create();
         pendingRequests.putIfAbsent(requestId, new ResponseMetaData<>(tickTs + maxRequestTimeout, future));
         request = requestTemplate.enrich(request, responseTemplate.getTopic(), requestId);
-        requestTemplate.send(key, request, headers);
+        requestTemplate.send(key, request, headers, null);
         return future;
     }
 
-    private byte[] uuidToBytes(UUID uuid) {
-        ByteBuffer buf = ByteBuffer.allocate(16);
-        buf.putLong(uuid.getMostSignificantBits());
-        buf.putLong(uuid.getLeastSignificantBits());
-        return buf.array();
-    }
-
-    private static UUID bytesToUuid(byte[] bytes) {
-        ByteBuffer bb = ByteBuffer.wrap(bytes);
-        long firstLong = bb.getLong();
-        long secondLong = bb.getLong();
-        return new UUID(firstLong, secondLong);
-    }
-
-    private byte[] stringToBytes(String string) {
-        return string.getBytes(StandardCharsets.UTF_8);
-    }
-
     private static class ResponseMetaData<T> {
         private final long expTime;
         private final SettableFuture<T> future;
diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaResponseTemplate.java b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaResponseTemplate.java
new file mode 100644
index 0000000..4c23ac2
--- /dev/null
+++ b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaResponseTemplate.java
@@ -0,0 +1,156 @@
+/**
+ * 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.kafka;
+
+import lombok.Builder;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.kafka.clients.consumer.ConsumerRecords;
+import org.apache.kafka.common.header.Header;
+import org.apache.kafka.common.header.internals.RecordHeader;
+
+import java.time.Duration;
+import java.util.Collections;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Created by ashvayka on 25.09.18.
+ */
+@Slf4j
+public class TbKafkaResponseTemplate<Request, Response> extends AbstractTbKafkaTemplate {
+
+    private final TBKafkaConsumerTemplate<Request> requestTemplate;
+    private final TBKafkaProducerTemplate<Response> responseTemplate;
+    private final TbKafkaHandler<Request, Response> handler;
+    private final ConcurrentMap<UUID, String> pendingRequests;
+    private final ExecutorService loopExecutor;
+    private final ScheduledExecutorService timeoutExecutor;
+    private final ExecutorService callbackExecutor;
+    private final int maxPendingRequests;
+    private final long requestTimeout;
+
+    private final long pollInterval;
+    private volatile boolean stopped = false;
+    private final AtomicInteger pendingRequestCount = new AtomicInteger();
+
+    @Builder
+    public TbKafkaResponseTemplate(TBKafkaConsumerTemplate<Request> requestTemplate,
+                                   TBKafkaProducerTemplate<Response> responseTemplate,
+                                   TbKafkaHandler<Request, Response> handler,
+                                   long pollInterval,
+                                   long requestTimeout,
+                                   int maxPendingRequests,
+                                   ExecutorService executor) {
+        this.requestTemplate = requestTemplate;
+        this.responseTemplate = responseTemplate;
+        this.handler = handler;
+        this.pendingRequests = new ConcurrentHashMap<>();
+        this.maxPendingRequests = maxPendingRequests;
+        this.pollInterval = pollInterval;
+        this.requestTimeout = requestTimeout;
+        this.callbackExecutor = executor;
+        this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor();
+        this.loopExecutor = Executors.newSingleThreadExecutor();
+    }
+
+    public void init() {
+        this.responseTemplate.init();
+        requestTemplate.subscribe();
+        loopExecutor.submit(() -> {
+            while (!stopped) {
+                try {
+                    while (pendingRequestCount.get() >= maxPendingRequests) {
+                        try {
+                            Thread.sleep(pollInterval);
+                        } catch (InterruptedException e) {
+                            log.trace("Failed to wait until the server has capacity to handle new requests", e);
+                        }
+                    }
+                    ConsumerRecords<String, byte[]> requests = requestTemplate.poll(Duration.ofMillis(pollInterval));
+                    requests.forEach(request -> {
+                        Header requestIdHeader = request.headers().lastHeader(TbKafkaSettings.REQUEST_ID_HEADER);
+                        if (requestIdHeader == null) {
+                            log.error("[{}] Missing requestId in header", request);
+                            return;
+                        }
+                        UUID requestId = bytesToUuid(requestIdHeader.value());
+                        if (requestId == null) {
+                            log.error("[{}] Missing requestId in header and body", request);
+                            return;
+                        }
+                        Header responseTopicHeader = request.headers().lastHeader(TbKafkaSettings.RESPONSE_TOPIC_HEADER);
+                        if (responseTopicHeader == null) {
+                            log.error("[{}] Missing response topic in header", request);
+                            return;
+                        }
+                        String responseTopic = bytesToString(responseTopicHeader.value());
+                        try {
+                            pendingRequestCount.getAndIncrement();
+                            Request decodedRequest = requestTemplate.decode(request);
+                            AsyncCallbackTemplate.withCallbackAndTimeout(handler.handle(decodedRequest),
+                                    response -> {
+                                        pendingRequestCount.decrementAndGet();
+                                        reply(requestId, responseTopic, response);
+                                    },
+                                    e -> {
+                                        pendingRequestCount.decrementAndGet();
+                                        if (e.getCause() != null && e.getCause() instanceof TimeoutException) {
+                                            log.warn("[{}] Timedout to process the request: {}", requestId, request, e);
+                                        } else {
+                                            log.trace("[{}] Failed to process the request: {}", requestId, request, e);
+                                        }
+                                    },
+                                    requestTimeout,
+                                    timeoutExecutor,
+                                    callbackExecutor);
+                        } catch (Throwable e) {
+                            pendingRequestCount.decrementAndGet();
+                            log.warn("[{}] Failed to process the request: {}", requestId, request, e);
+                        }
+                    });
+                } catch (Throwable e) {
+                    log.warn("Failed to obtain messages from queue.", e);
+                    try {
+                        Thread.sleep(pollInterval);
+                    } catch (InterruptedException e2) {
+                        log.trace("Failed to wait until the server has capacity to handle new requests", e2);
+                    }
+                }
+            }
+        });
+    }
+
+    public void stop() {
+        stopped = true;
+        if (timeoutExecutor != null) {
+            timeoutExecutor.shutdownNow();
+        }
+        if (loopExecutor != null) {
+            loopExecutor.shutdownNow();
+        }
+    }
+
+    private void reply(UUID requestId, String topic, Response response) {
+        responseTemplate.send(topic, requestId.toString(), response, Collections.singletonList(new RecordHeader(TbKafkaSettings.REQUEST_ID_HEADER, uuidToBytes(requestId))), null);
+    }
+
+}
diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml
new file mode 100644
index 0000000..d68a6db
--- /dev/null
+++ b/common/transport/coap/pom.xml
@@ -0,0 +1,88 @@
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.thingsboard.common</groupId>
+        <version>2.2.0-SNAPSHOT</version>
+        <artifactId>transport</artifactId>
+    </parent>
+    <groupId>org.thingsboard.common.transport</groupId>
+    <artifactId>coap</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Thingsboard CoAP Transport Common</name>
+    <url>https://thingsboard.io</url>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <main.dir>${basedir}/../../..</main.dir>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.thingsboard.common.transport</groupId>
+            <artifactId>transport-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.californium</groupId>
+            <artifactId>californium-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context-support</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>log4j-over-slf4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java
new file mode 100644
index 0000000..f5bfb49
--- /dev/null
+++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java
@@ -0,0 +1,155 @@
+/**
+ * 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.transport.coap.adaptors;
+
+import java.util.*;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.californium.core.coap.CoAP;
+import org.eclipse.californium.core.coap.Request;
+import org.eclipse.californium.core.coap.Response;
+import org.springframework.util.StringUtils;
+import org.thingsboard.server.common.msg.kv.AttributesKVMsg;
+import org.thingsboard.server.common.msg.session.SessionContext;
+import org.thingsboard.server.common.transport.adaptor.AdaptorException;
+import org.thingsboard.server.common.transport.adaptor.JsonConverter;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+import org.thingsboard.server.gen.transport.TransportProtos;
+import org.thingsboard.server.transport.coap.CoapTransportResource;
+
+@Component("JsonCoapAdaptor")
+@Slf4j
+public class JsonCoapAdaptor implements CoapTransportAdaptor {
+
+    @Override
+    public TransportProtos.PostTelemetryMsg convertToPostTelemetry(UUID sessionId, Request inbound) throws AdaptorException {
+        String payload = validatePayload(sessionId, inbound);
+        try {
+            return JsonConverter.convertToTelemetryProto(new JsonParser().parse(payload));
+        } catch (IllegalStateException | JsonSyntaxException ex) {
+            throw new AdaptorException(ex);
+        }
+    }
+
+    @Override
+    public TransportProtos.PostAttributeMsg convertToPostAttributes(UUID sessionId, Request inbound) throws AdaptorException {
+        String payload = validatePayload(sessionId, inbound);
+        try {
+            return JsonConverter.convertToAttributesProto(new JsonParser().parse(payload));
+        } catch (IllegalStateException | JsonSyntaxException ex) {
+            throw new AdaptorException(ex);
+        }
+    }
+
+    @Override
+    public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(UUID sessionId, Request inbound) throws AdaptorException {
+        List<String> queryElements = inbound.getOptions().getUriQuery();
+        TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder();
+        if (queryElements != null && queryElements.size() > 0) {
+            Set<String> clientKeys = toKeys(queryElements, "clientKeys");
+            Set<String> sharedKeys = toKeys(queryElements, "sharedKeys");
+            if (clientKeys != null) {
+                result.addAllClientAttributeNames(clientKeys);
+            }
+            if (sharedKeys != null) {
+                result.addAllSharedAttributeNames(sharedKeys);
+            }
+        }
+        return result.build();
+    }
+
+    @Override
+    public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound) throws AdaptorException {
+        Optional<Integer> requestId = CoapTransportResource.getRequestId(inbound);
+        String payload = validatePayload(sessionId, inbound);
+        JsonObject response = new JsonParser().parse(payload).getAsJsonObject();
+        return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId.orElseThrow(() -> new AdaptorException("Request id is missing!")))
+                .setPayload(response.toString()).build();
+    }
+
+    @Override
+    public TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(UUID sessionId, Request inbound) throws AdaptorException {
+        String payload = validatePayload(sessionId, inbound);
+        return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), 0);
+    }
+
+    @Override
+    public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.AttributeUpdateNotificationMsg msg) throws AdaptorException {
+        return getObserveNotification(session.getNextSeqNumber(), JsonConverter.toJson(msg));
+    }
+
+    @Override
+    public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.ToDeviceRpcRequestMsg msg) throws AdaptorException {
+        return getObserveNotification(session.getNextSeqNumber(), JsonConverter.toJson(msg, true));
+    }
+
+    @Override
+    public Response convertToPublish(CoapTransportResource.CoapSessionListener coapSessionListener, TransportProtos.ToServerRpcResponseMsg msg) throws AdaptorException {
+        Response response = new Response(CoAP.ResponseCode.CONTENT);
+        JsonElement result = JsonConverter.toJson(msg);
+        response.setPayload(result.toString());
+        return response;
+    }
+
+    @Override
+    public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.GetAttributeResponseMsg msg) throws AdaptorException {
+        if (msg.getClientAttributeListCount() == 0 && msg.getSharedAttributeListCount() == 0 && msg.getDeletedAttributeKeysCount() == 0) {
+            return new Response(CoAP.ResponseCode.NOT_FOUND);
+        } else {
+            Response response = new Response(CoAP.ResponseCode.CONTENT);
+            JsonObject result = JsonConverter.toJson(msg);
+            response.setPayload(result.toString());
+            return response;
+        }
+    }
+
+    private Response getObserveNotification(int seqNumber, JsonElement json) {
+        Response response = new Response(CoAP.ResponseCode.CONTENT);
+        response.getOptions().setObserve(seqNumber);
+        response.setPayload(json.toString());
+        return response;
+    }
+
+    private String validatePayload(UUID sessionId, Request inbound) throws AdaptorException {
+        String payload = inbound.getPayloadString();
+        if (payload == null) {
+            log.warn("[{}] Payload is empty!", sessionId);
+            throw new AdaptorException(new IllegalArgumentException("Payload is empty!"));
+        }
+        return payload;
+    }
+
+    private Set<String> toKeys(List<String> queryElements, String attributeName) throws AdaptorException {
+        String keys = null;
+        for (String queryElement : queryElements) {
+            String[] queryItem = queryElement.split("=");
+            if (queryItem.length == 2 && queryItem[0].equals(attributeName)) {
+                keys = queryItem[1];
+            }
+        }
+        if (keys != null && !StringUtils.isEmpty(keys)) {
+            return new HashSet<>(Arrays.asList(keys.split(",")));
+        } else {
+            return null;
+        }
+    }
+
+}
diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml
new file mode 100644
index 0000000..5d735c9
--- /dev/null
+++ b/common/transport/http/pom.xml
@@ -0,0 +1,81 @@
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.thingsboard.common</groupId>
+        <version>2.2.0-SNAPSHOT</version>
+        <artifactId>transport</artifactId>
+    </parent>
+    <groupId>org.thingsboard.common.transport</groupId>
+    <artifactId>http</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Thingsboard HTTP Transport Common</name>
+    <url>https://thingsboard.io</url>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <main.dir>${basedir}/../../..</main.dir>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.thingsboard.common.transport</groupId>
+            <artifactId>transport-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>log4j-over-slf4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java
new file mode 100644
index 0000000..b40fbc1
--- /dev/null
+++ b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java
@@ -0,0 +1,326 @@
+/**
+ * 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.transport.http;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.context.request.async.DeferredResult;
+import org.thingsboard.server.common.transport.SessionMsgListener;
+import org.thingsboard.server.common.transport.TransportContext;
+import org.thingsboard.server.common.transport.TransportService;
+import org.thingsboard.server.common.transport.TransportServiceCallback;
+import org.thingsboard.server.common.transport.adaptor.JsonConverter;
+import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
+import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto;
+import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
+import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+import java.util.function.Consumer;
+
+/**
+ * @author Andrew Shvayka
+ */
+@RestController
+@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.http.enabled}'=='true')")
+@RequestMapping("/api/v1")
+@Slf4j
+public class DeviceApiController {
+
+    @Autowired
+    private HttpTransportContext transportContext;
+
+    @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.GET, produces = "application/json")
+    public DeferredResult<ResponseEntity> getDeviceAttributes(@PathVariable("deviceToken") String deviceToken,
+                                                              @RequestParam(value = "clientKeys", required = false, defaultValue = "") String clientKeys,
+                                                              @RequestParam(value = "sharedKeys", required = false, defaultValue = "") String sharedKeys,
+                                                              HttpServletRequest httpRequest) {
+        DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
+        if (quotaExceeded(httpRequest, responseWriter)) {
+            return responseWriter;
+        }
+        transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
+                new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
+                    GetAttributeRequestMsg.Builder request = GetAttributeRequestMsg.newBuilder().setRequestId(0);
+                    List<String> clientKeySet = !StringUtils.isEmpty(clientKeys) ? Arrays.asList(clientKeys.split(",")) : null;
+                    List<String> sharedKeySet = !StringUtils.isEmpty(sharedKeys) ? Arrays.asList(sharedKeys.split(",")) : null;
+                    if (clientKeySet != null) {
+                        request.addAllClientAttributeNames(clientKeySet);
+                    }
+                    if (sharedKeySet != null) {
+                        request.addAllSharedAttributeNames(sharedKeySet);
+                    }
+                    TransportService transportService = transportContext.getTransportService();
+                    transportService.registerSyncSession(sessionInfo, new HttpSessionListener(responseWriter), transportContext.getDefaultTimeout());
+                    transportService.process(sessionInfo, request.build(), new SessionCloseOnErrorCallback(transportService, sessionInfo));
+                }));
+        return responseWriter;
+    }
+
+    @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.POST)
+    public DeferredResult<ResponseEntity> postDeviceAttributes(@PathVariable("deviceToken") String deviceToken,
+                                                               @RequestBody String json, HttpServletRequest request) {
+        DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
+        if (quotaExceeded(request, responseWriter)) {
+            return responseWriter;
+        }
+        transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
+                new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
+                    TransportService transportService = transportContext.getTransportService();
+                    transportService.process(sessionInfo, JsonConverter.convertToAttributesProto(new JsonParser().parse(json)),
+                            new HttpOkCallback(responseWriter));
+                }));
+        return responseWriter;
+    }
+
+    @RequestMapping(value = "/{deviceToken}/telemetry", method = RequestMethod.POST)
+    public DeferredResult<ResponseEntity> postTelemetry(@PathVariable("deviceToken") String deviceToken,
+                                                        @RequestBody String json, HttpServletRequest request) {
+        DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
+        if (quotaExceeded(request, responseWriter)) {
+            return responseWriter;
+        }
+        transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
+                new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
+                    TransportService transportService = transportContext.getTransportService();
+                    transportService.process(sessionInfo, JsonConverter.convertToTelemetryProto(new JsonParser().parse(json)),
+                            new HttpOkCallback(responseWriter));
+                }));
+        return responseWriter;
+    }
+
+    @RequestMapping(value = "/{deviceToken}/rpc", method = RequestMethod.GET, produces = "application/json")
+    public DeferredResult<ResponseEntity> subscribeToCommands(@PathVariable("deviceToken") String deviceToken,
+                                                              @RequestParam(value = "timeout", required = false, defaultValue = "0") long timeout,
+                                                              HttpServletRequest httpRequest) {
+        DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
+        if (quotaExceeded(httpRequest, responseWriter)) {
+            return responseWriter;
+        }
+        transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
+                new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
+                    TransportService transportService = transportContext.getTransportService();
+                    transportService.registerSyncSession(sessionInfo, new HttpSessionListener(responseWriter),
+                            timeout == 0 ? transportContext.getDefaultTimeout() : timeout);
+                    transportService.process(sessionInfo, SubscribeToRPCMsg.getDefaultInstance(),
+                            new SessionCloseOnErrorCallback(transportService, sessionInfo));
+
+                }));
+        return responseWriter;
+    }
+
+    @RequestMapping(value = "/{deviceToken}/rpc/{requestId}", method = RequestMethod.POST)
+    public DeferredResult<ResponseEntity> replyToCommand(@PathVariable("deviceToken") String deviceToken,
+                                                         @PathVariable("requestId") Integer requestId,
+                                                         @RequestBody String json, HttpServletRequest request) {
+        DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
+        if (quotaExceeded(request, responseWriter)) {
+            return responseWriter;
+        }
+        transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
+                new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
+                    TransportService transportService = transportContext.getTransportService();
+                    transportService.process(sessionInfo, ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(json).build(), new HttpOkCallback(responseWriter));
+                }));
+        return responseWriter;
+    }
+
+    @RequestMapping(value = "/{deviceToken}/rpc", method = RequestMethod.POST)
+    public DeferredResult<ResponseEntity> postRpcRequest(@PathVariable("deviceToken") String deviceToken,
+                                                         @RequestBody String json, HttpServletRequest httpRequest) {
+        DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
+        if (quotaExceeded(httpRequest, responseWriter)) {
+            return responseWriter;
+        }
+        transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
+                new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
+                    JsonObject request = new JsonParser().parse(json).getAsJsonObject();
+                    TransportService transportService = transportContext.getTransportService();
+                    transportService.registerSyncSession(sessionInfo, new HttpSessionListener(responseWriter), transportContext.getDefaultTimeout());
+                    transportService.process(sessionInfo, ToServerRpcRequestMsg.newBuilder().setRequestId(0)
+                                    .setMethodName(request.get("method").getAsString())
+                                    .setParams(request.get("params").toString()).build(),
+                            new SessionCloseOnErrorCallback(transportService, sessionInfo));
+                }));
+        return responseWriter;
+    }
+
+    @RequestMapping(value = "/{deviceToken}/attributes/updates", method = RequestMethod.GET, produces = "application/json")
+    public DeferredResult<ResponseEntity> subscribeToAttributes(@PathVariable("deviceToken") String deviceToken,
+                                                                @RequestParam(value = "timeout", required = false, defaultValue = "0") long timeout,
+                                                                HttpServletRequest httpRequest) {
+        DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
+        if (quotaExceeded(httpRequest, responseWriter)) {
+            return responseWriter;
+        }
+        transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
+                new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
+                    TransportService transportService = transportContext.getTransportService();
+                    transportService.registerSyncSession(sessionInfo, new HttpSessionListener(responseWriter),
+                            timeout == 0 ? transportContext.getDefaultTimeout() : timeout);
+                    transportService.process(sessionInfo, SubscribeToAttributeUpdatesMsg.getDefaultInstance(),
+                            new SessionCloseOnErrorCallback(transportService, sessionInfo));
+
+                }));
+        return responseWriter;
+    }
+
+    private boolean quotaExceeded(HttpServletRequest request, DeferredResult<ResponseEntity> responseWriter) {
+        if (transportContext.getQuotaService().isQuotaExceeded(request.getRemoteAddr())) {
+            log.warn("REST Quota exceeded for [{}] . Disconnect", request.getRemoteAddr());
+            responseWriter.setResult(new ResponseEntity<>(HttpStatus.BANDWIDTH_LIMIT_EXCEEDED));
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private static class DeviceAuthCallback implements TransportServiceCallback<ValidateDeviceCredentialsResponseMsg> {
+        private final TransportContext transportContext;
+        private final DeferredResult<ResponseEntity> responseWriter;
+        private final Consumer<SessionInfoProto> onSuccess;
+
+        DeviceAuthCallback(TransportContext transportContext, DeferredResult<ResponseEntity> responseWriter, Consumer<SessionInfoProto> onSuccess) {
+            this.transportContext = transportContext;
+            this.responseWriter = responseWriter;
+            this.onSuccess = onSuccess;
+        }
+
+        @Override
+        public void onSuccess(ValidateDeviceCredentialsResponseMsg msg) {
+            if (msg.hasDeviceInfo()) {
+                UUID sessionId = UUID.randomUUID();
+                DeviceInfoProto deviceInfoProto = msg.getDeviceInfo();
+                SessionInfoProto sessionInfo = SessionInfoProto.newBuilder()
+                        .setNodeId(transportContext.getNodeId())
+                        .setTenantIdMSB(deviceInfoProto.getTenantIdMSB())
+                        .setTenantIdLSB(deviceInfoProto.getTenantIdLSB())
+                        .setDeviceIdMSB(deviceInfoProto.getDeviceIdMSB())
+                        .setDeviceIdLSB(deviceInfoProto.getDeviceIdLSB())
+                        .setSessionIdMSB(sessionId.getMostSignificantBits())
+                        .setSessionIdLSB(sessionId.getLeastSignificantBits())
+                        .build();
+                onSuccess.accept(sessionInfo);
+            } else {
+                responseWriter.setResult(new ResponseEntity<>(HttpStatus.UNAUTHORIZED));
+            }
+        }
+
+        @Override
+        public void onError(Throwable e) {
+            log.warn("Failed to process request", e);
+            responseWriter.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
+        }
+    }
+
+    private static class SessionCloseOnErrorCallback implements TransportServiceCallback<Void> {
+        private final TransportService transportService;
+        private final SessionInfoProto sessionInfo;
+
+        SessionCloseOnErrorCallback(TransportService transportService, SessionInfoProto sessionInfo) {
+            this.transportService = transportService;
+            this.sessionInfo = sessionInfo;
+        }
+
+        @Override
+        public void onSuccess(Void msg) {
+        }
+
+        @Override
+        public void onError(Throwable e) {
+            transportService.deregisterSession(sessionInfo);
+        }
+    }
+
+    private static class HttpOkCallback implements TransportServiceCallback<Void> {
+        private final DeferredResult<ResponseEntity> responseWriter;
+
+        public HttpOkCallback(DeferredResult<ResponseEntity> responseWriter) {
+            this.responseWriter = responseWriter;
+        }
+
+        @Override
+        public void onSuccess(Void msg) {
+            responseWriter.setResult(new ResponseEntity<>(HttpStatus.OK));
+        }
+
+        @Override
+        public void onError(Throwable e) {
+            responseWriter.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
+        }
+    }
+
+
+    private static class HttpSessionListener implements SessionMsgListener {
+
+        private final DeferredResult<ResponseEntity> responseWriter;
+
+        HttpSessionListener(DeferredResult<ResponseEntity> responseWriter) {
+            this.responseWriter = responseWriter;
+        }
+
+        @Override
+        public void onGetAttributesResponse(GetAttributeResponseMsg msg) {
+            responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg).toString(), HttpStatus.OK));
+        }
+
+        @Override
+        public void onAttributeUpdate(AttributeUpdateNotificationMsg msg) {
+            responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg).toString(), HttpStatus.OK));
+        }
+
+        @Override
+        public void onRemoteSessionCloseCommand(SessionCloseNotificationProto sessionCloseNotification) {
+            responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT));
+        }
+
+        @Override
+        public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg msg) {
+            responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg, true).toString(), HttpStatus.OK));
+        }
+
+        @Override
+        public void onToServerRpcResponse(ToServerRpcResponseMsg msg) {
+            responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg).toString(), HttpStatus.OK));
+        }
+    }
+}
diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml
new file mode 100644
index 0000000..91f30a6
--- /dev/null
+++ b/common/transport/mqtt/pom.xml
@@ -0,0 +1,98 @@
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.thingsboard.common</groupId>
+        <version>2.2.0-SNAPSHOT</version>
+        <artifactId>transport</artifactId>
+    </parent>
+    <groupId>org.thingsboard.common.transport</groupId>
+    <artifactId>mqtt</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Thingsboard MQTT Transport Common</name>
+    <url>https://thingsboard.io</url>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <main.dir>${basedir}/../../..</main.dir>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.thingsboard.common.transport</groupId>
+            <artifactId>transport-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context-support</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>log4j-over-slf4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>jsr305</artifactId>
+            <version>3.0.1</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java
new file mode 100644
index 0000000..8393ca9
--- /dev/null
+++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java
@@ -0,0 +1,217 @@
+/**
+ * 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.transport.mqtt.adaptors;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.UnpooledByteBufAllocator;
+import io.netty.handler.codec.mqtt.MqttFixedHeader;
+import io.netty.handler.codec.mqtt.MqttMessage;
+import io.netty.handler.codec.mqtt.MqttMessageType;
+import io.netty.handler.codec.mqtt.MqttPublishMessage;
+import io.netty.handler.codec.mqtt.MqttPublishVariableHeader;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.thingsboard.server.common.transport.adaptor.AdaptorException;
+import org.thingsboard.server.common.transport.adaptor.JsonConverter;
+import org.thingsboard.server.gen.transport.TransportProtos;
+import org.thingsboard.server.transport.mqtt.MqttTopics;
+import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext;
+
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * @author Andrew Shvayka
+ */
+@Component("JsonMqttAdaptor")
+@Slf4j
+public class JsonMqttAdaptor implements MqttTransportAdaptor {
+
+    private static final Gson GSON = new Gson();
+    private static final Charset UTF8 = Charset.forName("UTF-8");
+    private static final ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false);
+
+    @Override
+    public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
+        String payload = validatePayload(ctx.getSessionId(), inbound.payload());
+        try {
+            return JsonConverter.convertToTelemetryProto(new JsonParser().parse(payload));
+        } catch (IllegalStateException | JsonSyntaxException ex) {
+            throw new AdaptorException(ex);
+        }
+    }
+
+    @Override
+    public TransportProtos.PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
+        String payload = validatePayload(ctx.getSessionId(), inbound.payload());
+        try {
+            return JsonConverter.convertToAttributesProto(new JsonParser().parse(payload));
+        } catch (IllegalStateException | JsonSyntaxException ex) {
+            throw new AdaptorException(ex);
+        }
+    }
+
+    @Override
+    public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
+        String topicName = inbound.variableHeader().topicName();
+        try {
+            TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder();
+            result.setRequestId(Integer.valueOf(topicName.substring(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX.length())));
+            String payload = inbound.payload().toString(UTF8);
+            JsonElement requestBody = new JsonParser().parse(payload);
+            Set<String> clientKeys = toStringSet(requestBody, "clientKeys");
+            Set<String> sharedKeys = toStringSet(requestBody, "sharedKeys");
+            if (clientKeys != null) {
+                result.addAllClientAttributeNames(clientKeys);
+            }
+            if (sharedKeys != null) {
+                result.addAllSharedAttributeNames(sharedKeys);
+            }
+            return result.build();
+        } catch (RuntimeException e) {
+            log.warn("Failed to decode get attributes request", e);
+            throw new AdaptorException(e);
+        }
+    }
+
+    @Override
+    public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
+        String topicName = inbound.variableHeader().topicName();
+        try {
+            Integer requestId = Integer.valueOf(topicName.substring(MqttTopics.DEVICE_RPC_RESPONSE_TOPIC.length()));
+            String payload = inbound.payload().toString(UTF8);
+            return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(payload).build();
+        } catch (RuntimeException e) {
+            log.warn("Failed to decode get attributes request", e);
+            throw new AdaptorException(e);
+        }
+    }
+
+    @Override
+    public TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
+        String topicName = inbound.variableHeader().topicName();
+        String payload = validatePayload(ctx.getSessionId(), inbound.payload());
+        try {
+            Integer requestId = Integer.valueOf(topicName.substring(MqttTopics.DEVICE_RPC_REQUESTS_TOPIC.length()));
+            return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), requestId);
+        } catch (IllegalStateException | JsonSyntaxException ex) {
+            throw new AdaptorException(ex);
+        }
+    }
+
+    @Override
+    public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
+        if (!StringUtils.isEmpty(responseMsg.getError())) {
+            throw new AdaptorException(responseMsg.getError());
+        } else {
+            Integer requestId = responseMsg.getRequestId();
+            if (requestId >= 0) {
+                return Optional.of(createMqttPublishMsg(ctx,
+                        MqttTopics.DEVICE_ATTRIBUTES_RESPONSE_TOPIC_PREFIX + requestId,
+                        JsonConverter.toJson(responseMsg)));
+            }
+            return Optional.empty();
+        }
+    }
+
+    @Override
+    public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
+        if (!StringUtils.isEmpty(responseMsg.getError())) {
+            throw new AdaptorException(responseMsg.getError());
+        } else {
+            JsonObject result = JsonConverter.getJsonObjectForGateway(responseMsg);
+            return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, result));
+        }
+    }
+
+    @Override
+    public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException {
+        return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_ATTRIBUTES_TOPIC, JsonConverter.toJson(notificationMsg)));
+    }
+
+    @Override
+    public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException {
+        JsonObject result = JsonConverter.getJsonObjectForGateway(deviceName, notificationMsg);
+        return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, result));
+    }
+
+    @Override
+    public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException {
+        return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), JsonConverter.toJson(rpcRequest, false)));
+    }
+
+    @Override
+    public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException {
+        return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_RPC_TOPIC, JsonConverter.toGatewayJson(deviceName, rpcRequest)));
+    }
+
+    @Override
+    public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToServerRpcResponseMsg rpcResponse) {
+        return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC + rpcResponse.getRequestId(), JsonConverter.toJson(rpcResponse)));
+    }
+
+    private MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, JsonElement json) {
+        MqttFixedHeader mqttFixedHeader =
+                new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0);
+        MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId());
+        ByteBuf payload = ALLOCATOR.buffer();
+        payload.writeBytes(GSON.toJson(json).getBytes(UTF8));
+        return new MqttPublishMessage(mqttFixedHeader, header, payload);
+    }
+
+    private Set<String> toStringSet(JsonElement requestBody, String name) {
+        JsonElement element = requestBody.getAsJsonObject().get(name);
+        if (element != null) {
+            return new HashSet<>(Arrays.asList(element.getAsString().split(",")));
+        } else {
+            return null;
+        }
+    }
+
+    public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException {
+        String payload = validatePayload(sessionId, payloadData);
+        try {
+            return new JsonParser().parse(payload);
+        } catch (JsonSyntaxException ex) {
+            throw new AdaptorException(ex);
+        }
+    }
+
+    private static String validatePayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException {
+        try {
+            String payload = payloadData.toString(UTF8);
+            if (payload == null) {
+                log.warn("[{}] Payload is empty!", sessionId);
+                throw new AdaptorException(new IllegalArgumentException("Payload is empty!"));
+            }
+            return payload;
+        } finally {
+            payloadData.release();
+        }
+    }
+
+}
diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java
new file mode 100644
index 0000000..7af0e66
--- /dev/null
+++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java
@@ -0,0 +1,62 @@
+/**
+ * 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.transport.mqtt.adaptors;
+
+import io.netty.handler.codec.mqtt.MqttMessage;
+import io.netty.handler.codec.mqtt.MqttPublishMessage;
+import org.thingsboard.server.common.transport.adaptor.AdaptorException;
+import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
+import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext;
+
+import java.util.Optional;
+
+/**
+ * @author Andrew Shvayka
+ */
+public interface MqttTransportAdaptor {
+
+    PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException;
+
+    PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException;
+
+    GetAttributeRequestMsg convertToGetAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException;
+
+    ToDeviceRpcResponseMsg convertToDeviceRpcResponse(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg) throws AdaptorException;
+
+    ToServerRpcRequestMsg convertToServerRpcRequest(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg) throws AdaptorException;
+
+    Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, GetAttributeResponseMsg responseMsg) throws AdaptorException;
+
+    Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, GetAttributeResponseMsg responseMsg) throws AdaptorException;
+
+    Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException;
+
+    Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException;
+
+    Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException;
+
+    Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException;
+
+    Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ToServerRpcResponseMsg rpcResponse) throws AdaptorException;
+}
diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java
new file mode 100644
index 0000000..538daa1
--- /dev/null
+++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java
@@ -0,0 +1,64 @@
+/**
+ * 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.transport.mqtt;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.netty.handler.ssl.SslHandler;
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.transport.TransportContext;
+import org.thingsboard.server.common.transport.TransportService;
+import org.thingsboard.server.common.transport.quota.host.HostRequestsQuotaService;
+import org.thingsboard.server.kafka.TbNodeIdProvider;
+import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Created by ashvayka on 04.10.18.
+ */
+@Slf4j
+@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.mqtt.enabled}'=='true')")
+@Component
+public class MqttTransportContext extends TransportContext {
+
+    @Getter
+    @Autowired(required = false)
+    private MqttSslHandlerProvider sslHandlerProvider;
+
+    @Getter
+    @Autowired
+    private MqttTransportAdaptor adaptor;
+
+    @Getter
+    @Value("${transport.mqtt.netty.max_payload_size}")
+    private Integer maxPayloadSize;
+
+    @Getter
+    @Setter
+    private SslHandler sslHandler;
+
+}
diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java
new file mode 100644
index 0000000..38c1fff
--- /dev/null
+++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java
@@ -0,0 +1,101 @@
+/**
+ * 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.transport.mqtt.session;
+
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.server.common.transport.SessionMsgListener;
+import org.thingsboard.server.gen.transport.TransportProtos;
+import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
+import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
+
+import java.util.UUID;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Created by ashvayka on 19.01.17.
+ */
+@Slf4j
+public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext implements SessionMsgListener {
+
+    private final GatewaySessionHandler parent;
+    private final SessionInfoProto sessionInfo;
+
+    public GatewayDeviceSessionCtx(GatewaySessionHandler parent, DeviceInfoProto deviceInfo, ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap) {
+        super(UUID.randomUUID(), mqttQoSMap);
+        this.parent = parent;
+        this.sessionInfo = SessionInfoProto.newBuilder()
+                .setNodeId(parent.getNodeId())
+                .setSessionIdMSB(sessionId.getMostSignificantBits())
+                .setSessionIdLSB(sessionId.getLeastSignificantBits())
+                .setDeviceIdMSB(deviceInfo.getDeviceIdMSB())
+                .setDeviceIdLSB(deviceInfo.getDeviceIdLSB())
+                .setTenantIdMSB(deviceInfo.getTenantIdMSB())
+                .setTenantIdLSB(deviceInfo.getTenantIdLSB())
+                .build();
+        setDeviceInfo(deviceInfo);
+    }
+
+    @Override
+    public UUID getSessionId() {
+        return sessionId;
+    }
+
+    @Override
+    public int nextMsgId() {
+        return parent.nextMsgId();
+    }
+
+    SessionInfoProto getSessionInfo() {
+        return sessionInfo;
+    }
+
+    @Override
+    public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg response) {
+        try {
+            parent.getAdaptor().convertToGatewayPublish(this, response).ifPresent(parent::writeAndFlush);
+        } catch (Exception e) {
+            log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
+        }
+    }
+
+    @Override
+    public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg notification) {
+        try {
+            parent.getAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), notification).ifPresent(parent::writeAndFlush);
+        } catch (Exception e) {
+            log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
+        }
+    }
+
+    @Override
+    public void onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto sessionCloseNotification) {
+        parent.deregisterSession(getDeviceInfo().getDeviceName());
+    }
+
+    @Override
+    public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg request) {
+        try {
+            parent.getAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), request).ifPresent(parent::writeAndFlush);
+        } catch (Exception e) {
+            log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
+        }
+    }
+
+    @Override
+    public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse) {
+        // This feature is not supported in the TB IoT Gateway yet.
+    }
+}
diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java
new file mode 100644
index 0000000..d600059
--- /dev/null
+++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java
@@ -0,0 +1,370 @@
+/**
+ * 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.transport.mqtt.session;
+
+
+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.SettableFuture;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSyntaxException;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.mqtt.MqttMessage;
+import io.netty.handler.codec.mqtt.MqttPublishMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
+import org.thingsboard.server.common.transport.TransportService;
+import org.thingsboard.server.common.transport.TransportServiceCallback;
+import org.thingsboard.server.common.transport.adaptor.AdaptorException;
+import org.thingsboard.server.common.transport.adaptor.JsonConverter;
+import org.thingsboard.server.gen.transport.TransportProtos;
+import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
+import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
+import org.thingsboard.server.transport.mqtt.MqttTransportContext;
+import org.thingsboard.server.transport.mqtt.MqttTransportHandler;
+import org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor;
+import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
+
+import javax.annotation.Nullable;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Created by ashvayka on 19.01.17.
+ */
+@Slf4j
+public class GatewaySessionHandler {
+
+    private static final String DEFAULT_DEVICE_TYPE = "default";
+    private static final String CAN_T_PARSE_VALUE = "Can't parse value: ";
+    private static final String DEVICE_PROPERTY = "device";
+
+    private final MqttTransportContext context;
+    private final TransportService transportService;
+    private final DeviceInfoProto gateway;
+    private final UUID sessionId;
+    private final Map<String, GatewayDeviceSessionCtx> devices;
+    private final ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap;
+    private final ChannelHandlerContext channel;
+    private final DeviceSessionCtx deviceSessionCtx;
+
+    public GatewaySessionHandler(MqttTransportContext context, DeviceSessionCtx deviceSessionCtx, UUID sessionId) {
+        this.context = context;
+        this.transportService = context.getTransportService();
+        this.deviceSessionCtx = deviceSessionCtx;
+        this.gateway = deviceSessionCtx.getDeviceInfo();
+        this.sessionId = sessionId;
+        this.devices = new ConcurrentHashMap<>();
+        this.mqttQoSMap = deviceSessionCtx.getMqttQoSMap();
+        this.channel = deviceSessionCtx.getChannel();
+    }
+
+    public void onDeviceConnect(MqttPublishMessage msg) throws AdaptorException {
+        JsonElement json = getJson(msg);
+        String deviceName = checkDeviceName(getDeviceName(json));
+        String deviceType = getDeviceType(json);
+        log.trace("[{}] onDeviceConnect: {}", sessionId, deviceName);
+        Futures.addCallback(onDeviceConnect(deviceName, deviceType), new FutureCallback<GatewayDeviceSessionCtx>() {
+            @Override
+            public void onSuccess(@Nullable GatewayDeviceSessionCtx result) {
+                ack(msg);
+                log.trace("[{}] onDeviceConnectOk: {}", sessionId, deviceName);
+            }
+
+            @Override
+            public void onFailure(Throwable t) {
+                log.warn("[{}] Failed to process device connect command: {}", sessionId, deviceName, t);
+
+            }
+        }, context.getExecutor());
+    }
+
+    private ListenableFuture<GatewayDeviceSessionCtx> onDeviceConnect(String deviceName, String deviceType) {
+        SettableFuture<GatewayDeviceSessionCtx> future = SettableFuture.create();
+        GatewayDeviceSessionCtx result = devices.get(deviceName);
+        if (result == null) {
+            transportService.process(GetOrCreateDeviceFromGatewayRequestMsg.newBuilder()
+                            .setDeviceName(deviceName)
+                            .setDeviceType(deviceType)
+                            .setGatewayIdMSB(gateway.getDeviceIdMSB())
+                            .setGatewayIdLSB(gateway.getDeviceIdLSB()).build(),
+                    new TransportServiceCallback<GetOrCreateDeviceFromGatewayResponseMsg>() {
+                        @Override
+                        public void onSuccess(GetOrCreateDeviceFromGatewayResponseMsg msg) {
+                            GatewayDeviceSessionCtx deviceSessionCtx = new GatewayDeviceSessionCtx(GatewaySessionHandler.this, msg.getDeviceInfo(), mqttQoSMap);
+                            if (devices.putIfAbsent(deviceName, deviceSessionCtx) == null) {
+                                SessionInfoProto deviceSessionInfo = deviceSessionCtx.getSessionInfo();
+                                transportService.process(deviceSessionInfo, MqttTransportHandler.getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null);
+                                transportService.process(deviceSessionInfo, TransportProtos.SubscribeToRPCMsg.getDefaultInstance(), null);
+                                transportService.process(deviceSessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), null);
+                                transportService.registerAsyncSession(deviceSessionInfo, deviceSessionCtx);
+                            }
+                            future.set(devices.get(deviceName));
+                        }
+
+                        @Override
+                        public void onError(Throwable e) {
+                            log.warn("[{}] Failed to process device connect command: {}", sessionId, deviceName, e);
+                            future.setException(e);
+                        }
+                    });
+        } else {
+            future.set(result);
+        }
+        return future;
+    }
+
+    public void onDeviceDisconnect(MqttPublishMessage msg) throws AdaptorException {
+        String deviceName = checkDeviceName(getDeviceName(getJson(msg)));
+        deregisterSession(deviceName);
+        ack(msg);
+    }
+
+    void deregisterSession(String deviceName) {
+        GatewayDeviceSessionCtx deviceSessionCtx = devices.remove(deviceName);
+        if (deviceSessionCtx != null) {
+            deregisterSession(deviceName, deviceSessionCtx);
+        } else {
+            log.debug("[{}] Device [{}] was already removed from the gateway session", sessionId, deviceName);
+        }
+    }
+
+    public void onGatewayDisconnect() {
+        devices.forEach(this::deregisterSession);
+    }
+
+    public void onDeviceTelemetry(MqttPublishMessage mqttMsg) throws AdaptorException {
+        JsonElement json = JsonMqttAdaptor.validateJsonPayload(sessionId, mqttMsg.payload());
+        int msgId = mqttMsg.variableHeader().packetId();
+        if (json.isJsonObject()) {
+            JsonObject jsonObj = json.getAsJsonObject();
+            for (Map.Entry<String, JsonElement> deviceEntry : jsonObj.entrySet()) {
+                String deviceName = deviceEntry.getKey();
+                Futures.addCallback(checkDeviceConnected(deviceName),
+                        new FutureCallback<GatewayDeviceSessionCtx>() {
+                            @Override
+                            public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) {
+                                if (!deviceEntry.getValue().isJsonArray()) {
+                                    throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
+                                }
+                                TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(deviceEntry.getValue().getAsJsonArray());
+                                transportService.process(deviceCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(channel, deviceName, msgId, postTelemetryMsg));
+                            }
+
+                            @Override
+                            public void onFailure(Throwable t) {
+                                log.debug("[{}] Failed to process device teleemtry command: {}", sessionId, deviceName, t);
+                            }
+                        }, context.getExecutor());
+            }
+        } else {
+            throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
+        }
+    }
+
+    public void onDeviceAttributes(MqttPublishMessage mqttMsg) throws AdaptorException {
+        JsonElement json = JsonMqttAdaptor.validateJsonPayload(sessionId, mqttMsg.payload());
+        int msgId = mqttMsg.variableHeader().packetId();
+        if (json.isJsonObject()) {
+            JsonObject jsonObj = json.getAsJsonObject();
+            for (Map.Entry<String, JsonElement> deviceEntry : jsonObj.entrySet()) {
+                String deviceName = deviceEntry.getKey();
+                Futures.addCallback(checkDeviceConnected(deviceName),
+                        new FutureCallback<GatewayDeviceSessionCtx>() {
+                            @Override
+                            public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) {
+                                if (!deviceEntry.getValue().isJsonObject()) {
+                                    throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
+                                }
+                                TransportProtos.PostAttributeMsg postAttributeMsg = JsonConverter.convertToAttributesProto(deviceEntry.getValue().getAsJsonObject());
+                                transportService.process(deviceCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(channel, deviceName, msgId, postAttributeMsg));
+                            }
+
+                            @Override
+                            public void onFailure(Throwable t) {
+                                log.debug("[{}] Failed to process device attributes command: {}", sessionId, deviceName, t);
+                            }
+                        }, context.getExecutor());
+            }
+        } else {
+            throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
+        }
+    }
+
+    public void onDeviceRpcResponse(MqttPublishMessage mqttMsg) throws AdaptorException {
+        JsonElement json = JsonMqttAdaptor.validateJsonPayload(sessionId, mqttMsg.payload());
+        int msgId = mqttMsg.variableHeader().packetId();
+        if (json.isJsonObject()) {
+            JsonObject jsonObj = json.getAsJsonObject();
+            String deviceName = jsonObj.get(DEVICE_PROPERTY).getAsString();
+            Futures.addCallback(checkDeviceConnected(deviceName),
+                    new FutureCallback<GatewayDeviceSessionCtx>() {
+                        @Override
+                        public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) {
+                            Integer requestId = jsonObj.get("id").getAsInt();
+                            String data = jsonObj.get("data").toString();
+                            TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
+                                    .setRequestId(requestId).setPayload(data).build();
+                            transportService.process(deviceCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(channel, deviceName, msgId, rpcResponseMsg));
+                        }
+
+                        @Override
+                        public void onFailure(Throwable t) {
+                            log.debug("[{}] Failed to process device teleemtry command: {}", sessionId, deviceName, t);
+                        }
+                    }, context.getExecutor());
+        } else {
+            throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
+        }
+    }
+
+    public void onDeviceAttributesRequest(MqttPublishMessage msg) throws AdaptorException {
+        JsonElement json = JsonMqttAdaptor.validateJsonPayload(sessionId, msg.payload());
+        if (json.isJsonObject()) {
+            JsonObject jsonObj = json.getAsJsonObject();
+            int requestId = jsonObj.get("id").getAsInt();
+            String deviceName = jsonObj.get(DEVICE_PROPERTY).getAsString();
+            boolean clientScope = jsonObj.get("client").getAsBoolean();
+            Set<String> keys;
+            if (jsonObj.has("key")) {
+                keys = Collections.singleton(jsonObj.get("key").getAsString());
+            } else {
+                JsonArray keysArray = jsonObj.get("keys").getAsJsonArray();
+                keys = new HashSet<>();
+                for (JsonElement keyObj : keysArray) {
+                    keys.add(keyObj.getAsString());
+                }
+            }
+            TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder();
+            result.setRequestId(requestId);
+
+            if (clientScope) {
+                result.addAllClientAttributeNames(keys);
+            } else {
+                result.addAllSharedAttributeNames(keys);
+            }
+            TransportProtos.GetAttributeRequestMsg requestMsg = result.build();
+            int msgId = msg.variableHeader().packetId();
+            Futures.addCallback(checkDeviceConnected(deviceName),
+                    new FutureCallback<GatewayDeviceSessionCtx>() {
+                        @Override
+                        public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) {
+                            transportService.process(deviceCtx.getSessionInfo(), requestMsg, getPubAckCallback(channel, deviceName, msgId, requestMsg));
+                        }
+
+                        @Override
+                        public void onFailure(Throwable t) {
+                            log.debug("[{}] Failed to process device attributes request command: {}", sessionId, deviceName, t);
+                        }
+                    }, context.getExecutor());
+            ack(msg);
+        } else {
+            throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
+        }
+    }
+
+    private ListenableFuture<GatewayDeviceSessionCtx> checkDeviceConnected(String deviceName) {
+        GatewayDeviceSessionCtx ctx = devices.get(deviceName);
+        if (ctx == null) {
+            log.debug("[{}] Missing device [{}] for the gateway session", sessionId, deviceName);
+            return onDeviceConnect(deviceName, DEFAULT_DEVICE_TYPE);
+        } else {
+            return Futures.immediateFuture(ctx);
+        }
+    }
+
+    private String checkDeviceName(String deviceName) {
+        if (StringUtils.isEmpty(deviceName)) {
+            throw new RuntimeException("Device name is empty!");
+        } else {
+            return deviceName;
+        }
+    }
+
+    private String getDeviceName(JsonElement json) throws AdaptorException {
+        return json.getAsJsonObject().get(DEVICE_PROPERTY).getAsString();
+    }
+
+    private String getDeviceType(JsonElement json) throws AdaptorException {
+        JsonElement type = json.getAsJsonObject().get("type");
+        return type == null || type instanceof JsonNull ? DEFAULT_DEVICE_TYPE : type.getAsString();
+    }
+
+    private JsonElement getJson(MqttPublishMessage mqttMsg) throws AdaptorException {
+        return JsonMqttAdaptor.validateJsonPayload(sessionId, mqttMsg.payload());
+    }
+
+    private void ack(MqttPublishMessage msg) {
+        if (msg.variableHeader().packetId() > 0) {
+            writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(msg.variableHeader().packetId()));
+        }
+    }
+
+    void writeAndFlush(MqttMessage mqttMessage) {
+        channel.writeAndFlush(mqttMessage);
+    }
+
+    public String getNodeId() {
+        return context.getNodeId();
+    }
+
+    private void deregisterSession(String deviceName, GatewayDeviceSessionCtx deviceSessionCtx) {
+        transportService.deregisterSession(deviceSessionCtx.getSessionInfo());
+        transportService.process(deviceSessionCtx.getSessionInfo(), MqttTransportHandler.getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null);
+        log.debug("[{}] Removed device [{}] from the gateway session", sessionId, deviceName);
+    }
+
+    private <T> TransportServiceCallback<Void> getPubAckCallback(final ChannelHandlerContext ctx, final String deviceName, final int msgId, final T msg) {
+        return new TransportServiceCallback<Void>() {
+            @Override
+            public void onSuccess(Void dummy) {
+                log.trace("[{}][{}] Published msg: {}", sessionId, deviceName, msg);
+                if (msgId > 0) {
+                    ctx.writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(msgId));
+                }
+            }
+
+            @Override
+            public void onError(Throwable e) {
+                log.trace("[{}] Failed to publish msg: {}", sessionId, deviceName, msg, e);
+                ctx.close();
+            }
+        };
+    }
+
+    public MqttTransportContext getContext() {
+        return context;
+    }
+
+    public MqttTransportAdaptor getAdaptor() {
+        return context.getAdaptor();
+    }
+
+    public int nextMsgId() {
+        return deviceSessionCtx.nextMsgId();
+    }
+}
diff --git a/common/transport/pom.xml b/common/transport/pom.xml
index a349012..83e7ba8 100644
--- a/common/transport/pom.xml
+++ b/common/transport/pom.xml
@@ -16,7 +16,7 @@
 
 -->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.thingsboard</groupId>
@@ -25,67 +25,20 @@
     </parent>
     <groupId>org.thingsboard.common</groupId>
     <artifactId>transport</artifactId>
-    <packaging>jar</packaging>
+    <packaging>pom</packaging>
 
-    <name>Thingsboard Server Common Transport components</name>
+    <name>Thingsboard Server Commons</name>
     <url>https://thingsboard.io</url>
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <main.dir>${basedir}/../..</main.dir>
     </properties>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.thingsboard.common</groupId>
-            <artifactId>data</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.thingsboard.common</groupId>
-            <artifactId>message</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.google.code.gson</groupId>
-            <artifactId>gson</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>log4j-over-slf4j</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>ch.qos.logback</groupId>
-            <artifactId>logback-core</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>ch.qos.logback</groupId>
-            <artifactId>logback-classic</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-all</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework</groupId>
-            <artifactId>spring-context</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.google.guava</groupId>
-            <artifactId>guava</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-lang3</artifactId>
-        </dependency>
-    </dependencies>
+    <modules>
+        <module>transport-api</module>
+        <module>mqtt</module>
+        <module>http</module>
+        <module>coap</module>
+    </modules>
 
 </project>
diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml
new file mode 100644
index 0000000..4ed6ed7
--- /dev/null
+++ b/common/transport/transport-api/pom.xml
@@ -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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.thingsboard.common</groupId>
+        <version>2.2.0-SNAPSHOT</version>
+        <artifactId>transport</artifactId>
+    </parent>
+    <groupId>org.thingsboard.common.transport</groupId>
+    <artifactId>transport-api</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Thingsboard Server Common Transport components</name>
+    <url>https://thingsboard.io</url>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <main.dir>${basedir}/../../..</main.dir>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.thingsboard.common</groupId>
+            <artifactId>data</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.thingsboard.common</groupId>
+            <artifactId>message</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.thingsboard.common</groupId>
+            <artifactId>queue</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>log4j-over-slf4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.protobuf</groupId>
+            <artifactId>protobuf-java</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.vladimir-bukhtoyarov</groupId>
+            <artifactId>bucket4j-core</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.xolstice.maven.plugins</groupId>
+                <artifactId>protobuf-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java
new file mode 100644
index 0000000..7498e8e
--- /dev/null
+++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java
@@ -0,0 +1,430 @@
+/**
+ * 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.transport.adaptor;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSyntaxException;
+import org.springframework.util.StringUtils;
+import org.thingsboard.server.common.data.kv.AttributeKey;
+import org.thingsboard.server.common.data.kv.AttributeKvEntry;
+import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
+import org.thingsboard.server.common.data.kv.BooleanDataEntry;
+import org.thingsboard.server.common.data.kv.DoubleDataEntry;
+import org.thingsboard.server.common.data.kv.KvEntry;
+import org.thingsboard.server.common.data.kv.LongDataEntry;
+import org.thingsboard.server.common.data.kv.StringDataEntry;
+import org.thingsboard.server.common.msg.kv.AttributesKVMsg;
+import org.thingsboard.server.gen.transport.TransportProtos;
+import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto;
+import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
+import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto;
+import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+public class JsonConverter {
+
+    private static final Gson GSON = new Gson();
+    private static final String CAN_T_PARSE_VALUE = "Can't parse value: ";
+    private static final String DEVICE_PROPERTY = "device";
+
+    public static PostTelemetryMsg convertToTelemetryProto(JsonElement jsonObject) throws JsonSyntaxException {
+        long systemTs = System.currentTimeMillis();
+        PostTelemetryMsg.Builder builder = PostTelemetryMsg.newBuilder();
+        if (jsonObject.isJsonObject()) {
+            parseObject(builder, systemTs, jsonObject);
+        } else if (jsonObject.isJsonArray()) {
+            jsonObject.getAsJsonArray().forEach(je -> {
+                if (je.isJsonObject()) {
+                    parseObject(builder, systemTs, je.getAsJsonObject());
+                } else {
+                    throw new JsonSyntaxException(CAN_T_PARSE_VALUE + je);
+                }
+            });
+        } else {
+            throw new JsonSyntaxException(CAN_T_PARSE_VALUE + jsonObject);
+        }
+        return builder.build();
+    }
+
+    public static PostAttributeMsg convertToAttributesProto(JsonElement jsonObject) throws JsonSyntaxException {
+        if (jsonObject.isJsonObject()) {
+            PostAttributeMsg.Builder result = PostAttributeMsg.newBuilder();
+            List<KeyValueProto> keyValueList = parseProtoValues(jsonObject.getAsJsonObject());
+            result.addAllKv(keyValueList);
+            return result.build();
+        } else {
+            throw new JsonSyntaxException(CAN_T_PARSE_VALUE + jsonObject);
+        }
+    }
+
+    public static JsonElement toJson(TransportProtos.ToDeviceRpcRequestMsg msg, boolean includeRequestId) {
+        JsonObject result = new JsonObject();
+        if (includeRequestId) {
+            result.addProperty("id", msg.getRequestId());
+        }
+        result.addProperty("method", msg.getMethodName());
+        result.add("params", new JsonParser().parse(msg.getParams()));
+        return result;
+    }
+
+    private static void parseObject(PostTelemetryMsg.Builder builder, long systemTs, JsonElement jsonObject) {
+        JsonObject jo = jsonObject.getAsJsonObject();
+        if (jo.has("ts") && jo.has("values")) {
+            parseWithTs(builder, jo);
+        } else {
+            parseWithoutTs(builder, systemTs, jo);
+        }
+    }
+
+    private static void parseWithoutTs(PostTelemetryMsg.Builder request, long systemTs, JsonObject jo) {
+        TsKvListProto.Builder builder = TsKvListProto.newBuilder();
+        builder.setTs(systemTs);
+        builder.addAllKv(parseProtoValues(jo));
+        request.addTsKvList(builder.build());
+    }
+
+    private static void parseWithTs(PostTelemetryMsg.Builder request, JsonObject jo) {
+        TsKvListProto.Builder builder = TsKvListProto.newBuilder();
+        builder.setTs(jo.get("ts").getAsLong());
+        builder.addAllKv(parseProtoValues(jo.get("values").getAsJsonObject()));
+        request.addTsKvList(builder.build());
+    }
+
+    private static List<KeyValueProto> parseProtoValues(JsonObject valuesObject) {
+        List<KeyValueProto> result = new ArrayList<>();
+        for (Entry<String, JsonElement> valueEntry : valuesObject.entrySet()) {
+            JsonElement element = valueEntry.getValue();
+            if (element.isJsonPrimitive()) {
+                JsonPrimitive value = element.getAsJsonPrimitive();
+                if (value.isString()) {
+                    result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.STRING_V)
+                            .setStringV(value.getAsString()).build());
+                } else if (value.isBoolean()) {
+                    result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.BOOLEAN_V)
+                            .setBoolV(value.getAsBoolean()).build());
+                } else if (value.isNumber()) {
+                    if (value.getAsString().contains(".")) {
+                        result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.DOUBLE_V)
+                                .setDoubleV(value.getAsDouble()).build());
+                    } else {
+                        try {
+                            long longValue = Long.parseLong(value.getAsString());
+                            result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.LONG_V)
+                                    .setLongV(longValue).build());
+                        } catch (NumberFormatException e) {
+                            throw new JsonSyntaxException("Big integer values are not supported!");
+                        }
+                    }
+                } else {
+                    throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value);
+                }
+            } else {
+                throw new JsonSyntaxException(CAN_T_PARSE_VALUE + element);
+            }
+        }
+        return result;
+    }
+
+    public static TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(JsonElement json, int requestId) throws JsonSyntaxException {
+        JsonObject object = json.getAsJsonObject();
+        return TransportProtos.ToServerRpcRequestMsg.newBuilder().setRequestId(requestId).setMethodName(object.get("method").getAsString()).setParams(GSON.toJson(object.get("params"))).build();
+    }
+
+    private static void parseNumericValue(List<KvEntry> result, Entry<String, JsonElement> valueEntry, JsonPrimitive value) {
+        if (value.getAsString().contains(".")) {
+            result.add(new DoubleDataEntry(valueEntry.getKey(), value.getAsDouble()));
+        } else {
+            try {
+                long longValue = Long.parseLong(value.getAsString());
+                result.add(new LongDataEntry(valueEntry.getKey(), longValue));
+            } catch (NumberFormatException e) {
+                throw new JsonSyntaxException("Big integer values are not supported!");
+            }
+        }
+    }
+
+    public static JsonObject toJson(GetAttributeResponseMsg payload) {
+        JsonObject result = new JsonObject();
+        if (payload.getClientAttributeListCount() > 0) {
+            JsonObject attrObject = new JsonObject();
+            payload.getClientAttributeListList().forEach(addToObjectFromProto(attrObject));
+            result.add("client", attrObject);
+        }
+        if (payload.getSharedAttributeListCount() > 0) {
+            JsonObject attrObject = new JsonObject();
+            payload.getSharedAttributeListList().forEach(addToObjectFromProto(attrObject));
+            result.add("shared", attrObject);
+        }
+        if (payload.getDeletedAttributeKeysCount() > 0) {
+            JsonArray attrObject = new JsonArray();
+            payload.getDeletedAttributeKeysList().forEach(attrObject::add);
+            result.add("deleted", attrObject);
+        }
+        return result;
+    }
+
+    public static JsonElement toJson(AttributeUpdateNotificationMsg payload) {
+        JsonObject result = new JsonObject();
+        if (payload.getSharedUpdatedCount() > 0) {
+            payload.getSharedUpdatedList().forEach(addToObjectFromProto(result));
+        }
+        if (payload.getSharedDeletedCount() > 0) {
+            JsonArray attrObject = new JsonArray();
+            payload.getSharedDeletedList().forEach(attrObject::add);
+            result.add("deleted", attrObject);
+        }
+        return result;
+    }
+
+    public static JsonObject toJson(AttributesKVMsg payload, boolean asMap) {
+        JsonObject result = new JsonObject();
+        if (asMap) {
+            if (!payload.getClientAttributes().isEmpty()) {
+                JsonObject attrObject = new JsonObject();
+                payload.getClientAttributes().forEach(addToObject(attrObject));
+                result.add("client", attrObject);
+            }
+            if (!payload.getSharedAttributes().isEmpty()) {
+                JsonObject attrObject = new JsonObject();
+                payload.getSharedAttributes().forEach(addToObject(attrObject));
+                result.add("shared", attrObject);
+            }
+        } else {
+            payload.getClientAttributes().forEach(addToObject(result));
+            payload.getSharedAttributes().forEach(addToObject(result));
+        }
+        if (!payload.getDeletedAttributes().isEmpty()) {
+            JsonArray attrObject = new JsonArray();
+            payload.getDeletedAttributes().forEach(addToObject(attrObject));
+            result.add("deleted", attrObject);
+        }
+        return result;
+    }
+
+    public static JsonObject getJsonObjectForGateway(TransportProtos.GetAttributeResponseMsg responseMsg) {
+        JsonObject result = new JsonObject();
+        result.addProperty("id", responseMsg.getRequestId());
+        if (responseMsg.getClientAttributeListCount() > 0) {
+            addValues(result, responseMsg.getClientAttributeListList());
+        }
+        if (responseMsg.getSharedAttributeListCount() > 0) {
+            addValues(result, responseMsg.getSharedAttributeListList());
+        }
+        return result;
+    }
+
+    public static JsonObject getJsonObjectForGateway(String deviceName, AttributeUpdateNotificationMsg notificationMsg) {
+        JsonObject result = new JsonObject();
+        result.addProperty(DEVICE_PROPERTY, deviceName);
+        result.add("data", toJson(notificationMsg));
+        return result;
+    }
+
+    private static void addValues(JsonObject result, List<TransportProtos.TsKvProto> kvList) {
+        if (kvList.size() == 1) {
+            addValueToJson(result, "value", kvList.get(0).getKv());
+        } else {
+            JsonObject values;
+            if (result.has("values")) {
+                values = result.get("values").getAsJsonObject();
+            } else {
+                values = new JsonObject();
+                result.add("values", values);
+            }
+            kvList.forEach(value -> addValueToJson(values, value.getKv().getKey(), value.getKv()));
+        }
+    }
+
+    private static void addValueToJson(JsonObject json, String name, TransportProtos.KeyValueProto entry) {
+        switch (entry.getType()) {
+            case BOOLEAN_V:
+                json.addProperty(name, entry.getBoolV());
+                break;
+            case STRING_V:
+                json.addProperty(name, entry.getStringV());
+                break;
+            case DOUBLE_V:
+                json.addProperty(name, entry.getDoubleV());
+                break;
+            case LONG_V:
+                json.addProperty(name, entry.getLongV());
+                break;
+        }
+    }
+
+    private static Consumer<AttributeKey> addToObject(JsonArray result) {
+        return key -> result.add(key.getAttributeKey());
+    }
+
+    private static Consumer<TsKvProto> addToObjectFromProto(JsonObject result) {
+        return de -> {
+            JsonPrimitive value;
+            switch (de.getKv().getType()) {
+                case BOOLEAN_V:
+                    value = new JsonPrimitive(de.getKv().getBoolV());
+                    break;
+                case DOUBLE_V:
+                    value = new JsonPrimitive(de.getKv().getDoubleV());
+                    break;
+                case LONG_V:
+                    value = new JsonPrimitive(de.getKv().getLongV());
+                    break;
+                case STRING_V:
+                    value = new JsonPrimitive(de.getKv().getStringV());
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unsupported data type: " + de.getKv().getType());
+            }
+            result.add(de.getKv().getKey(), value);
+        };
+    }
+
+    private static Consumer<AttributeKvEntry> addToObject(JsonObject result) {
+        return de -> {
+            JsonPrimitive value;
+            switch (de.getDataType()) {
+                case BOOLEAN:
+                    value = new JsonPrimitive(de.getBooleanValue().get());
+                    break;
+                case DOUBLE:
+                    value = new JsonPrimitive(de.getDoubleValue().get());
+                    break;
+                case LONG:
+                    value = new JsonPrimitive(de.getLongValue().get());
+                    break;
+                case STRING:
+                    value = new JsonPrimitive(de.getStrValue().get());
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unsupported data type: " + de.getDataType());
+            }
+            result.add(de.getKey(), value);
+        };
+    }
+
+    public static JsonElement toJson(TransportProtos.ToServerRpcResponseMsg msg) {
+        if (StringUtils.isEmpty(msg.getError())) {
+            return new JsonParser().parse(msg.getPayload());
+        } else {
+            JsonObject errorMsg = new JsonObject();
+            errorMsg.addProperty("error", msg.getError());
+            return errorMsg;
+        }
+    }
+
+    public static JsonElement toErrorJson(String errorMsg) {
+        JsonObject error = new JsonObject();
+        error.addProperty("error", errorMsg);
+        return error;
+    }
+
+    public static JsonElement toGatewayJson(String deviceName, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) {
+        JsonObject result = new JsonObject();
+        result.addProperty(DEVICE_PROPERTY, deviceName);
+        result.add("data", JsonConverter.toJson(rpcRequest, true));
+        return result;
+    }
+
+    public static Set<AttributeKvEntry> convertToAttributes(JsonElement element) {
+        Set<AttributeKvEntry> result = new HashSet<>();
+        long ts = System.currentTimeMillis();
+        result.addAll(parseValues(element.getAsJsonObject()).stream().map(kv -> new BaseAttributeKvEntry(kv, ts)).collect(Collectors.toList()));
+        return result;
+    }
+
+    private static List<KvEntry> parseValues(JsonObject valuesObject) {
+        List<KvEntry> result = new ArrayList<>();
+        for (Entry<String, JsonElement> valueEntry : valuesObject.entrySet()) {
+            JsonElement element = valueEntry.getValue();
+            if (element.isJsonPrimitive()) {
+                JsonPrimitive value = element.getAsJsonPrimitive();
+                if (value.isString()) {
+                    result.add(new StringDataEntry(valueEntry.getKey(), value.getAsString()));
+                } else if (value.isBoolean()) {
+                    result.add(new BooleanDataEntry(valueEntry.getKey(), value.getAsBoolean()));
+                } else if (value.isNumber()) {
+                    parseNumericValue(result, valueEntry, value);
+                } else {
+                    throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value);
+                }
+            } else {
+                throw new JsonSyntaxException(CAN_T_PARSE_VALUE + element);
+            }
+        }
+        return result;
+    }
+
+    public static Map<Long, List<KvEntry>> convertToTelemetry(JsonElement jsonObject, long systemTs) throws JsonSyntaxException {
+        Map<Long, List<KvEntry>> result = new HashMap<>();
+        if (jsonObject.isJsonObject()) {
+            parseObject(result, systemTs, jsonObject);
+        } else if (jsonObject.isJsonArray()) {
+            jsonObject.getAsJsonArray().forEach(je -> {
+                if (je.isJsonObject()) {
+                    parseObject(result, systemTs, je.getAsJsonObject());
+                } else {
+                    throw new JsonSyntaxException(CAN_T_PARSE_VALUE + je);
+                }
+            });
+        } else {
+            throw new JsonSyntaxException(CAN_T_PARSE_VALUE + jsonObject);
+        }
+        return result;
+    }
+
+    private static void parseObject(Map<Long, List<KvEntry>> result, long systemTs, JsonElement jsonObject) {
+        JsonObject jo = jsonObject.getAsJsonObject();
+        if (jo.has("ts") && jo.has("values")) {
+            parseWithTs(result, jo);
+        } else {
+            parseWithoutTs(result, systemTs, jo);
+        }
+    }
+
+    private static void parseWithoutTs(Map<Long, List<KvEntry>> result, long systemTs, JsonObject jo) {
+        for (KvEntry entry : parseValues(jo)) {
+            result.computeIfAbsent(systemTs, tmp -> new ArrayList<>()).add(entry);
+        }
+    }
+
+    public static void parseWithTs(Map<Long, List<KvEntry>> result, JsonObject jo) {
+        long ts = jo.get("ts").getAsLong();
+        JsonObject valuesObject = jo.get("values").getAsJsonObject();
+        for (KvEntry entry : parseValues(valuesObject)) {
+            result.computeIfAbsent(ts, tmp -> new ArrayList<>()).add(entry);
+        }
+    }
+
+
+}
diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java
new file mode 100644
index 0000000..265dacb
--- /dev/null
+++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java
@@ -0,0 +1,164 @@
+/**
+ * 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.transport.service;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.transport.SessionMsgListener;
+import org.thingsboard.server.common.transport.TransportService;
+import org.thingsboard.server.common.transport.TransportServiceCallback;
+import org.thingsboard.server.gen.transport.TransportProtos;
+
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Created by ashvayka on 17.10.18.
+ */
+@Slf4j
+public abstract class AbstractTransportService implements TransportService {
+
+    @Value("${transport.rate_limits.enabled}")
+    private boolean rateLimitEnabled;
+    @Value("${transport.rate_limits.tenant}")
+    private String perTenantLimitsConf;
+    @Value("${transport.rate_limits.tenant}")
+    private String perDevicesLimitsConf;
+
+    protected ScheduledExecutorService schedulerExecutor;
+    protected ExecutorService transportCallbackExecutor;
+    private ConcurrentMap<UUID, SessionMetaData> sessions = new ConcurrentHashMap<>();
+
+    //TODO: Implement cleanup of this maps.
+    private ConcurrentMap<TenantId, TbTransportRateLimits> perTenantLimits = new ConcurrentHashMap<>();
+    private ConcurrentMap<DeviceId, TbTransportRateLimits> perDeviceLimits = new ConcurrentHashMap<>();
+
+    @Override
+    public void registerAsyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener) {
+        sessions.putIfAbsent(toId(sessionInfo), new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listener));
+        //TODO: monitor sessions periodically: PING REQ/RESP, etc.
+    }
+
+    @Override
+    public void registerSyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout) {
+        sessions.putIfAbsent(toId(sessionInfo), new SessionMetaData(sessionInfo, TransportProtos.SessionType.SYNC, listener));
+        schedulerExecutor.schedule(() -> {
+            listener.onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto.getDefaultInstance());
+            deregisterSession(sessionInfo);
+        }, timeout, TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    public void deregisterSession(TransportProtos.SessionInfoProto sessionInfo) {
+        sessions.remove(toId(sessionInfo));
+    }
+
+    @Override
+    public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, TransportServiceCallback<Void> callback) {
+        if (!rateLimitEnabled) {
+            return true;
+        }
+        TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB()));
+        TbTransportRateLimits rateLimits = perTenantLimits.computeIfAbsent(tenantId, id -> new TbTransportRateLimits(perTenantLimitsConf));
+        if (!rateLimits.tryConsume()) {
+            if (callback != null) {
+                callback.onError(new TbRateLimitsException(EntityType.TENANT));
+            }
+            return false;
+        }
+        DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
+        rateLimits = perDeviceLimits.computeIfAbsent(deviceId, id -> new TbTransportRateLimits(perDevicesLimitsConf));
+        if (!rateLimits.tryConsume()) {
+            if (callback != null) {
+                callback.onError(new TbRateLimitsException(EntityType.DEVICE));
+            }
+            return false;
+        }
+        return true;
+    }
+
+    protected void processToTransportMsg(TransportProtos.DeviceActorToTransportMsg toSessionMsg) {
+        UUID sessionId = new UUID(toSessionMsg.getSessionIdMSB(), toSessionMsg.getSessionIdLSB());
+        SessionMetaData md = sessions.get(sessionId);
+        if (md != null) {
+            SessionMsgListener listener = md.getListener();
+            transportCallbackExecutor.submit(() -> {
+                if (toSessionMsg.hasGetAttributesResponse()) {
+                    listener.onGetAttributesResponse(toSessionMsg.getGetAttributesResponse());
+                }
+                if (toSessionMsg.hasAttributeUpdateNotification()) {
+                    listener.onAttributeUpdate(toSessionMsg.getAttributeUpdateNotification());
+                }
+                if (toSessionMsg.hasSessionCloseNotification()) {
+                    listener.onRemoteSessionCloseCommand(toSessionMsg.getSessionCloseNotification());
+                }
+                if (toSessionMsg.hasToDeviceRequest()) {
+                    listener.onToDeviceRpcRequest(toSessionMsg.getToDeviceRequest());
+                }
+                if (toSessionMsg.hasToServerResponse()) {
+                    listener.onToServerRpcResponse(toSessionMsg.getToServerResponse());
+                }
+            });
+            if (md.getSessionType() == TransportProtos.SessionType.SYNC) {
+                deregisterSession(md.getSessionInfo());
+            }
+        } else {
+            //TODO: should we notify the device actor about missed session?
+            log.debug("[{}] Missing session.", sessionId);
+        }
+    }
+
+    protected UUID toId(TransportProtos.SessionInfoProto sessionInfo) {
+        return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB());
+    }
+
+    String getRoutingKey(TransportProtos.SessionInfoProto sessionInfo) {
+        return new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()).toString();
+    }
+
+    public void init() {
+        if (rateLimitEnabled) {
+            //Just checking the configuration parameters
+            new TbTransportRateLimits(perTenantLimitsConf);
+            new TbTransportRateLimits(perDevicesLimitsConf);
+        }
+        this.schedulerExecutor = Executors.newSingleThreadScheduledExecutor();
+        this.transportCallbackExecutor = new ThreadPoolExecutor(0, 20, 60L, TimeUnit.SECONDS, new SynchronousQueue<>());
+    }
+
+    public void destroy() {
+        if (rateLimitEnabled) {
+            perTenantLimits.clear();
+            perDeviceLimits.clear();
+        }
+        if (schedulerExecutor != null) {
+            schedulerExecutor.shutdownNow();
+        }
+        if (transportCallbackExecutor != null) {
+            transportCallbackExecutor.shutdownNow();
+        }
+    }
+}
diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/RemoteTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/RemoteTransportService.java
new file mode 100644
index 0000000..6774942
--- /dev/null
+++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/RemoteTransportService.java
@@ -0,0 +1,331 @@
+/**
+ * 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.transport.service;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.kafka.clients.admin.CreateTopicsResult;
+import org.apache.kafka.clients.admin.NewTopic;
+import org.apache.kafka.clients.consumer.ConsumerRecords;
+import org.apache.kafka.clients.producer.Callback;
+import org.apache.kafka.clients.producer.RecordMetadata;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.common.transport.SessionMsgListener;
+import org.thingsboard.server.common.transport.TransportService;
+import org.thingsboard.server.common.transport.TransportServiceCallback;
+import org.thingsboard.server.gen.transport.TransportProtos;
+import org.thingsboard.server.gen.transport.TransportProtos.*;
+import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
+import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
+import org.thingsboard.server.kafka.*;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.time.Duration;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Created by ashvayka on 05.10.18.
+ */
+@ConditionalOnExpression("'${transport.type:null}'=='null'")
+@Service
+@Slf4j
+public class RemoteTransportService extends AbstractTransportService {
+
+    @Value("${kafka.rule_engine.topic}")
+    private String ruleEngineTopic;
+    @Value("${kafka.notifications.topic}")
+    private String notificationsTopic;
+    @Value("${kafka.notifications.poll_interval}")
+    private int notificationsPollDuration;
+    @Value("${kafka.notifications.auto_commit_interval}")
+    private int notificationsAutoCommitInterval;
+    @Value("${kafka.transport_api.requests_topic}")
+    private String transportApiRequestsTopic;
+    @Value("${kafka.transport_api.responses_topic}")
+    private String transportApiResponsesTopic;
+    @Value("${kafka.transport_api.max_pending_requests}")
+    private long maxPendingRequests;
+    @Value("${kafka.transport_api.max_requests_timeout}")
+    private long maxRequestsTimeout;
+    @Value("${kafka.transport_api.response_poll_interval}")
+    private int responsePollDuration;
+    @Value("${kafka.transport_api.response_auto_commit_interval}")
+    private int autoCommitInterval;
+
+    @Autowired
+    private TbKafkaSettings kafkaSettings;
+    //We use this to get the node id. We should replace this with a component that provides the node id.
+    @Autowired
+    private TbNodeIdProvider nodeIdProvider;
+
+    private TbKafkaRequestTemplate<TransportApiRequestMsg, TransportApiResponseMsg> transportApiTemplate;
+    private TBKafkaProducerTemplate<ToRuleEngineMsg> ruleEngineProducer;
+    private TBKafkaConsumerTemplate<ToTransportMsg> mainConsumer;
+
+    private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor();
+
+    private volatile boolean stopped = false;
+
+    @PostConstruct
+    public void init() {
+        super.init();
+
+        TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder<TransportApiRequestMsg> requestBuilder = TBKafkaProducerTemplate.builder();
+        requestBuilder.settings(kafkaSettings);
+        requestBuilder.defaultTopic(transportApiRequestsTopic);
+        requestBuilder.encoder(new TransportApiRequestEncoder());
+
+        TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder<TransportApiResponseMsg> responseBuilder = TBKafkaConsumerTemplate.builder();
+        responseBuilder.settings(kafkaSettings);
+        responseBuilder.topic(transportApiResponsesTopic + "." + nodeIdProvider.getNodeId());
+        responseBuilder.clientId("transport-api-client-" + nodeIdProvider.getNodeId());
+        responseBuilder.groupId("transport-api-client");
+        responseBuilder.autoCommit(true);
+        responseBuilder.autoCommitIntervalMs(autoCommitInterval);
+        responseBuilder.decoder(new TransportApiResponseDecoder());
+
+        TbKafkaRequestTemplate.TbKafkaRequestTemplateBuilder
+                <TransportApiRequestMsg, TransportApiResponseMsg> builder = TbKafkaRequestTemplate.builder();
+        builder.requestTemplate(requestBuilder.build());
+        builder.responseTemplate(responseBuilder.build());
+        builder.maxPendingRequests(maxPendingRequests);
+        builder.maxRequestTimeout(maxRequestsTimeout);
+        builder.pollInterval(responsePollDuration);
+        transportApiTemplate = builder.build();
+        transportApiTemplate.init();
+
+        TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder<ToRuleEngineMsg> ruleEngineProducerBuilder = TBKafkaProducerTemplate.builder();
+        ruleEngineProducerBuilder.settings(kafkaSettings);
+        ruleEngineProducerBuilder.defaultTopic(ruleEngineTopic);
+        ruleEngineProducerBuilder.encoder(new ToRuleEngineMsgEncoder());
+        ruleEngineProducer = ruleEngineProducerBuilder.build();
+        ruleEngineProducer.init();
+
+        String notificationsTopicName = notificationsTopic + "." + nodeIdProvider.getNodeId();
+
+        try {
+            TBKafkaAdmin admin = new TBKafkaAdmin(kafkaSettings);
+            CreateTopicsResult result = admin.createTopic(new NewTopic(notificationsTopicName, 1, (short) 1));
+            result.all().get();
+        } catch (Exception e) {
+            log.trace("Failed to create topic: {}", e.getMessage(), e);
+        }
+
+        TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder<ToTransportMsg> mainConsumerBuilder = TBKafkaConsumerTemplate.builder();
+        mainConsumerBuilder.settings(kafkaSettings);
+        mainConsumerBuilder.topic(notificationsTopicName);
+        mainConsumerBuilder.clientId("transport-" + nodeIdProvider.getNodeId());
+        mainConsumerBuilder.groupId("transport");
+        mainConsumerBuilder.autoCommit(true);
+        mainConsumerBuilder.autoCommitIntervalMs(notificationsAutoCommitInterval);
+        mainConsumerBuilder.decoder(new ToTransportMsgResponseDecoder());
+        mainConsumer = mainConsumerBuilder.build();
+        mainConsumer.subscribe();
+
+        mainConsumerExecutor.execute(() -> {
+            while (!stopped) {
+                try {
+                    ConsumerRecords<String, byte[]> records = mainConsumer.poll(Duration.ofMillis(notificationsPollDuration));
+                    records.forEach(record -> {
+                        try {
+                            ToTransportMsg toTransportMsg = mainConsumer.decode(record);
+                            if (toTransportMsg.hasToDeviceSessionMsg()) {
+                                processToTransportMsg(toTransportMsg.getToDeviceSessionMsg());
+                            }
+                        } catch (Throwable e) {
+                            log.warn("Failed to process the notification.", e);
+                        }
+                    });
+                } catch (Exception e) {
+                    log.warn("Failed to obtain messages from queue.", e);
+                    try {
+                        Thread.sleep(notificationsPollDuration);
+                    } catch (InterruptedException e2) {
+                        log.trace("Failed to wait until the server has capacity to handle new requests", e2);
+                    }
+                }
+            }
+        });
+    }
+
+    @PreDestroy
+    public void destroy() {
+        super.destroy();
+        stopped = true;
+        if (transportApiTemplate != null) {
+            transportApiTemplate.stop();
+        }
+        if (mainConsumer != null) {
+            mainConsumer.unsubscribe();
+        }
+        if (mainConsumerExecutor != null) {
+            mainConsumerExecutor.shutdownNow();
+        }
+    }
+
+    @Override
+    public void process(ValidateDeviceTokenRequestMsg msg, TransportServiceCallback<ValidateDeviceCredentialsResponseMsg> callback) {
+        AsyncCallbackTemplate.withCallback(transportApiTemplate.post(msg.getToken(),
+                TransportApiRequestMsg.newBuilder().setValidateTokenRequestMsg(msg).build()),
+                response -> callback.onSuccess(response.getValidateTokenResponseMsg()), callback::onError, transportCallbackExecutor);
+    }
+
+    @Override
+    public void process(ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback<ValidateDeviceCredentialsResponseMsg> callback) {
+        AsyncCallbackTemplate.withCallback(transportApiTemplate.post(msg.getHash(),
+                TransportApiRequestMsg.newBuilder().setValidateX509CertRequestMsg(msg).build()),
+                response -> callback.onSuccess(response.getValidateTokenResponseMsg()), callback::onError, transportCallbackExecutor);
+    }
+
+    @Override
+    public void process(GetOrCreateDeviceFromGatewayRequestMsg msg, TransportServiceCallback<GetOrCreateDeviceFromGatewayResponseMsg> callback) {
+        AsyncCallbackTemplate.withCallback(transportApiTemplate.post(msg.getDeviceName(),
+                TransportApiRequestMsg.newBuilder().setGetOrCreateDeviceRequestMsg(msg).build()),
+                response -> callback.onSuccess(response.getGetOrCreateDeviceResponseMsg()), callback::onError, transportCallbackExecutor);
+    }
+
+    @Override
+    public void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback<Void> callback) {
+        if (checkLimits(sessionInfo, callback)) {
+            ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
+                    TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
+                            .setSessionEvent(msg).build()
+            ).build();
+            send(sessionInfo, toRuleEngineMsg, callback);
+        }
+    }
+
+    @Override
+    public void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback<Void> callback) {
+        if (checkLimits(sessionInfo, callback)) {
+            ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
+                    TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
+                            .setPostTelemetry(msg).build()
+            ).build();
+            send(sessionInfo, toRuleEngineMsg, callback);
+        }
+    }
+
+    @Override
+    public void process(SessionInfoProto sessionInfo, PostAttributeMsg msg, TransportServiceCallback<Void> callback) {
+        if (checkLimits(sessionInfo, callback)) {
+            ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
+                    TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
+                            .setPostAttributes(msg).build()
+            ).build();
+            send(sessionInfo, toRuleEngineMsg, callback);
+        }
+    }
+
+    @Override
+    public void process(SessionInfoProto sessionInfo, GetAttributeRequestMsg msg, TransportServiceCallback<Void> callback) {
+        if (checkLimits(sessionInfo, callback)) {
+            ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
+                    TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
+                            .setGetAttributes(msg).build()
+            ).build();
+            send(sessionInfo, toRuleEngineMsg, callback);
+        }
+    }
+
+    @Override
+    public void process(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback<Void> callback) {
+        if (checkLimits(sessionInfo, callback)) {
+            ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
+                    TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
+                            .setSubscribeToAttributes(msg).build()
+            ).build();
+            send(sessionInfo, toRuleEngineMsg, callback);
+        }
+    }
+
+    @Override
+    public void process(SessionInfoProto sessionInfo, SubscribeToRPCMsg msg, TransportServiceCallback<Void> callback) {
+        if (checkLimits(sessionInfo, callback)) {
+            ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
+                    TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
+                            .setSubscribeToRPC(msg).build()
+            ).build();
+            send(sessionInfo, toRuleEngineMsg, callback);
+        }
+    }
+
+    @Override
+    public void process(SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg msg, TransportServiceCallback<Void> callback) {
+        if (checkLimits(sessionInfo, callback)) {
+            ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
+                    TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
+                            .setToDeviceRPCCallResponse(msg).build()
+            ).build();
+            send(sessionInfo, toRuleEngineMsg, callback);
+        }
+    }
+
+    @Override
+    public void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback<Void> callback) {
+        if (checkLimits(sessionInfo, callback)) {
+            ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
+                    TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
+                            .setToServerRPCCallRequest(msg).build()
+            ).build();
+            send(sessionInfo, toRuleEngineMsg, callback);
+        }
+    }
+
+    private static class TransportCallbackAdaptor implements Callback {
+        private final TransportServiceCallback<Void> callback;
+
+        TransportCallbackAdaptor(TransportServiceCallback<Void> callback) {
+            this.callback = callback;
+        }
+
+        @Override
+        public void onCompletion(RecordMetadata metadata, Exception exception) {
+            if (exception == null) {
+                if (callback != null) {
+                    callback.onSuccess(null);
+                }
+            } else {
+                if (callback != null) {
+                    callback.onError(exception);
+                }
+            }
+        }
+    }
+
+    private void send(SessionInfoProto sessionInfo, ToRuleEngineMsg toRuleEngineMsg, TransportServiceCallback<Void> callback) {
+        ruleEngineProducer.send(getRoutingKey(sessionInfo), toRuleEngineMsg, new TransportCallbackAdaptor(callback));
+    }
+}
diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TbTransportRateLimits.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TbTransportRateLimits.java
new file mode 100644
index 0000000..d598734
--- /dev/null
+++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TbTransportRateLimits.java
@@ -0,0 +1,53 @@
+/**
+ * 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.transport.service;
+
+import io.github.bucket4j.Bandwidth;
+import io.github.bucket4j.Bucket4j;
+import io.github.bucket4j.local.LocalBucket;
+import io.github.bucket4j.local.LocalBucketBuilder;
+
+import java.time.Duration;
+
+/**
+ * Created by ashvayka on 22.10.18.
+ */
+class TbTransportRateLimits {
+    private final LocalBucket bucket;
+
+    public TbTransportRateLimits(String limitsConfiguration) {
+        LocalBucketBuilder builder = Bucket4j.builder();
+        boolean initialized = false;
+        for (String limitSrc : limitsConfiguration.split(",")) {
+            long capacity = Long.parseLong(limitSrc.split(":")[0]);
+            long duration = Long.parseLong(limitSrc.split(":")[1]);
+            builder.addLimit(Bandwidth.simple(capacity, Duration.ofSeconds(duration)));
+            initialized = true;
+        }
+        if (initialized) {
+            bucket = builder.build();
+        } else {
+            throw new IllegalArgumentException("Failed to parse rate limits configuration: " + limitsConfiguration);
+        }
+
+
+    }
+
+    boolean tryConsume() {
+        return bucket.tryConsume(1);
+    }
+
+}
diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java
new file mode 100644
index 0000000..ab42982
--- /dev/null
+++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.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.server.common.transport;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.Data;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.thingsboard.server.common.transport.quota.host.HostRequestsQuotaService;
+import org.thingsboard.server.kafka.TbNodeIdProvider;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Created by ashvayka on 15.10.18.
+ */
+@Slf4j
+@Data
+public class TransportContext {
+
+    protected final ObjectMapper mapper = new ObjectMapper();
+
+    @Autowired
+    private TransportService transportService;
+
+    @Autowired
+    private TbNodeIdProvider nodeIdProvider;
+
+    @Autowired(required = false)
+    private HostRequestsQuotaService quotaService;
+
+    @Getter
+    private ExecutorService executor;
+
+    @PostConstruct
+    public void init() {
+        executor = Executors.newCachedThreadPool();
+    }
+
+    @PreDestroy
+    public void stop() {
+        if (executor != null) {
+            executor.shutdownNow();
+        }
+    }
+
+    public String getNodeId() {
+        return nodeIdProvider.getNodeId();
+    }
+
+}
diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
new file mode 100644
index 0000000..a47438f
--- /dev/null
+++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
@@ -0,0 +1,70 @@
+/**
+ * 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.transport;
+
+import org.thingsboard.server.gen.transport.TransportProtos;
+import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
+import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
+
+/**
+ * Created by ashvayka on 04.10.18.
+ */
+public interface TransportService {
+
+    void process(ValidateDeviceTokenRequestMsg msg,
+                 TransportServiceCallback<ValidateDeviceCredentialsResponseMsg> callback);
+
+    void process(ValidateDeviceX509CertRequestMsg msg,
+                 TransportServiceCallback<ValidateDeviceCredentialsResponseMsg> callback);
+
+    void process(TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg msg,
+                 TransportServiceCallback<TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg> callback);
+
+    boolean checkLimits(SessionInfoProto sessionInfo, TransportServiceCallback<Void> callback);
+
+    void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback<Void> callback);
+
+    void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback<Void> callback);
+
+    void process(SessionInfoProto sessionInfo, PostAttributeMsg msg, TransportServiceCallback<Void> callback);
+
+    void process(SessionInfoProto sessionInfo, GetAttributeRequestMsg msg, TransportServiceCallback<Void> callback);
+
+    void process(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback<Void> callback);
+
+    void process(SessionInfoProto sessionInfo, SubscribeToRPCMsg msg, TransportServiceCallback<Void> callback);
+
+    void process(SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg msg, TransportServiceCallback<Void> callback);
+
+    void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback<Void> callback);
+
+    void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener);
+
+    void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout);
+
+    void deregisterSession(SessionInfoProto sessionInfo);
+
+}
diff --git a/common/transport/transport-api/src/main/proto/transport.proto b/common/transport/transport-api/src/main/proto/transport.proto
new file mode 100644
index 0000000..0e36dab
--- /dev/null
+++ b/common/transport/transport-api/src/main/proto/transport.proto
@@ -0,0 +1,217 @@
+/**
+ * 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.
+ */
+syntax = "proto3";
+package transport;
+
+option java_package = "org.thingsboard.server.gen.transport";
+option java_outer_classname = "TransportProtos";
+
+/**
+ * Data Structures;
+ */
+message SessionInfoProto {
+  string nodeId = 1;
+  int64 sessionIdMSB = 2;
+  int64 sessionIdLSB = 3;
+  int64 tenantIdMSB = 4;
+  int64 tenantIdLSB = 5;
+  int64 deviceIdMSB = 6;
+  int64 deviceIdLSB = 7;
+}
+
+enum SessionEvent {
+  OPEN = 0;
+  CLOSED = 1;
+}
+
+enum SessionType {
+  SYNC = 0;
+  ASYNC = 1;
+}
+
+enum KeyValueType {
+  BOOLEAN_V = 0;
+  LONG_V = 1;
+  DOUBLE_V = 2;
+  STRING_V = 3;
+}
+
+message KeyValueProto {
+  string key = 1;
+  KeyValueType type = 2;
+  bool bool_v = 3;
+  int64 long_v = 4;
+  double double_v = 5;
+  string string_v = 6;
+}
+
+message TsKvProto {
+  int64 ts = 1;
+  KeyValueProto kv = 2;
+}
+
+message TsKvListProto {
+  int64 ts = 1;
+  repeated KeyValueProto kv = 2;
+}
+
+message DeviceInfoProto {
+  int64 tenantIdMSB = 1;
+  int64 tenantIdLSB = 2;
+  int64 deviceIdMSB = 3;
+  int64 deviceIdLSB = 4;
+  string deviceName = 5;
+  string deviceType = 6;
+  string additionalInfo = 7;
+}
+
+/**
+ * Messages that use Data Structures;
+ */
+message SessionEventMsg {
+  SessionType sessionType = 1;
+  SessionEvent event = 2;
+}
+
+message PostTelemetryMsg {
+  repeated TsKvListProto tsKvList = 1;
+}
+
+message PostAttributeMsg {
+  repeated KeyValueProto kv = 1;
+}
+
+message GetAttributeRequestMsg {
+  int32 requestId = 1;
+  repeated string clientAttributeNames = 2;
+  repeated string sharedAttributeNames = 3;
+}
+
+message GetAttributeResponseMsg {
+  int32 requestId = 1;
+  repeated TsKvProto clientAttributeList = 2;
+  repeated TsKvProto sharedAttributeList = 3;
+  repeated string deletedAttributeKeys = 4;
+  string error = 5;
+}
+
+message AttributeUpdateNotificationMsg {
+  repeated TsKvProto sharedUpdated = 1;
+  repeated string sharedDeleted = 2;
+}
+
+message ValidateDeviceTokenRequestMsg {
+  string token = 1;
+}
+
+message ValidateDeviceX509CertRequestMsg {
+  string hash = 1;
+}
+
+message ValidateDeviceCredentialsResponseMsg {
+  DeviceInfoProto deviceInfo = 1;
+  string credentialsBody = 2;
+}
+
+message GetOrCreateDeviceFromGatewayRequestMsg {
+  int64 gatewayIdMSB = 1;
+  int64 gatewayIdLSB = 2;
+  string deviceName = 3;
+  string deviceType = 4;
+}
+
+message GetOrCreateDeviceFromGatewayResponseMsg {
+  DeviceInfoProto deviceInfo = 1;
+}
+
+message SessionCloseNotificationProto {
+  string message = 1;
+}
+
+message SubscribeToAttributeUpdatesMsg {
+  bool unsubscribe = 1;
+}
+
+message SubscribeToRPCMsg {
+  bool unsubscribe = 1;
+}
+
+message ToDeviceRpcRequestMsg {
+  int32 requestId = 1;
+  string methodName = 2;
+  string params = 3;
+}
+
+message ToDeviceRpcResponseMsg {
+  int32 requestId = 1;
+  string payload = 2;
+}
+
+message ToServerRpcRequestMsg {
+  int32 requestId = 1;
+  string methodName = 2;
+  string params = 3;
+}
+
+message ToServerRpcResponseMsg {
+  int32 requestId = 1;
+  string payload = 2;
+  string error = 3;
+}
+
+message TransportToDeviceActorMsg {
+  SessionInfoProto sessionInfo = 1;
+  SessionEventMsg sessionEvent = 2;
+  PostTelemetryMsg postTelemetry = 3;
+  PostAttributeMsg postAttributes = 4;
+  GetAttributeRequestMsg getAttributes = 5;
+  SubscribeToAttributeUpdatesMsg subscribeToAttributes = 6;
+  SubscribeToRPCMsg subscribeToRPC = 7;
+  ToDeviceRpcResponseMsg toDeviceRPCCallResponse = 8;
+  ToServerRpcRequestMsg toServerRPCCallRequest = 9;
+}
+
+message DeviceActorToTransportMsg {
+   int64 sessionIdMSB = 1;
+   int64 sessionIdLSB = 2;
+   SessionCloseNotificationProto sessionCloseNotification = 3;
+   GetAttributeResponseMsg getAttributesResponse = 4;
+   AttributeUpdateNotificationMsg attributeUpdateNotification = 5;
+   ToDeviceRpcRequestMsg toDeviceRequest = 6;
+   ToServerRpcResponseMsg toServerResponse = 7;
+}
+
+/**
+ * Main messages;
+ */
+message ToRuleEngineMsg {
+  TransportToDeviceActorMsg toDeviceActorMsg = 1;
+}
+
+message ToTransportMsg {
+  DeviceActorToTransportMsg toDeviceSessionMsg = 1;
+}
+
+message TransportApiRequestMsg {
+   ValidateDeviceTokenRequestMsg validateTokenRequestMsg = 1;
+   ValidateDeviceX509CertRequestMsg validateX509CertRequestMsg = 2;
+   GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 3;
+}
+
+message TransportApiResponseMsg {
+   ValidateDeviceCredentialsResponseMsg validateTokenResponseMsg = 1;
+   GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2;
+}
\ No newline at end of file
diff --git a/dao/src/main/java/org/thingsboard/server/dao/cache/CaffeineCacheConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/cache/CaffeineCacheConfiguration.java
index 688dfb3..fc2868e 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/cache/CaffeineCacheConfiguration.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/cache/CaffeineCacheConfiguration.java
@@ -18,6 +18,7 @@ package org.thingsboard.server.dao.cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.github.benmanes.caffeine.cache.RemovalCause;
 import com.github.benmanes.caffeine.cache.Ticker;
+import com.github.benmanes.caffeine.cache.Weigher;
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -31,6 +32,7 @@ import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
@@ -64,8 +66,9 @@ public class CaffeineCacheConfiguration {
     private CaffeineCache buildCache(String name, CacheSpecs cacheSpec) {
         final Caffeine<Object, Object> caffeineBuilder
                 = Caffeine.newBuilder()
+                .weigher(collectionSafeWeigher())
+                .maximumWeight(cacheSpec.getMaxSize())
                 .expireAfterWrite(cacheSpec.getTimeToLiveInMinutes(), TimeUnit.MINUTES)
-                .maximumSize(cacheSpec.getMaxSize())
                 .ticker(ticker());
         return new CaffeineCache(name, caffeineBuilder.build());
     }
@@ -80,4 +83,12 @@ public class CaffeineCacheConfiguration {
         return new PreviousDeviceCredentialsIdKeyGenerator();
     }
 
+    private Weigher<? super Object, ? super Object> collectionSafeWeigher() {
+        return (Weigher<Object, Object>) (key, value) -> {
+            if(value instanceof Collection) {
+                return ((Collection) value).size();
+            }
+            return 1;
+        };
+    }
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
index 4219b06..4b25f2b 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
@@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.Device;
 import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.security.DeviceCredentials;
 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
-import org.thingsboard.server.dao.EncryptionUtil;
+import org.thingsboard.server.common.msg.EncryptionUtil;
 import org.thingsboard.server.dao.exception.DataValidationException;
 import org.thingsboard.server.dao.service.DataValidator;
 
diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
index 1b67ca0..33a2699 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
@@ -29,6 +29,7 @@ import org.thingsboard.server.dao.asset.AssetService;
 import org.thingsboard.server.dao.customer.CustomerService;
 import org.thingsboard.server.dao.dashboard.DashboardService;
 import org.thingsboard.server.dao.device.DeviceService;
+import org.thingsboard.server.dao.entityview.EntityViewService;
 import org.thingsboard.server.dao.rule.RuleChainService;
 import org.thingsboard.server.dao.tenant.TenantService;
 import org.thingsboard.server.dao.user.UserService;
@@ -47,6 +48,9 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
     private DeviceService deviceService;
 
     @Autowired
+    private EntityViewService entityViewService;
+
+    @Autowired
     private TenantService tenantService;
 
     @Autowired
@@ -81,6 +85,9 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
             case DEVICE:
                 hasName = deviceService.findDeviceByIdAsync(new DeviceId(entityId.getId()));
                 break;
+            case ENTITY_VIEW:
+                hasName = entityViewService.findEntityViewByIdAsync(new EntityViewId(entityId.getId()));
+                break;
             case TENANT:
                 hasName = tenantService.findTenantByIdAsync(new TenantId(entityId.getId()));
                 break;
diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java
index fe1a8d1..6ddc18d 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java
@@ -84,8 +84,7 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<Entit
     @Override
     public EntityView save(EntityView domain) {
         EntityView savedEntityView = super.save(domain);
-        EntitySubtype entitySubtype = new EntitySubtype(savedEntityView.getTenantId(), EntityType.ENTITY_VIEW,
-                savedEntityView.getId().getEntityType().toString());
+        EntitySubtype entitySubtype = new EntitySubtype(savedEntityView.getTenantId(), EntityType.ENTITY_VIEW, savedEntityView.getType());
         EntitySubtypeEntity entitySubtypeEntity = new EntitySubtypeEntity(entitySubtype);
         Statement saveStatement = cluster.getMapper(EntitySubtypeEntity.class).saveQuery(entitySubtypeEntity);
         executeWrite(saveStatement);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
index 6b4ffbc..2d94cc2 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
@@ -92,9 +92,6 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
     private CustomerDao customerDao;
 
     @Autowired
-    private AttributesService attributesService;
-
-    @Autowired
     private CacheManager cacheManager;
 
     @Caching(evict = {
@@ -105,24 +102,10 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
         log.trace("Executing save entity view [{}]", entityView);
         entityViewValidator.validate(entityView);
         EntityView savedEntityView = entityViewDao.save(entityView);
-
-        List<ListenableFuture<List<Void>>> futures = new ArrayList<>();
-        if (savedEntityView.getKeys() != null) {
-            futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.CLIENT_SCOPE, savedEntityView.getKeys().getAttributes().getCs()));
-            futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SERVER_SCOPE, savedEntityView.getKeys().getAttributes().getSs()));
-            futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SHARED_SCOPE, savedEntityView.getKeys().getAttributes().getSh()));
-        }
-        for (ListenableFuture<List<Void>> future : futures) {
-            try {
-                future.get();
-            } catch (InterruptedException | ExecutionException e) {
-                log.error("Failed to copy attributes to entity view", e);
-                throw new RuntimeException("Failed to copy attributes to entity view", e);
-            }
-        }
         return savedEntityView;
     }
 
+    @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}")
     @Override
     public EntityView assignEntityViewToCustomer(EntityViewId entityViewId, CustomerId customerId) {
         EntityView entityView = findEntityViewById(entityViewId);
@@ -294,36 +277,6 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
                 });
     }
 
-    private ListenableFuture<List<Void>> copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection<String> keys) {
-        if (keys != null && !keys.isEmpty()) {
-            ListenableFuture<List<AttributeKvEntry>> getAttrFuture = attributesService.find(entityView.getEntityId(), scope, keys);
-            return Futures.transform(getAttrFuture, attributeKvEntries -> {
-                List<AttributeKvEntry> filteredAttributes = new ArrayList<>();
-                if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) {
-                    filteredAttributes =
-                            attributeKvEntries.stream()
-                                    .filter(attributeKvEntry -> {
-                                        long startTime = entityView.getStartTimeMs();
-                                        long endTime = entityView.getEndTimeMs();
-                                        long lastUpdateTs = attributeKvEntry.getLastUpdateTs();
-                                        return startTime == 0 && endTime == 0 ||
-                                                (endTime == 0 && startTime < lastUpdateTs) ||
-                                                (startTime == 0 && endTime > lastUpdateTs)
-                                                ? true : startTime < lastUpdateTs && endTime > lastUpdateTs;
-                                    }).collect(Collectors.toList());
-                }
-                try {
-                    return attributesService.save(entityView.getId(), scope, filteredAttributes).get();
-                } catch (InterruptedException | ExecutionException e) {
-                    log.error("Failed to copy attributes to entity view", e);
-                    throw new RuntimeException("Failed to copy attributes to entity view", e);
-                }
-            });
-        } else {
-            return Futures.immediateFuture(null);
-        }
-    }
-
     private DataValidator<EntityView> entityViewValidator =
             new DataValidator<EntityView>() {
 
diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java
index 4a09bb7..4658cbe 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java
@@ -79,12 +79,16 @@ public class BaseTimeseriesService implements TimeseriesService {
         if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) {
             EntityView entityView = entityViewService.findEntityViewById((EntityViewId) entityId);
             List<String> filteredKeys = new ArrayList<>(keys);
-            if (!entityView.getKeys().getTimeseries().isEmpty()) {
+            if (entityView.getKeys() != null && entityView.getKeys().getTimeseries() != null &&
+                    !entityView.getKeys().getTimeseries().isEmpty()) {
                 filteredKeys.retainAll(entityView.getKeys().getTimeseries());
             }
             List<ReadTsKvQuery> queries =
                     filteredKeys.stream()
-                            .map(key -> new BaseReadTsKvQuery(key, entityView.getStartTimeMs(), entityView.getEndTimeMs(), 1, "ASC"))
+                            .map(key -> {
+                                long endTs = entityView.getEndTimeMs() != 0 ? entityView.getEndTimeMs() : Long.MAX_VALUE;
+                                return new BaseReadTsKvQuery(key, entityView.getStartTimeMs(), endTs, 1, "DESC");
+                            })
                             .collect(Collectors.toList());
 
             if (queries.size() > 0) {
@@ -100,7 +104,17 @@ public class BaseTimeseriesService implements TimeseriesService {
     @Override
     public ListenableFuture<List<TsKvEntry>> findAllLatest(EntityId entityId) {
         validate(entityId);
-        return timeseriesDao.findAllLatest(entityId);
+        if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) {
+            EntityView entityView = entityViewService.findEntityViewById((EntityViewId) entityId);
+            if (entityView.getKeys() != null && entityView.getKeys().getTimeseries() != null &&
+                    !entityView.getKeys().getTimeseries().isEmpty()) {
+                return findLatest(entityId, entityView.getKeys().getTimeseries());
+            } else {
+                return Futures.immediateFuture(new ArrayList<>());
+            }
+        } else {
+            return timeseriesDao.findAllLatest(entityId);
+        }
     }
 
     @Override
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
index 1e1f15d..f49c357 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
@@ -45,6 +45,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
 import org.thingsboard.server.dao.dashboard.DashboardService;
 import org.thingsboard.server.dao.device.DeviceCredentialsService;
 import org.thingsboard.server.dao.device.DeviceService;
+import org.thingsboard.server.dao.entityview.EntityViewService;
 import org.thingsboard.server.dao.event.EventService;
 import org.thingsboard.server.dao.relation.RelationService;
 import org.thingsboard.server.dao.rule.RuleChainService;
@@ -89,6 +90,9 @@ public abstract class AbstractServiceTest {
     protected AssetService assetService;
 
     @Autowired
+    protected EntityViewService entityViewService;
+
+    @Autowired
     protected DeviceCredentialsService deviceCredentialsService;
 
     @Autowired
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java
index 4528d9a..88f4d84 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java
@@ -17,9 +17,15 @@ package org.thingsboard.server.dao.service.timeseries;
 
 import com.datastax.driver.core.utils.UUIDs;
 import lombok.extern.slf4j.Slf4j;
+import org.junit.After;
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
+import org.thingsboard.server.common.data.EntityView;
+import org.thingsboard.server.common.data.Tenant;
 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.kv.Aggregation;
 import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery;
 import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
@@ -30,6 +36,7 @@ import org.thingsboard.server.common.data.kv.KvEntry;
 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.data.objects.TelemetryEntityView;
 import org.thingsboard.server.dao.service.AbstractServiceTest;
 
 import java.util.ArrayList;
@@ -61,6 +68,22 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
     KvEntry doubleKvEntry = new DoubleDataEntry(DOUBLE_KEY, Double.MAX_VALUE);
     KvEntry booleanKvEntry = new BooleanDataEntry(BOOLEAN_KEY, Boolean.TRUE);
 
+    private TenantId tenantId;
+
+    @Before
+    public void before() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("My tenant");
+        Tenant savedTenant = tenantService.saveTenant(tenant);
+        Assert.assertNotNull(savedTenant);
+        tenantId = savedTenant.getId();
+    }
+
+    @After
+    public void after() {
+        tenantService.deleteTenant(tenantId);
+    }
+
     @Test
     public void testFindAllLatest() throws Exception {
         DeviceId deviceId = new DeviceId(UUIDs.timeBased());
@@ -69,7 +92,15 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
         saveEntries(deviceId, TS - 1);
         saveEntries(deviceId, TS);
 
-        List<TsKvEntry> tsList = tsService.findAllLatest(deviceId).get();
+        testLatestTsAndVerify(deviceId);
+
+        EntityView entityView = saveAndCreateEntityView(deviceId, Arrays.asList(STRING_KEY, DOUBLE_KEY, LONG_KEY, BOOLEAN_KEY));
+
+        testLatestTsAndVerify(entityView.getId());
+    }
+
+    private void testLatestTsAndVerify(EntityId entityId) throws ExecutionException, InterruptedException {
+        List<TsKvEntry> tsList = tsService.findAllLatest(entityId).get();
 
         assertNotNull(tsList);
         assertEquals(4, tsList.size());
@@ -89,6 +120,18 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
         assertEquals(expected, tsList);
     }
 
+    private EntityView saveAndCreateEntityView(DeviceId deviceId, List<String> timeseries) {
+        EntityView entityView = new EntityView();
+        entityView.setName("entity_view_name");
+        entityView.setType("default");
+        entityView.setTenantId(tenantId);
+        TelemetryEntityView keys = new TelemetryEntityView();
+        keys.setTimeseries(timeseries);
+        entityView.setKeys(keys);
+        entityView.setEntityId(deviceId);
+        return entityViewService.saveEntityView(entityView);
+    }
+
     @Test
     public void testFindLatest() throws Exception {
         DeviceId deviceId = new DeviceId(UUIDs.timeBased());
@@ -100,6 +143,12 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
         List<TsKvEntry> entries = tsService.findLatest(deviceId, Collections.singleton(STRING_KEY)).get();
         Assert.assertEquals(1, entries.size());
         Assert.assertEquals(toTsEntry(TS, stringKvEntry), entries.get(0));
+
+        EntityView entityView = saveAndCreateEntityView(deviceId, Arrays.asList(STRING_KEY));
+
+        entries = tsService.findLatest(entityView.getId(), Collections.singleton(STRING_KEY)).get();
+        Assert.assertEquals(1, entries.size());
+        Assert.assertEquals(toTsEntry(TS, stringKvEntry), entries.get(0));
     }
 
     @Test
diff --git a/dao/src/test/resources/nosql-test.properties b/dao/src/test/resources/nosql-test.properties
index 06a92fa..7c3ec51 100644
--- a/dao/src/test/resources/nosql-test.properties
+++ b/dao/src/test/resources/nosql-test.properties
@@ -1,7 +1,2 @@
 database.entities.type=cassandra
 database.ts.type=cassandra
-
-cassandra.queue.partitioning=HOURS
-cassandra.queue.ack.ttl=3600
-cassandra.queue.msg.ttl=3600
-cassandra.queue.partitions.ttl=3600
\ No newline at end of file

msa/docker/.env 5(+4 -1)

diff --git a/msa/docker/.env b/msa/docker/.env
index 111ff1e..3d8faeb 100644
--- a/msa/docker/.env
+++ b/msa/docker/.env
@@ -4,7 +4,10 @@ DOCKER_REPO=local-maven-build
 JS_EXECUTOR_DOCKER_NAME=tb-js-executor
 TB_NODE_DOCKER_NAME=tb-node
 WEB_UI_DOCKER_NAME=tb-web-ui
+MQTT_TRANSPORT_DOCKER_NAME=tb-mqtt-transport
+HTTP_TRANSPORT_DOCKER_NAME=tb-http-transport
+COAP_TRANSPORT_DOCKER_NAME=tb-coap-transport
 
 TB_VERSION=2.2.0-SNAPSHOT
 
-KAFKA_TOPICS=js.eval.requests:100:1
+KAFKA_TOPICS=js.eval.requests:100:1:delete --config=retention.ms=60000 --config=retention.bytes=1073741824,tb.transport.api.requests:30:1:delete --config=retention.ms=60000 --config=retention.bytes=1073741824,tb.rule-engine:30:1
diff --git a/msa/docker/docker-compose.yml b/msa/docker/docker-compose.yml
index 93c2ab0..e3b56ae 100644
--- a/msa/docker/docker-compose.yml
+++ b/msa/docker/docker-compose.yml
@@ -36,6 +36,9 @@ services:
       KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE
       KAFKA_CREATE_TOPICS: "${KAFKA_TOPICS}"
       KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'false'
+      KAFKA_LOG_RETENTION_BYTES: 1073741824
+      KAFKA_LOG_RETENTION_MS: 300000
+      KAFKA_LOG_CLEANUP_POLICY: delete
     depends_on:
       - zookeeper
   tb-js-executor:
@@ -53,8 +56,6 @@ services:
     image: "${DOCKER_REPO}/${TB_NODE_DOCKER_NAME}:${TB_VERSION}"
     ports:
       - "8080"
-      - "1883:1883"
-      - "5683:5683/udp"
     logging:
       driver: "json-file"
       options:
@@ -72,6 +73,61 @@ services:
       - ./tb-node/log:/var/log/thingsboard
     depends_on:
       - kafka
+  tb-mqtt-transport1:
+    restart: always
+    image: "${DOCKER_REPO}/${MQTT_TRANSPORT_DOCKER_NAME}:${TB_VERSION}"
+    ports:
+      - "1883"
+    environment:
+      TB_KAFKA_SERVERS: kafka:9092
+    env_file:
+      - tb-mqtt-transport.env
+    depends_on:
+      - kafka
+  tb-mqtt-transport2:
+    restart: always
+    image: "${DOCKER_REPO}/${MQTT_TRANSPORT_DOCKER_NAME}:${TB_VERSION}"
+    ports:
+      - "1883"
+    environment:
+      TB_KAFKA_SERVERS: kafka:9092
+    env_file:
+      - tb-mqtt-transport.env
+    depends_on:
+      - kafka
+  tb-http-transport1:
+    restart: always
+    image: "${DOCKER_REPO}/${HTTP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}"
+    ports:
+      - "8081"
+    environment:
+      TB_KAFKA_SERVERS: kafka:9092
+    env_file:
+      - tb-http-transport.env
+    depends_on:
+      - kafka
+  tb-http-transport2:
+    restart: always
+    image: "${DOCKER_REPO}/${HTTP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}"
+    ports:
+      - "8081"
+    environment:
+      TB_KAFKA_SERVERS: kafka:9092
+    env_file:
+      - tb-http-transport.env
+    depends_on:
+      - kafka
+  tb-coap-transport:
+    restart: always
+    image: "${DOCKER_REPO}/${COAP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}"
+    ports:
+      - "5683:5683/udp"
+    environment:
+      TB_KAFKA_SERVERS: kafka:9092
+    env_file:
+      - tb-coap-transport.env
+    depends_on:
+      - kafka
   tb-web-ui1:
     restart: always
     image: "${DOCKER_REPO}/${WEB_UI_DOCKER_NAME}:${TB_VERSION}"
@@ -103,12 +159,18 @@ services:
     ports:
      - "80:80"
      - "443:443"
+     - "1883:1883"
      - "9999:9999"
     cap_add:
      - NET_ADMIN
     environment:
       HTTP_PORT: 80
       HTTPS_PORT: 443
+      MQTT_PORT: 1883
     links:
         - tb-web-ui1
         - tb-web-ui2
+        - tb-mqtt-transport1
+        - tb-mqtt-transport2
+        - tb-http-transport1
+        - tb-http-transport2
diff --git a/msa/docker/haproxy/config/haproxy.cfg b/msa/docker/haproxy/config/haproxy.cfg
index 5f17465..cb70552 100644
--- a/msa/docker/haproxy/config/haproxy.cfg
+++ b/msa/docker/haproxy/config/haproxy.cfg
@@ -1,6 +1,9 @@
 #HA Proxy Config
 global
- maxconn 4096
+ ulimit-n 500000
+ maxconn 99999
+ maxpipes 99999
+ tune.maxaccept 500
 
  log 127.0.0.1 local0
  log 127.0.0.1 local1 notice
@@ -13,8 +16,6 @@ global
 
 defaults
 
- option forwardfor
-
  log global
 
  mode http
@@ -30,23 +31,42 @@ listen stats
  stats uri /stats
  stats auth admin:admin@123
 
+listen mqtt-in
+ bind *:${MQTT_PORT}
+ mode tcp
+ option clitcpka # For TCP keep-alive
+ timeout client 3h
+ timeout server 3h
+ option tcplog
+ balance leastconn
+ server tbMqtt1 tb-mqtt-transport1:1883 check
+ server tbMqtt2 tb-mqtt-transport2:1883 check
+
 frontend http-in
  bind *:${HTTP_PORT}
 
+ option forwardfor
+
  reqadd X-Forwarded-Proto:\ http
 
  acl transport_http_acl path_beg /api/v1/
  acl letsencrypt_http_acl path_beg /.well-known/acme-challenge/
  redirect scheme https if !letsencrypt_http_acl !transport_http_acl
  use_backend letsencrypt_http if letsencrypt_http_acl
+ use_backend tb-http-backend if transport_http_acl
 
  default_backend tb-web-backend
 
 frontend https_in
   bind *:${HTTPS_PORT} ssl crt /usr/local/etc/haproxy/default.pem crt /usr/local/etc/haproxy/certs.d ciphers ECDHE-RSA-AES256-SHA:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM
 
+  option forwardfor
+
   reqadd X-Forwarded-Proto:\ https
 
+  acl transport_http_acl path_beg /api/v1/
+  use_backend tb-http-backend if transport_http_acl
+
   default_backend tb-web-backend
 
 backend letsencrypt_http
@@ -59,3 +79,10 @@ backend tb-web-backend
   server tbWeb1 tb-web-ui1:8080 check
   server tbWeb2 tb-web-ui2:8080 check
   http-request set-header X-Forwarded-Port %[dst_port]
+
+backend tb-http-backend
+  balance leastconn
+  option tcp-check
+  option log-health-checks
+  server tbHttp1 tb-http-transport1:8081 check
+  server tbHttp2 tb-http-transport2:8081 check
diff --git a/msa/docker/tb-coap-transport.env b/msa/docker/tb-coap-transport.env
new file mode 100644
index 0000000..b3331fe
--- /dev/null
+++ b/msa/docker/tb-coap-transport.env
@@ -0,0 +1,6 @@
+
+COAP_BIND_ADDRESS=0.0.0.0
+COAP_BIND_PORT=5683
+COAP_TIMEOUT=10000
+
+TB_KAFKA_SERVERS=localhost:9092
\ No newline at end of file
diff --git a/msa/docker/tb-http-transport.env b/msa/docker/tb-http-transport.env
new file mode 100644
index 0000000..33c4791
--- /dev/null
+++ b/msa/docker/tb-http-transport.env
@@ -0,0 +1,6 @@
+
+HTTP_BIND_ADDRESS=0.0.0.0
+HTTP_BIND_PORT=8081
+HTTP_REQUEST_TIMEOUT=60000
+
+TB_KAFKA_SERVERS=localhost:9092
\ No newline at end of file
diff --git a/msa/docker/tb-mqtt-transport.env b/msa/docker/tb-mqtt-transport.env
new file mode 100644
index 0000000..5ada3f7
--- /dev/null
+++ b/msa/docker/tb-mqtt-transport.env
@@ -0,0 +1,6 @@
+
+MQTT_BIND_ADDRESS=0.0.0.0
+MQTT_BIND_PORT=1883
+MQTT_TIMEOUT=10000
+
+TB_KAFKA_SERVERS=localhost:9092
\ No newline at end of file
diff --git a/msa/docker/tb-node.env b/msa/docker/tb-node.env
index 874bf75..abeccb1 100644
--- a/msa/docker/tb-node.env
+++ b/msa/docker/tb-node.env
@@ -1,8 +1,6 @@
 # ThingsBoard server configuration
-MQTT_BIND_ADDRESS=0.0.0.0
-MQTT_BIND_PORT=1883
-COAP_BIND_ADDRESS=0.0.0.0
-COAP_BIND_PORT=5683
+
+TRANSPORT_TYPE=remote
 
 # type of database to use: sql[DEFAULT] or cassandra
 DATABASE_TS_TYPE=sql

msa/pom.xml 1(+1 -0)

diff --git a/msa/pom.xml b/msa/pom.xml
index dd4c365..d357677 100644
--- a/msa/pom.xml
+++ b/msa/pom.xml
@@ -40,6 +40,7 @@
         <module>js-executor</module>
         <module>web-ui</module>
         <module>tb-node</module>
+        <module>transport</module>
         <module>integration-tests</module>
     </modules>
 
diff --git a/msa/transport/coap/docker/Dockerfile b/msa/transport/coap/docker/Dockerfile
new file mode 100644
index 0000000..dcaef3a
--- /dev/null
+++ b/msa/transport/coap/docker/Dockerfile
@@ -0,0 +1,31 @@
+#
+# 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.
+#
+
+FROM openjdk:8-jre
+
+COPY logback.xml ${pkg.name}.conf start-tb-coap-transport.sh ${pkg.name}.deb /tmp/
+
+RUN chmod a+x /tmp/*.sh \
+    && mv /tmp/start-tb-coap-transport.sh /usr/bin
+
+RUN dpkg -i /tmp/${pkg.name}.deb
+
+RUN update-rc.d ${pkg.name} disable
+
+RUN mv /tmp/logback.xml ${pkg.installFolder}/conf \
+    && mv /tmp/${pkg.name}.conf ${pkg.installFolder}/conf
+
+CMD ["start-tb-coap-transport.sh"]
diff --git a/msa/transport/coap/docker/logback.xml b/msa/transport/coap/docker/logback.xml
new file mode 100644
index 0000000..e9d8692
--- /dev/null
+++ b/msa/transport/coap/docker/logback.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+
+    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.
+
+-->
+<!DOCTYPE configuration>
+<configuration scan="true" scanPeriod="10 seconds">
+
+    <appender name="fileLogAppender"
+              class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>/var/log/${pkg.name}/${pkg.name}.log</file>
+        <rollingPolicy
+                class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>/var/log/${pkg.name}/${pkg.name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <maxFileSize>100MB</maxFileSize>
+            <maxHistory>30</maxHistory>
+            <totalSizeCap>3GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <logger name="org.thingsboard.server" level="INFO" />
+
+    <root level="INFO">
+        <appender-ref ref="fileLogAppender"/>
+        <appender-ref ref="STDOUT"/>
+    </root>
+
+</configuration>
\ No newline at end of file
diff --git a/msa/transport/coap/docker/start-tb-coap-transport.sh b/msa/transport/coap/docker/start-tb-coap-transport.sh
new file mode 100755
index 0000000..43d4602
--- /dev/null
+++ b/msa/transport/coap/docker/start-tb-coap-transport.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+#
+# 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.
+#
+
+CONF_FOLDER="${pkg.installFolder}/conf"
+jarfile=${pkg.installFolder}/bin/${pkg.name}.jar
+configfile=${pkg.name}.conf
+
+source "${CONF_FOLDER}/${configfile}"
+
+echo "Starting '${project.name}' ..."
+
+exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.coap.ThingsboardCoapTransportApplication \
+                    -Dspring.jpa.hibernate.ddl-auto=none \
+                    -Dlogging.config=${CONF_FOLDER}/logback.xml \
+                    org.springframework.boot.loader.PropertiesLauncher
diff --git a/msa/transport/coap/docker/tb-coap-transport.conf b/msa/transport/coap/docker/tb-coap-transport.conf
new file mode 100644
index 0000000..6d4c54a
--- /dev/null
+++ b/msa/transport/coap/docker/tb-coap-transport.conf
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+
+export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
+export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"
+export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
+export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"
+export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError"
+export LOG_FILENAME=${pkg.name}.out
+export LOADER_PATH=${pkg.installFolder}/conf
diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml
new file mode 100644
index 0000000..07c4e63
--- /dev/null
+++ b/msa/transport/coap/pom.xml
@@ -0,0 +1,137 @@
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.thingsboard.msa</groupId>
+        <version>2.2.0-SNAPSHOT</version>
+        <artifactId>transport</artifactId>
+    </parent>
+    <groupId>org.thingsboard.msa.transport</groupId>
+    <artifactId>coap</artifactId>
+    <packaging>pom</packaging>
+
+    <name>ThingsBoard COAP Transport Microservice</name>
+    <url>https://thingsboard.io</url>
+    <description>ThingsBoard COAP Transport Microservice</description>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <main.dir>${basedir}/../../..</main.dir>
+        <pkg.name>tb-coap-transport</pkg.name>
+        <docker.name>tb-coap-transport</docker.name>
+        <pkg.user>thingsboard</pkg.user>
+        <pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
+        <pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.thingsboard.transport</groupId>
+            <artifactId>coap</artifactId>
+            <version>${project.version}</version>
+            <classifier>deb</classifier>
+            <type>deb</type>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-tb-coap-transport-deb</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>copy</goal>
+                        </goals>
+                        <configuration>
+                            <artifactItems>
+                                <artifactItem>
+                                    <groupId>org.thingsboard.transport</groupId>
+                                    <artifactId>coap</artifactId>
+                                    <classifier>deb</classifier>
+                                    <type>deb</type>
+                                    <destFileName>${pkg.name}.deb</destFileName>
+                                    <outputDirectory>${project.build.directory}</outputDirectory>
+                                </artifactItem>
+                            </artifactItems>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-docker-config</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>docker</directory>
+                                    <filtering>true</filtering>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>com.spotify</groupId>
+                <artifactId>dockerfile-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>build-docker-image</id>
+                        <phase>pre-integration-test</phase>
+                        <goals>
+                            <goal>build</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <skip>${dockerfile.skip}</skip>
+                    <repository>${docker.repo}/${docker.name}</repository>
+                    <tag>${project.version}</tag>
+                    <verbose>true</verbose>
+                    <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
+                    <contextDirectory>${project.build.directory}</contextDirectory>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <repositories>
+        <repository>
+            <id>jenkins</id>
+            <name>Jenkins Repository</name>
+            <url>http://repo.jenkins-ci.org/releases</url>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </repository>
+    </repositories>
+</project>
diff --git a/msa/transport/http/docker/Dockerfile b/msa/transport/http/docker/Dockerfile
new file mode 100644
index 0000000..212047f
--- /dev/null
+++ b/msa/transport/http/docker/Dockerfile
@@ -0,0 +1,31 @@
+#
+# 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.
+#
+
+FROM openjdk:8-jre
+
+COPY logback.xml ${pkg.name}.conf start-tb-http-transport.sh ${pkg.name}.deb /tmp/
+
+RUN chmod a+x /tmp/*.sh \
+    && mv /tmp/start-tb-http-transport.sh /usr/bin
+
+RUN dpkg -i /tmp/${pkg.name}.deb
+
+RUN update-rc.d ${pkg.name} disable
+
+RUN mv /tmp/logback.xml ${pkg.installFolder}/conf \
+    && mv /tmp/${pkg.name}.conf ${pkg.installFolder}/conf
+
+CMD ["start-tb-http-transport.sh"]
diff --git a/msa/transport/http/docker/logback.xml b/msa/transport/http/docker/logback.xml
new file mode 100644
index 0000000..e9d8692
--- /dev/null
+++ b/msa/transport/http/docker/logback.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+
+    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.
+
+-->
+<!DOCTYPE configuration>
+<configuration scan="true" scanPeriod="10 seconds">
+
+    <appender name="fileLogAppender"
+              class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>/var/log/${pkg.name}/${pkg.name}.log</file>
+        <rollingPolicy
+                class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>/var/log/${pkg.name}/${pkg.name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <maxFileSize>100MB</maxFileSize>
+            <maxHistory>30</maxHistory>
+            <totalSizeCap>3GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <logger name="org.thingsboard.server" level="INFO" />
+
+    <root level="INFO">
+        <appender-ref ref="fileLogAppender"/>
+        <appender-ref ref="STDOUT"/>
+    </root>
+
+</configuration>
\ No newline at end of file
diff --git a/msa/transport/http/docker/start-tb-http-transport.sh b/msa/transport/http/docker/start-tb-http-transport.sh
new file mode 100755
index 0000000..667988f
--- /dev/null
+++ b/msa/transport/http/docker/start-tb-http-transport.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+#
+# 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.
+#
+
+CONF_FOLDER="${pkg.installFolder}/conf"
+jarfile=${pkg.installFolder}/bin/${pkg.name}.jar
+configfile=${pkg.name}.conf
+
+source "${CONF_FOLDER}/${configfile}"
+
+echo "Starting '${project.name}' ..."
+
+exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.http.ThingsboardHttpTransportApplication \
+                    -Dspring.jpa.hibernate.ddl-auto=none \
+                    -Dlogging.config=${CONF_FOLDER}/logback.xml \
+                    org.springframework.boot.loader.PropertiesLauncher
diff --git a/msa/transport/http/docker/tb-http-transport.conf b/msa/transport/http/docker/tb-http-transport.conf
new file mode 100644
index 0000000..6d4c54a
--- /dev/null
+++ b/msa/transport/http/docker/tb-http-transport.conf
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+
+export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
+export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"
+export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
+export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"
+export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError"
+export LOG_FILENAME=${pkg.name}.out
+export LOADER_PATH=${pkg.installFolder}/conf
diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml
new file mode 100644
index 0000000..22eb299
--- /dev/null
+++ b/msa/transport/http/pom.xml
@@ -0,0 +1,137 @@
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.thingsboard.msa</groupId>
+        <version>2.2.0-SNAPSHOT</version>
+        <artifactId>transport</artifactId>
+    </parent>
+    <groupId>org.thingsboard.msa.transport</groupId>
+    <artifactId>http</artifactId>
+    <packaging>pom</packaging>
+
+    <name>ThingsBoard HTTP Transport Microservice</name>
+    <url>https://thingsboard.io</url>
+    <description>ThingsBoard HTTP Transport Microservice</description>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <main.dir>${basedir}/../../..</main.dir>
+        <pkg.name>tb-http-transport</pkg.name>
+        <docker.name>tb-http-transport</docker.name>
+        <pkg.user>thingsboard</pkg.user>
+        <pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
+        <pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.thingsboard.transport</groupId>
+            <artifactId>http</artifactId>
+            <version>${project.version}</version>
+            <classifier>deb</classifier>
+            <type>deb</type>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-tb-http-transport-deb</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>copy</goal>
+                        </goals>
+                        <configuration>
+                            <artifactItems>
+                                <artifactItem>
+                                    <groupId>org.thingsboard.transport</groupId>
+                                    <artifactId>http</artifactId>
+                                    <classifier>deb</classifier>
+                                    <type>deb</type>
+                                    <destFileName>${pkg.name}.deb</destFileName>
+                                    <outputDirectory>${project.build.directory}</outputDirectory>
+                                </artifactItem>
+                            </artifactItems>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-docker-config</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>docker</directory>
+                                    <filtering>true</filtering>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>com.spotify</groupId>
+                <artifactId>dockerfile-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>build-docker-image</id>
+                        <phase>pre-integration-test</phase>
+                        <goals>
+                            <goal>build</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <skip>${dockerfile.skip}</skip>
+                    <repository>${docker.repo}/${docker.name}</repository>
+                    <tag>${project.version}</tag>
+                    <verbose>true</verbose>
+                    <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
+                    <contextDirectory>${project.build.directory}</contextDirectory>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <repositories>
+        <repository>
+            <id>jenkins</id>
+            <name>Jenkins Repository</name>
+            <url>http://repo.jenkins-ci.org/releases</url>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </repository>
+    </repositories>
+</project>
diff --git a/msa/transport/mqtt/docker/Dockerfile b/msa/transport/mqtt/docker/Dockerfile
new file mode 100644
index 0000000..5827c65
--- /dev/null
+++ b/msa/transport/mqtt/docker/Dockerfile
@@ -0,0 +1,31 @@
+#
+# 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.
+#
+
+FROM openjdk:8-jre
+
+COPY logback.xml ${pkg.name}.conf start-tb-mqtt-transport.sh ${pkg.name}.deb /tmp/
+
+RUN chmod a+x /tmp/*.sh \
+    && mv /tmp/start-tb-mqtt-transport.sh /usr/bin
+
+RUN dpkg -i /tmp/${pkg.name}.deb
+
+RUN update-rc.d ${pkg.name} disable
+
+RUN mv /tmp/logback.xml ${pkg.installFolder}/conf \
+    && mv /tmp/${pkg.name}.conf ${pkg.installFolder}/conf
+
+CMD ["start-tb-mqtt-transport.sh"]
diff --git a/msa/transport/mqtt/docker/logback.xml b/msa/transport/mqtt/docker/logback.xml
new file mode 100644
index 0000000..e9d8692
--- /dev/null
+++ b/msa/transport/mqtt/docker/logback.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+
+    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.
+
+-->
+<!DOCTYPE configuration>
+<configuration scan="true" scanPeriod="10 seconds">
+
+    <appender name="fileLogAppender"
+              class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>/var/log/${pkg.name}/${pkg.name}.log</file>
+        <rollingPolicy
+                class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>/var/log/${pkg.name}/${pkg.name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <maxFileSize>100MB</maxFileSize>
+            <maxHistory>30</maxHistory>
+            <totalSizeCap>3GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <logger name="org.thingsboard.server" level="INFO" />
+
+    <root level="INFO">
+        <appender-ref ref="fileLogAppender"/>
+        <appender-ref ref="STDOUT"/>
+    </root>
+
+</configuration>
\ No newline at end of file
diff --git a/msa/transport/mqtt/docker/start-tb-mqtt-transport.sh b/msa/transport/mqtt/docker/start-tb-mqtt-transport.sh
new file mode 100755
index 0000000..8fe06d2
--- /dev/null
+++ b/msa/transport/mqtt/docker/start-tb-mqtt-transport.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+#
+# 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.
+#
+
+CONF_FOLDER="${pkg.installFolder}/conf"
+jarfile=${pkg.installFolder}/bin/${pkg.name}.jar
+configfile=${pkg.name}.conf
+
+source "${CONF_FOLDER}/${configfile}"
+
+echo "Starting '${project.name}' ..."
+
+exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.mqtt.ThingsboardMqttTransportApplication \
+                    -Dspring.jpa.hibernate.ddl-auto=none \
+                    -Dlogging.config=${CONF_FOLDER}/logback.xml \
+                    org.springframework.boot.loader.PropertiesLauncher
diff --git a/msa/transport/mqtt/docker/tb-mqtt-transport.conf b/msa/transport/mqtt/docker/tb-mqtt-transport.conf
new file mode 100644
index 0000000..6d4c54a
--- /dev/null
+++ b/msa/transport/mqtt/docker/tb-mqtt-transport.conf
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+
+export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
+export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"
+export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
+export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"
+export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError"
+export LOG_FILENAME=${pkg.name}.out
+export LOADER_PATH=${pkg.installFolder}/conf
diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml
new file mode 100644
index 0000000..975f9fc
--- /dev/null
+++ b/msa/transport/mqtt/pom.xml
@@ -0,0 +1,137 @@
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.thingsboard.msa</groupId>
+        <version>2.2.0-SNAPSHOT</version>
+        <artifactId>transport</artifactId>
+    </parent>
+    <groupId>org.thingsboard.msa.transport</groupId>
+    <artifactId>mqtt</artifactId>
+    <packaging>pom</packaging>
+
+    <name>ThingsBoard MQTT Transport Microservice</name>
+    <url>https://thingsboard.io</url>
+    <description>ThingsBoard MQTT Transport Microservice</description>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <main.dir>${basedir}/../../..</main.dir>
+        <pkg.name>tb-mqtt-transport</pkg.name>
+        <docker.name>tb-mqtt-transport</docker.name>
+        <pkg.user>thingsboard</pkg.user>
+        <pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
+        <pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.thingsboard.transport</groupId>
+            <artifactId>mqtt</artifactId>
+            <version>${project.version}</version>
+            <classifier>deb</classifier>
+            <type>deb</type>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-tb-mqtt-transport-deb</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>copy</goal>
+                        </goals>
+                        <configuration>
+                            <artifactItems>
+                                <artifactItem>
+                                    <groupId>org.thingsboard.transport</groupId>
+                                    <artifactId>mqtt</artifactId>
+                                    <classifier>deb</classifier>
+                                    <type>deb</type>
+                                    <destFileName>${pkg.name}.deb</destFileName>
+                                    <outputDirectory>${project.build.directory}</outputDirectory>
+                                </artifactItem>
+                            </artifactItems>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-docker-config</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>docker</directory>
+                                    <filtering>true</filtering>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>com.spotify</groupId>
+                <artifactId>dockerfile-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>build-docker-image</id>
+                        <phase>pre-integration-test</phase>
+                        <goals>
+                            <goal>build</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <skip>${dockerfile.skip}</skip>
+                    <repository>${docker.repo}/${docker.name}</repository>
+                    <tag>${project.version}</tag>
+                    <verbose>true</verbose>
+                    <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
+                    <contextDirectory>${project.build.directory}</contextDirectory>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <repositories>
+        <repository>
+            <id>jenkins</id>
+            <name>Jenkins Repository</name>
+            <url>http://repo.jenkins-ci.org/releases</url>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </repository>
+    </repositories>
+</project>
diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml
new file mode 100644
index 0000000..9f091e3
--- /dev/null
+++ b/msa/transport/pom.xml
@@ -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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.thingsboard</groupId>
+        <version>2.2.0-SNAPSHOT</version>
+        <artifactId>msa</artifactId>
+    </parent>
+    <groupId>org.thingsboard.msa</groupId>
+    <artifactId>transport</artifactId>
+    <packaging>pom</packaging>
+
+    <name>ThingsBoard Transport Microservices</name>
+    <url>https://thingsboard.io</url>
+
+    <properties>
+        <main.dir>${basedir}/../..</main.dir>
+    </properties>
+
+    <modules>
+        <module>mqtt</module>
+        <module>http</module>
+        <module>coap</module>
+    </modules>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>com.spotify</groupId>
+                    <artifactId>dockerfile-maven-plugin</artifactId>
+                    <version>1.4.5</version>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+    </build>
+
+</project>

pom.xml 25(+18 -7)

diff --git a/pom.xml b/pom.xml
index d6db123..387e0ed 100755
--- a/pom.xml
+++ b/pom.xml
@@ -50,7 +50,7 @@
         <commons-validator.version>1.5.0</commons-validator.version>
         <commons-io.version>2.5</commons-io.version>
         <commons-csv.version>1.4</commons-csv.version>
-        <jackson.version>2.8.8.1</jackson.version>
+        <jackson.version>2.8.11.1</jackson.version>
         <json-schema-validator.version>2.2.6</json-schema-validator.version>
         <scala.version>2.11</scala.version>
         <akka.version>2.4.2</akka.version>
@@ -82,6 +82,7 @@
         <elasticsearch.version>5.0.2</elasticsearch.version>
         <delight-nashorn-sandbox.version>0.1.14</delight-nashorn-sandbox.version>
         <kafka.version>2.0.0</kafka.version>
+        <bucket4j.version>4.1.1</bucket4j.version>
     </properties>
 
     <modules>
@@ -355,18 +356,23 @@
                 <version>${project.version}</version>
             </dependency>
             <dependency>
-                <groupId>org.thingsboard.transport</groupId>
-                <artifactId>http</artifactId>
+                <groupId>org.thingsboard.common.transport</groupId>
+                <artifactId>transport-api</artifactId>
                 <version>${project.version}</version>
             </dependency>
             <dependency>
-                <groupId>org.thingsboard.transport</groupId>
-                <artifactId>coap</artifactId>
+                <groupId>org.thingsboard.common.transport</groupId>
+                <artifactId>mqtt</artifactId>
                 <version>${project.version}</version>
             </dependency>
             <dependency>
-                <groupId>org.thingsboard.transport</groupId>
-                <artifactId>mqtt</artifactId>
+                <groupId>org.thingsboard.common.transport</groupId>
+                <artifactId>http</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.thingsboard.common.transport</groupId>
+                <artifactId>coap</artifactId>
                 <version>${project.version}</version>
             </dependency>
             <dependency>
@@ -773,6 +779,11 @@
                 <artifactId>delight-nashorn-sandbox</artifactId>
                 <version>${delight-nashorn-sandbox.version}</version>
             </dependency>
+            <dependency>
+                <groupId>com.github.vladimir-bukhtoyarov</groupId>
+                <artifactId>bucket4j-core</artifactId>
+                <version>${bucket4j.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 
diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml
index 6390929..c22ce78 100644
--- a/rule-engine/rule-engine-components/pom.xml
+++ b/rule-engine/rule-engine-components/pom.xml
@@ -45,8 +45,8 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>org.thingsboard.common</groupId>
-            <artifactId>transport</artifactId>
+            <groupId>org.thingsboard.common.transport</groupId>
+            <artifactId>transport-api</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -72,6 +72,10 @@
             <artifactId>guava</artifactId>
         </dependency>
         <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-web</artifactId>
             <scope>provided</scope>
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java
index 51386dc..459e35b 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java
@@ -17,7 +17,9 @@ package org.thingsboard.rule.engine.action;
 
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.ListenableFuture;
+import com.google.gson.JsonElement;
 import com.google.gson.JsonParser;
+import com.google.gson.JsonPrimitive;
 import lombok.extern.slf4j.Slf4j;
 import org.thingsboard.rule.engine.api.EmptyNodeConfiguration;
 import org.thingsboard.rule.engine.api.RuleNode;
@@ -25,7 +27,6 @@ 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.rule.engine.api.util.DonAsynchron;
 import org.thingsboard.rule.engine.api.util.TbNodeUtils;
 import org.thingsboard.server.common.data.DataConstants;
@@ -37,12 +38,11 @@ import org.thingsboard.server.common.msg.session.SessionMsgType;
 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
 
 import javax.annotation.Nullable;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
 
-import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE;
 import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
 
 @Slf4j
@@ -68,70 +68,88 @@ public class TbCopyAttributesToEntityViewNode implements TbNode {
     }
 
     @Override
-    public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
-        if (!msg.getMetaData().getData().isEmpty()) {
-            long now = System.currentTimeMillis();
-            String scope = msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()) ?
-                    DataConstants.CLIENT_SCOPE : msg.getMetaData().getValue("scope");
+    public void onMsg(TbContext ctx, TbMsg msg) {
+        if (DataConstants.ATTRIBUTES_UPDATED.equals(msg.getType()) ||
+                DataConstants.ATTRIBUTES_DELETED.equals(msg.getType()) ||
+                SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msg.getType())) {
+            if (!msg.getMetaData().getData().isEmpty()) {
+                long now = System.currentTimeMillis();
+                String scope = msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()) ?
+                        DataConstants.CLIENT_SCOPE : msg.getMetaData().getValue("scope");
 
-            ListenableFuture<List<EntityView>> entityViewsFuture =
-                    ctx.getEntityViewService().findEntityViewsByTenantIdAndEntityIdAsync(ctx.getTenantId(), msg.getOriginator());
+                ListenableFuture<List<EntityView>> entityViewsFuture =
+                        ctx.getEntityViewService().findEntityViewsByTenantIdAndEntityIdAsync(ctx.getTenantId(), msg.getOriginator());
 
-            DonAsynchron.withCallback(entityViewsFuture,
-                    entityViews -> {
-                        for (EntityView entityView : entityViews) {
-                            long startTime = entityView.getStartTimeMs();
-                            long endTime = entityView.getEndTimeMs();
-                            if ((endTime != 0  && endTime > now && startTime < now) || (endTime == 0 && startTime < now)) {
-                                Set<AttributeKvEntry> attributes =
-                                        JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())).getAttributes();
-                                List<AttributeKvEntry> filteredAttributes =
-                                        attributes.stream()
-                                                .filter(attr -> {
-                                                    switch (scope) {
-                                                        case DataConstants.CLIENT_SCOPE:
-                                                            if (entityView.getKeys().getAttributes().getCs().isEmpty()) {
-                                                                return true;
-                                                            }
-                                                            return entityView.getKeys().getAttributes().getCs().contains(attr.getKey());
-                                                        case DataConstants.SERVER_SCOPE:
-                                                            if (entityView.getKeys().getAttributes().getSs().isEmpty()) {
-                                                                return true;
-                                                            }
-                                                            return entityView.getKeys().getAttributes().getSs().contains(attr.getKey());
-                                                        case  DataConstants.SHARED_SCOPE:
-                                                            if (entityView.getKeys().getAttributes().getSh().isEmpty()) {
-                                                                return true;
-                                                            }
-                                                            return entityView.getKeys().getAttributes().getSh().contains(attr.getKey());
+                DonAsynchron.withCallback(entityViewsFuture,
+                        entityViews -> {
+                            for (EntityView entityView : entityViews) {
+                                long startTime = entityView.getStartTimeMs();
+                                long endTime = entityView.getEndTimeMs();
+                                if ((endTime != 0 && endTime > now && startTime < now) || (endTime == 0 && startTime < now)) {
+                                    if (DataConstants.ATTRIBUTES_UPDATED.equals(msg.getType()) ||
+                                            SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msg.getType())) {
+                                        Set<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData()));
+                                        List<AttributeKvEntry> filteredAttributes =
+                                                attributes.stream().filter(attr -> attributeContainsInEntityView(scope, attr.getKey(), entityView)).collect(Collectors.toList());
+                                        ctx.getTelemetryService().saveAndNotify(entityView.getId(), scope, filteredAttributes,
+                                                new FutureCallback<Void>() {
+                                                    @Override
+                                                    public void onSuccess(@Nullable Void result) {
+                                                        transformAndTellNext(ctx, msg, entityView);
                                                     }
-                                                    return false;
-                                                }).collect(Collectors.toList());
 
-                                ctx.getTelemetryService().saveAndNotify(entityView.getId(), scope, filteredAttributes,
-                                        new FutureCallback<Void>() {
-                                            @Override
-                                            public void onSuccess(@Nullable Void result) {
-                                                TbMsg updMsg = ctx.transformMsg(msg, msg.getType(), entityView.getId(), msg.getMetaData(), msg.getData());
-                                                ctx.tellNext(updMsg, SUCCESS);
-                                            }
-
-                                            @Override
-                                            public void onFailure(Throwable t) {
-                                                ctx.tellFailure(msg, t);
+                                                    @Override
+                                                    public void onFailure(Throwable t) {
+                                                        ctx.tellFailure(msg, t);
+                                                    }
+                                                });
+                                    } else if (DataConstants.ATTRIBUTES_DELETED.equals(msg.getType())) {
+                                        List<String> attributes = new ArrayList<>();
+                                        for (JsonElement element : new JsonParser().parse(msg.getData()).getAsJsonObject().get("attributes").getAsJsonArray()) {
+                                            if (element.isJsonPrimitive()) {
+                                                JsonPrimitive value = element.getAsJsonPrimitive();
+                                                if (value.isString()) {
+                                                    attributes.add(value.getAsString());
+                                                }
                                             }
-                                        });
+                                        }
+                                        List<String> filteredAttributes =
+                                                attributes.stream().filter(attr -> attributeContainsInEntityView(scope, attr, entityView)).collect(Collectors.toList());
+                                        if (filteredAttributes != null && !filteredAttributes.isEmpty()) {
+                                            ctx.getAttributesService().removeAll(entityView.getId(), scope, filteredAttributes);
+                                            transformAndTellNext(ctx, msg, entityView);
+                                        }
+                                    }
+                                }
                             }
-                        }
-                    },
-                    t -> ctx.tellFailure(msg, t));
+                        },
+                        t -> ctx.tellFailure(msg, t));
+            } else {
+                ctx.tellFailure(msg, new IllegalArgumentException("Message metadata is empty"));
+            }
         } else {
-            ctx.tellNext(msg, FAILURE);
+            ctx.tellFailure(msg, new IllegalArgumentException("Unsupported msg type [" + msg.getType() + "]"));
         }
     }
 
+    private void transformAndTellNext(TbContext ctx, TbMsg msg, EntityView entityView) {
+        TbMsg updMsg = ctx.transformMsg(msg, msg.getType(), entityView.getId(), msg.getMetaData(), msg.getData());
+        ctx.tellNext(updMsg, SUCCESS);
+    }
+
+    private boolean attributeContainsInEntityView(String scope, String attrKey, EntityView entityView) {
+        switch (scope) {
+            case DataConstants.CLIENT_SCOPE:
+                return entityView.getKeys().getAttributes().getCs().contains(attrKey);
+            case DataConstants.SERVER_SCOPE:
+                return entityView.getKeys().getAttributes().getSs().contains(attrKey);
+            case DataConstants.SHARED_SCOPE:
+                return entityView.getKeys().getAttributes().getSh().contains(attrKey);
+        }
+        return false;
+    }
+
     @Override
     public void destroy() {
-
     }
 }
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
index f78b186..035a17d 100644
--- 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
@@ -30,7 +30,7 @@ import org.thingsboard.server.common.msg.session.SessionMsgType;
         configClazz = EmptyNodeConfiguration.class,
         relationTypes = {"Post attributes", "Post telemetry", "RPC Request from Device", "RPC Request to Device", "Activity Event", "Inactivity Event",
                 "Connect Event", "Disconnect Event", "Entity Created", "Entity Updated", "Entity Deleted", "Entity Assigned",
-                "Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Other"},
+                "Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Alarm Acknowledged", "Alarm Cleared", "Other"},
         nodeDescription = "Route incoming messages by Message Type",
         nodeDetails = "Sends messages with message types <b>\"Post attributes\", \"Post telemetry\", \"RPC Request\"</b> etc. via corresponding chain, otherwise <b>Other</b> chain is used.",
         uiResources = {"static/rulenode/rulenode-core-config.js"},
@@ -75,6 +75,10 @@ public class TbMsgTypeSwitchNode implements TbNode {
             relationType = "Attributes Updated";
         } else if (msg.getType().equals(DataConstants.ATTRIBUTES_DELETED)) {
             relationType = "Attributes Deleted";
+        } else if (msg.getType().equals(DataConstants.ALARM_ACK)) {
+            relationType = "Alarm Acknowledged";
+        } else if (msg.getType().equals(DataConstants.ALARM_CLEAR)) {
+            relationType = "Alarm Cleared";
         } else if (msg.getType().equals(DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE)) {
             relationType = "RPC Request to Device";
         } else {
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
index 5cdb04e..12dbc6d 100644
--- 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
@@ -63,7 +63,7 @@ public class TbMsgAttributesNode implements TbNode {
         }
 
         String src = msg.getData();
-        Set<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(new JsonParser().parse(src)).getAttributes();
+        Set<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(new JsonParser().parse(src));
         ctx.getTelemetryService().saveAndNotify(msg.getOriginator(), config.getScope(), new ArrayList<>(attributes), new TelemetryNodeCallback(ctx, msg));
         if (msg.getOriginator().getEntityType() == EntityType.DEVICE && DataConstants.SHARED_SCOPE.equals(config.getScope())) {
             ctx.getTelemetryService().onSharedAttributesUpdate(ctx.getTenantId(), new DeviceId(msg.getOriginator().getId()), attributes);
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java
index efdf4af..4cb9999 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java
@@ -29,7 +29,6 @@ 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.TelemetryUploadRequest;
 import org.thingsboard.server.common.msg.session.SessionMsgType;
 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
 
@@ -68,13 +67,13 @@ public class TbMsgTimeseriesNode implements TbNode {
         if (!StringUtils.isEmpty(tsStr)) {
             try {
                 ts = Long.parseLong(tsStr);
-            } catch (NumberFormatException e) {}
+            } catch (NumberFormatException e) {
+            }
         } else {
             ts = System.currentTimeMillis();
         }
         String src = msg.getData();
-        TelemetryUploadRequest telemetryUploadRequest = JsonConverter.convertToTelemetry(new JsonParser().parse(src), ts);
-        Map<Long, List<KvEntry>> tsKvMap = telemetryUploadRequest.getData();
+        Map<Long, List<KvEntry>> tsKvMap = JsonConverter.convertToTelemetry(new JsonParser().parse(src), ts);
         if (tsKvMap == null) {
             ctx.tellFailure(msg, new IllegalArgumentException("Msg body is empty: " + src));
             return;
diff --git a/transport/coap/build.gradle b/transport/coap/build.gradle
new file mode 100644
index 0000000..6d54cb4
--- /dev/null
+++ b/transport/coap/build.gradle
@@ -0,0 +1,140 @@
+/**
+ * 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.
+ */
+import org.apache.tools.ant.filters.ReplaceTokens
+
+buildscript {
+    ext {
+        osPackageVersion = "3.8.0"
+    }
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath("com.netflix.nebula:gradle-ospackage-plugin:${osPackageVersion}")
+    }
+}
+
+apply plugin: "nebula.ospackage"
+
+buildDir = projectBuildDir
+version = projectVersion
+distsDirName = "./"
+
+// OS Package plugin configuration
+ospackage {
+    packageName = pkgName
+    version = "${project.version}"
+    release = 1
+    os = LINUX
+    type = BINARY
+
+    into pkgInstallFolder
+
+    user pkgName
+    permissionGroup pkgName
+
+    // Copy the actual .jar file
+    from(mainJar) {
+        // Strip the version from the jar filename
+        rename { String fileName ->
+            "${pkgName}.jar"
+        }
+        fileMode 0500
+        into "bin"
+    }
+
+    // Copy the config files
+    from("target/conf") {
+        exclude "${pkgName}.conf"
+        fileType CONFIG | NOREPLACE
+        fileMode 0754
+        into "conf"
+    }
+
+}
+
+// Configure our RPM build task
+buildRpm {
+
+    arch = NOARCH
+
+    version = projectVersion.replace('-', '')
+    archiveName = "${pkgName}.rpm"
+
+    requires("java-1.8.0")
+
+    from("target/conf") {
+        include "${pkgName}.conf"
+        filter(ReplaceTokens, tokens: ['pkg.platform': 'rpm'])
+        fileType CONFIG | NOREPLACE
+        fileMode 0754
+        into "${pkgInstallFolder}/conf"
+    }
+
+    preInstall file("${buildDir}/control/rpm/preinst")
+    postInstall file("${buildDir}/control/rpm/postinst")
+    preUninstall file("${buildDir}/control/rpm/prerm")
+    postUninstall file("${buildDir}/control/rpm/postrm")
+
+    user pkgName
+    permissionGroup pkgName
+
+    // Copy the system unit files
+    from("${buildDir}/control/${pkgName}.service") {
+        addParentDirs = false
+        fileMode 0644
+        into "/usr/lib/systemd/system"
+    }
+
+    directory(pkgLogFolder, 0755)
+    link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml")
+    link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf")
+}
+
+// Same as the buildRpm task
+buildDeb {
+
+    arch = "all"
+
+    archiveName = "${pkgName}.deb"
+
+    requires("openjdk-8-jre").or("java8-runtime").or("oracle-java8-installer").or("openjdk-8-jre-headless")
+
+    from("target/conf") {
+        include "${pkgName}.conf"
+        filter(ReplaceTokens, tokens: ['pkg.platform': 'deb'])
+        fileType CONFIG | NOREPLACE
+        fileMode 0754
+        into "${pkgInstallFolder}/conf"
+    }
+
+    configurationFile("${pkgInstallFolder}/conf/${pkgName}.conf")
+    configurationFile("${pkgInstallFolder}/conf/${pkgName}.yml")
+    configurationFile("${pkgInstallFolder}/conf/logback.xml")
+
+    preInstall file("${buildDir}/control/deb/preinst")
+    postInstall file("${buildDir}/control/deb/postinst")
+    preUninstall file("${buildDir}/control/deb/prerm")
+    postUninstall file("${buildDir}/control/deb/postrm")
+
+    user pkgName
+    permissionGroup pkgName
+
+    directory(pkgLogFolder, 0755)
+    link("/etc/init.d/${pkgName}", "${pkgInstallFolder}/bin/${pkgName}.jar")
+    link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml")
+    link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf")
+}

transport/coap/pom.xml 299(+278 -21)

diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml
index a7015f0..a5e8107 100644
--- a/transport/coap/pom.xml
+++ b/transport/coap/pom.xml
@@ -27,42 +27,37 @@
     <artifactId>coap</artifactId>
     <packaging>jar</packaging>
 
-    <name>Thingsboard COAP Transport</name>
+    <name>Thingsboard CoAP Transport Service</name>
     <url>https://thingsboard.io</url>
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <main.dir>${basedir}/../..</main.dir>
+        <pkg.name>tb-coap-transport</pkg.name>
+        <pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
+        <pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
+        <pkg.win.dist>${project.build.directory}/windows</pkg.win.dist>
     </properties>
 
     <dependencies>
         <dependency>
-            <groupId>org.thingsboard.common</groupId>
-            <artifactId>transport</artifactId>
-        </dependency>    
-        <dependency>
-            <groupId>org.eclipse.californium</groupId>
-            <artifactId>californium-core</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework</groupId>
-            <artifactId>spring-context</artifactId>
+            <groupId>org.thingsboard.common.transport</groupId>
+            <artifactId>coap</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>log4j-over-slf4j</artifactId>
+            <groupId>org.thingsboard.common</groupId>
+            <artifactId>queue</artifactId>
         </dependency>
         <dependency>
-            <groupId>ch.qos.logback</groupId>
-            <artifactId>logback-core</artifactId>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
         <dependency>
-            <groupId>ch.qos.logback</groupId>
-            <artifactId>logback-classic</artifactId>
+            <groupId>com.sun.winsw</groupId>
+            <artifactId>winsw</artifactId>
+            <classifier>bin</classifier>
+            <type>exe</type>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
@@ -81,4 +76,266 @@
         </dependency>
     </dependencies>
 
+    <build>
+        <finalName>${pkg.name}-${project.version}</finalName>
+        <resources>
+            <resource>
+                <directory>${project.basedir}/src/main/resources</directory>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-conf</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/conf</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>src/main/resources</directory>
+                                    <excludes>
+                                        <exclude>logback.xml</exclude>
+                                    </excludes>
+                                    <filtering>false</filtering>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>copy-service-conf</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/conf</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>src/main/conf</directory>
+                                    <filtering>true</filtering>
+                                </resource>
+                            </resources>
+                            <filters>
+                                <filter>src/main/filters/unix.properties</filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>copy-win-conf</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${pkg.win.dist}/conf</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>src/main/resources</directory>
+                                    <excludes>
+                                        <exclude>logback.xml</exclude>
+                                    </excludes>
+                                    <filtering>false</filtering>
+                                </resource>
+                                <resource>
+                                    <directory>src/main/conf</directory>
+                                    <excludes>
+                                        <exclude>tb-coap-transport.conf</exclude>
+                                    </excludes>
+                                    <filtering>true</filtering>
+                                </resource>
+                            </resources>
+                            <filters>
+                                <filter>src/main/filters/windows.properties</filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>copy-control</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/control</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>src/main/scripts/control</directory>
+                                    <filtering>true</filtering>
+                                </resource>
+                            </resources>
+                            <filters>
+                                <filter>src/main/filters/unix.properties</filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>copy-windows-control</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${pkg.win.dist}</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>src/main/scripts/windows</directory>
+                                    <filtering>true</filtering>
+                                </resource>
+                            </resources>
+                            <filters>
+                                <filter>src/main/filters/windows.properties</filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-winsw-service</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>copy</goal>
+                        </goals>
+                        <configuration>
+                            <artifactItems>
+                                <artifactItem>
+                                    <groupId>com.sun.winsw</groupId>
+                                    <artifactId>winsw</artifactId>
+                                    <classifier>bin</classifier>
+                                    <type>exe</type>
+                                    <destFileName>service.exe</destFileName>
+                                </artifactItem>
+                            </artifactItems>
+                            <outputDirectory>${pkg.win.dist}</outputDirectory>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>**/logback.xml</exclude>
+                    </excludes>
+                    <archive>
+                        <manifestEntries>
+                            <Implementation-Title>ThingsBoard CoAP Transport Service</Implementation-Title>
+                            <Implementation-Version>${project.version}</Implementation-Version>
+                        </manifestEntries>
+                    </archive>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <mainClass>org.thingsboard.server.coap.ThingsboardCoapTransportApplication</mainClass>
+                    <classifier>boot</classifier>
+                    <layout>ZIP</layout>
+                    <executable>true</executable>
+                    <excludeDevtools>true</excludeDevtools>
+                    <embeddedLaunchScriptProperties>
+                        <confFolder>${pkg.installFolder}/conf</confFolder>
+                        <logFolder>${pkg.unixLogFolder}</logFolder>
+                        <logFilename>${pkg.name}.out</logFilename>
+                        <initInfoProvides>${pkg.name}</initInfoProvides>
+                    </embeddedLaunchScriptProperties>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.fortasoft</groupId>
+                <artifactId>gradle-maven-plugin</artifactId>
+                <configuration>
+                    <tasks>
+                        <task>build</task>
+                        <task>buildDeb</task>
+                        <task>buildRpm</task>
+                    </tasks>
+                    <args>
+                        <arg>-PprojectBuildDir=${project.build.directory}</arg>
+                        <arg>-PprojectVersion=${project.version}</arg>
+                        <arg>-PmainJar=${project.build.directory}/${project.build.finalName}-boot.${project.packaging}</arg>
+                        <arg>-PpkgName=${pkg.name}</arg>
+                        <arg>-PpkgInstallFolder=${pkg.installFolder}</arg>
+                        <arg>-PpkgLogFolder=${pkg.unixLogFolder}</arg>
+                    </args>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>invoke</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <finalName>${pkg.name}</finalName>
+                    <descriptors>
+                        <descriptor>src/main/assembly/windows.xml</descriptor>
+                    </descriptors>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-install-plugin</artifactId>
+                <configuration>
+                    <file>${project.build.directory}/${pkg.name}.deb</file>
+                    <artifactId>${project.artifactId}</artifactId>
+                    <groupId>${project.groupId}</groupId>
+                    <version>${project.version}</version>
+                    <classifier>deb</classifier>
+                    <packaging>deb</packaging>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>install-deb</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>install-file</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+    <repositories>
+        <repository>
+            <id>jenkins</id>
+            <name>Jenkins Repository</name>
+            <url>http://repo.jenkins-ci.org/releases</url>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </repository>
+    </repositories>
 </project>
diff --git a/transport/coap/src/main/assembly/windows.xml b/transport/coap/src/main/assembly/windows.xml
new file mode 100644
index 0000000..82da34e
--- /dev/null
+++ b/transport/coap/src/main/assembly/windows.xml
@@ -0,0 +1,71 @@
+<!--
+
+    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.
+
+-->
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
+    <id>windows</id>
+
+    <formats>
+        <format>zip</format>
+    </formats>
+
+    <!-- Workaround to create logs directory -->
+    <fileSets>
+        <fileSet>
+            <directory>${pkg.win.dist}</directory>
+            <outputDirectory>logs</outputDirectory>
+            <excludes>
+                <exclude>*/**</exclude>
+            </excludes>
+        </fileSet>
+        <fileSet>
+            <directory>${pkg.win.dist}/conf</directory>
+            <outputDirectory>conf</outputDirectory>
+            <lineEnding>windows</lineEnding>
+        </fileSet>
+    </fileSets>
+
+    <files>
+        <file>
+            <source>${project.build.directory}/${project.build.finalName}-boot.${project.packaging}</source>
+            <outputDirectory>lib</outputDirectory>
+            <destName>${pkg.name}.jar</destName>
+        </file>
+        <file>
+            <source>${pkg.win.dist}/service.exe</source>
+            <outputDirectory/>
+            <destName>${pkg.name}.exe</destName>
+        </file>
+        <file>
+            <source>${pkg.win.dist}/service.xml</source>
+            <outputDirectory/>
+            <destName>${pkg.name}.xml</destName>
+            <lineEnding>windows</lineEnding>
+        </file>
+        <file>
+            <source>${pkg.win.dist}/install.bat</source>
+            <outputDirectory/>
+            <lineEnding>windows</lineEnding>
+        </file>
+        <file>
+            <source>${pkg.win.dist}/uninstall.bat</source>
+            <outputDirectory/>
+            <lineEnding>windows</lineEnding>
+        </file>
+    </files>
+</assembly>
diff --git a/transport/coap/src/main/conf/logback.xml b/transport/coap/src/main/conf/logback.xml
new file mode 100644
index 0000000..f36469d
--- /dev/null
+++ b/transport/coap/src/main/conf/logback.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+
+    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.
+
+-->
+<!DOCTYPE configuration>
+<configuration>
+
+    <appender name="fileLogAppender"
+              class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${pkg.logFolder}/${pkg.name}.log</file>
+        <rollingPolicy
+                class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${pkg.logFolder}/${pkg.name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <maxFileSize>100MB</maxFileSize>
+            <maxHistory>30</maxHistory>
+            <totalSizeCap>3GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <logger name="org.thingsboard.server" level="INFO" />
+
+    <root level="INFO">
+        <appender-ref ref="fileLogAppender"/>
+    </root>
+
+</configuration>
diff --git a/transport/coap/src/main/conf/tb-coap-transport.conf b/transport/coap/src/main/conf/tb-coap-transport.conf
new file mode 100644
index 0000000..0afa91c
--- /dev/null
+++ b/transport/coap/src/main/conf/tb-coap-transport.conf
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+
+export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
+export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"
+export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
+export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"
+export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly"
+export LOG_FILENAME=${pkg.name}.out
+export LOADER_PATH=${pkg.installFolder}/conf
diff --git a/transport/coap/src/main/filters/unix.properties b/transport/coap/src/main/filters/unix.properties
new file mode 100644
index 0000000..8967278
--- /dev/null
+++ b/transport/coap/src/main/filters/unix.properties
@@ -0,0 +1 @@
+pkg.logFolder=${pkg.unixLogFolder}
\ No newline at end of file
diff --git a/transport/coap/src/main/filters/windows.properties b/transport/coap/src/main/filters/windows.properties
new file mode 100644
index 0000000..a6e48d9
--- /dev/null
+++ b/transport/coap/src/main/filters/windows.properties
@@ -0,0 +1,2 @@
+pkg.logFolder=${BASE}\\logs
+pkg.winWrapperLogFolder=%BASE%\\logs
diff --git a/transport/coap/src/main/java/org/thingsboard/server/coap/ThingsboardCoapTransportApplication.java b/transport/coap/src/main/java/org/thingsboard/server/coap/ThingsboardCoapTransportApplication.java
new file mode 100644
index 0000000..1ad86dd
--- /dev/null
+++ b/transport/coap/src/main/java/org/thingsboard/server/coap/ThingsboardCoapTransportApplication.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.server.coap;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+import java.util.Arrays;
+
+@SpringBootConfiguration
+@EnableAsync
+@EnableScheduling
+@ComponentScan({"org.thingsboard.server.coap", "org.thingsboard.server.common", "org.thingsboard.server.transport.coap", "org.thingsboard.server.kafka"})
+public class ThingsboardCoapTransportApplication {
+
+    private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name";
+    private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "tb-coap-transport";
+
+    public static void main(String[] args) {
+        SpringApplication.run(ThingsboardCoapTransportApplication.class, updateArguments(args));
+    }
+
+    private static String[] updateArguments(String[] args) {
+        if (Arrays.stream(args).noneMatch(arg -> arg.startsWith(SPRING_CONFIG_NAME_KEY))) {
+            String[] modifiedArgs = new String[args.length + 1];
+            System.arraycopy(args, 0, modifiedArgs, 0, args.length);
+            modifiedArgs[args.length] = DEFAULT_SPRING_CONFIG_PARAM;
+            return modifiedArgs;
+        }
+        return args;
+    }
+}
diff --git a/transport/coap/src/main/resources/logback.xml b/transport/coap/src/main/resources/logback.xml
new file mode 100644
index 0000000..18864a9
--- /dev/null
+++ b/transport/coap/src/main/resources/logback.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+
+    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.
+
+-->
+<!DOCTYPE configuration>
+<configuration scan="true" scanPeriod="10 seconds">
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <logger name="org.thingsboard.server" level="TRACE" />
+
+    <root level="INFO">
+        <appender-ref ref="STDOUT"/>
+    </root>
+
+</configuration>
\ No newline at end of file
diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml
new file mode 100644
index 0000000..1790d82
--- /dev/null
+++ b/transport/coap/src/main/resources/tb-coap-transport.yml
@@ -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.
+#
+
+spring.main.web-environment: false
+spring.main.web-application-type: none
+
+# MQTT server parameters
+transport:
+  coap:
+    bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
+    bind_port: "${COAP_BIND_PORT:5683}"
+    timeout: "${COAP_TIMEOUT:10000}"
+
+#Quota parameters
+quota:
+  host:
+    # Max allowed number of API requests in interval for single host
+    limit: "${QUOTA_HOST_LIMIT:10000}"
+    # Interval duration
+    intervalMs: "${QUOTA_HOST_INTERVAL_MS:60000}"
+    # Maximum silence duration for host after which Host removed from QuotaService. Must be bigger than intervalMs
+    ttlMs: "${QUOTA_HOST_TTL_MS:60000}"
+    # Interval for scheduled task that cleans expired records. TTL is used for expiring
+    cleanPeriodMs: "${QUOTA_HOST_CLEAN_PERIOD_MS:300000}"
+    # Enable Host API Limits
+    enabled: "${QUOTA_HOST_ENABLED:true}"
+    # Array of whitelist hosts
+    whitelist: "${QUOTA_HOST_WHITELIST:localhost,127.0.0.1}"
+    # Array of blacklist hosts
+    blacklist: "${QUOTA_HOST_BLACKLIST:}"
+    log:
+      topSize: 10
+      intervalMin: 2
+
+kafka:
+  enabled: true
+  bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"
+  acks: "${TB_KAFKA_ACKS:all}"
+  retries: "${TB_KAFKA_RETRIES:1}"
+  batch.size: "${TB_KAFKA_BATCH_SIZE:16384}"
+  linger.ms: "${TB_KAFKA_LINGER_MS:1}"
+  buffer.memory: "${TB_BUFFER_MEMORY:33554432}"
+  transport_api:
+    requests_topic: "${TB_TRANSPORT_API_REQUEST_TOPIC:tb.transport.api.requests}"
+    responses_topic: "${TB_TRANSPORT_API_RESPONSE_TOPIC:tb.transport.api.responses}"
+    max_pending_requests: "${TB_TRANSPORT_MAX_PENDING_REQUESTS:10000}"
+    max_requests_timeout: "${TB_TRANSPORT_MAX_REQUEST_TIMEOUT:10000}"
+    response_poll_interval: "${TB_TRANSPORT_RESPONSE_POLL_INTERVAL_MS:25}"
+    response_auto_commit_interval: "${TB_TRANSPORT_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}"
+  rule_engine:
+    topic: "${TB_RULE_ENGINE_TOPIC:tb.rule-engine}"
+  notifications:
+    topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}"
+    poll_interval: "${TB_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}"
+    auto_commit_interval: "${TB_TRANSPORT_NOTIFICATIONS_AUTO_COMMIT_INTERVAL_MS:100}"
diff --git a/transport/coap/src/main/scripts/control/deb/postinst b/transport/coap/src/main/scripts/control/deb/postinst
new file mode 100644
index 0000000..d4066c0
--- /dev/null
+++ b/transport/coap/src/main/scripts/control/deb/postinst
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+chown -R ${pkg.name}: ${pkg.logFolder}
+chown -R ${pkg.name}: ${pkg.installFolder}
+update-rc.d ${pkg.name} defaults
+
diff --git a/transport/coap/src/main/scripts/control/deb/postrm b/transport/coap/src/main/scripts/control/deb/postrm
new file mode 100644
index 0000000..6186580
--- /dev/null
+++ b/transport/coap/src/main/scripts/control/deb/postrm
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+update-rc.d -f ${pkg.name} remove
diff --git a/transport/coap/src/main/scripts/control/deb/preinst b/transport/coap/src/main/scripts/control/deb/preinst
new file mode 100644
index 0000000..6be5959
--- /dev/null
+++ b/transport/coap/src/main/scripts/control/deb/preinst
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+if ! getent group ${pkg.name} >/dev/null; then
+    addgroup --system ${pkg.name}
+fi
+
+if ! getent passwd ${pkg.name} >/dev/null; then
+    adduser --quiet \
+            --system \
+            --ingroup ${pkg.name} \
+            --quiet \
+            --disabled-login \
+            --disabled-password \
+            --home ${pkg.installFolder} \
+            --no-create-home \
+            -gecos "Thingsboard application" \
+            ${pkg.name}
+fi
diff --git a/transport/coap/src/main/scripts/control/deb/prerm b/transport/coap/src/main/scripts/control/deb/prerm
new file mode 100644
index 0000000..898d3ef
--- /dev/null
+++ b/transport/coap/src/main/scripts/control/deb/prerm
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+if [ -e /var/run/${pkg.name}/${pkg.name}.pid ]; then
+    service ${pkg.name} stop
+fi
diff --git a/transport/coap/src/main/scripts/control/rpm/postinst b/transport/coap/src/main/scripts/control/rpm/postinst
new file mode 100644
index 0000000..8a7a88f
--- /dev/null
+++ b/transport/coap/src/main/scripts/control/rpm/postinst
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+chown -R ${pkg.name}: ${pkg.logFolder}
+chown -R ${pkg.name}: ${pkg.installFolder}
+
+if [ $1 -eq 1 ] ; then
+        # Initial installation
+        systemctl --no-reload enable ${pkg.name}.service >/dev/null 2>&1 || :
+fi
diff --git a/transport/coap/src/main/scripts/control/rpm/postrm b/transport/coap/src/main/scripts/control/rpm/postrm
new file mode 100644
index 0000000..8e1f8a2
--- /dev/null
+++ b/transport/coap/src/main/scripts/control/rpm/postrm
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+if [ $1 -ge 1 ] ; then
+        # Package upgrade, not uninstall
+        systemctl try-restart ${pkg.name}.service >/dev/null 2>&1 || :
+fi
diff --git a/transport/coap/src/main/scripts/control/rpm/preinst b/transport/coap/src/main/scripts/control/rpm/preinst
new file mode 100644
index 0000000..e19fc88
--- /dev/null
+++ b/transport/coap/src/main/scripts/control/rpm/preinst
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+getent group ${pkg.name} >/dev/null || groupadd -r ${pkg.name}
+getent passwd ${pkg.name} >/dev/null || \
+useradd -d ${pkg.installFolder} -g ${pkg.name} -M -r ${pkg.name} -s /sbin/nologin \
+-c "Thingsboard application"
diff --git a/transport/coap/src/main/scripts/control/rpm/prerm b/transport/coap/src/main/scripts/control/rpm/prerm
new file mode 100644
index 0000000..accb487
--- /dev/null
+++ b/transport/coap/src/main/scripts/control/rpm/prerm
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+if [ $1 -eq 0 ] ; then
+        # Package removal, not upgrade
+        systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :
+fi
diff --git a/transport/coap/src/main/scripts/control/tb-coap-transport.service b/transport/coap/src/main/scripts/control/tb-coap-transport.service
new file mode 100644
index 0000000..d456fc0
--- /dev/null
+++ b/transport/coap/src/main/scripts/control/tb-coap-transport.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=${pkg.name}
+After=syslog.target
+
+[Service]
+User=${pkg.name}
+ExecStart=${pkg.installFolder}/bin/${pkg.name}.jar
+SuccessExitStatus=143
+
+[Install]
+WantedBy=multi-user.target
diff --git a/transport/coap/src/main/scripts/windows/install.bat b/transport/coap/src/main/scripts/windows/install.bat
new file mode 100644
index 0000000..dba7736
--- /dev/null
+++ b/transport/coap/src/main/scripts/windows/install.bat
@@ -0,0 +1,87 @@
+@ECHO OFF
+
+setlocal ENABLEEXTENSIONS
+
+@ECHO Detecting Java version installed.
+:CHECK_JAVA_64
+@ECHO Detecting if it is 64 bit machine
+set KEY_NAME="HKEY_LOCAL_MACHINE\Software\Wow6432Node\JavaSoft\Java Runtime Environment"
+set VALUE_NAME=CurrentVersion
+
+FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
+    set ValueName=%%A
+    set ValueType=%%B
+    set ValueValue=%%C
+)
+@ECHO CurrentVersion %ValueValue%
+
+SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%"
+SET VALUE_NAME=JavaHome
+
+if defined ValueName (
+    FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
+        set ValueName2=%%A
+        set ValueType2=%%B
+        set JRE_PATH2=%%C
+
+        if defined ValueName2 (
+            set ValueName = %ValueName2%
+            set ValueType = %ValueType2%
+            set ValueValue =  %JRE_PATH2%
+        )
+    )
+)
+
+IF NOT "%JRE_PATH2%" == "" GOTO JAVA_INSTALLED
+
+:CHECK_JAVA_32
+@ECHO Detecting if it is 32 bit machine
+set KEY_NAME="HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment"
+set VALUE_NAME=CurrentVersion
+
+FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
+    set ValueName=%%A
+    set ValueType=%%B
+    set ValueValue=%%C
+)
+@ECHO CurrentVersion %ValueValue%
+
+SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%"
+SET VALUE_NAME=JavaHome
+
+if defined ValueName (
+    FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
+        set ValueName2=%%A
+        set ValueType2=%%B
+        set JRE_PATH2=%%C
+
+        if defined ValueName2 (
+            set ValueName = %ValueName2%
+            set ValueType = %ValueType2%
+            set ValueValue =  %JRE_PATH2%
+        )
+    )
+)
+
+IF "%JRE_PATH2%" == ""  GOTO JAVA_NOT_INSTALLED
+
+:JAVA_INSTALLED
+
+@ECHO Java 1.8 found!
+@ECHO Installing ${pkg.name} ...
+
+%BASE%${pkg.name}.exe install
+
+@ECHO ${pkg.name} installed successfully!
+
+GOTO END
+
+:JAVA_NOT_INSTALLED
+@ECHO Java 1.8 or above is not installed
+@ECHO Please go to https://java.com/ and install Java. Then retry installation.
+PAUSE
+GOTO END
+
+:END
+
+
diff --git a/transport/coap/src/main/scripts/windows/service.xml b/transport/coap/src/main/scripts/windows/service.xml
new file mode 100644
index 0000000..f7b9d30
--- /dev/null
+++ b/transport/coap/src/main/scripts/windows/service.xml
@@ -0,0 +1,36 @@
+<service>
+    <id>${pkg.name}</id>
+    <name>${project.name}</name>
+    <description>${project.description}</description>
+    <workingdirectory>%BASE%\conf</workingdirectory>
+    <logpath>${pkg.winWrapperLogFolder}</logpath>
+    <logmode>rotate</logmode>
+    <env name="LOADER_PATH" value="%BASE%\conf" />
+    <executable>java</executable>
+    <startargument>-Xloggc:%BASE%\logs\gc.log</startargument>
+    <startargument>-XX:+HeapDumpOnOutOfMemoryError</startargument>
+    <startargument>-XX:+PrintGCDetails</startargument>
+    <startargument>-XX:+PrintGCDateStamps</startargument>
+    <startargument>-XX:+PrintHeapAtGC</startargument>
+    <startargument>-XX:+PrintTenuringDistribution</startargument>
+    <startargument>-XX:+PrintGCApplicationStoppedTime</startargument>
+    <startargument>-XX:+UseGCLogFileRotation</startargument>
+    <startargument>-XX:NumberOfGCLogFiles=10</startargument>
+    <startargument>-XX:GCLogFileSize=10M</startargument>
+    <startargument>-XX:-UseBiasedLocking</startargument>
+    <startargument>-XX:+UseTLAB</startargument>
+    <startargument>-XX:+ResizeTLAB</startargument>
+    <startargument>-XX:+PerfDisableSharedMem</startargument>
+    <startargument>-XX:+UseCondCardMark</startargument>
+    <startargument>-XX:CMSWaitDuration=10000</startargument>
+    <startargument>-XX:+UseParNewGC</startargument>
+    <startargument>-XX:+UseConcMarkSweepGC</startargument>
+    <startargument>-XX:+CMSParallelRemarkEnabled</startargument>
+    <startargument>-XX:+CMSParallelInitialMarkEnabled</startargument>
+    <startargument>-XX:+CMSEdenChunksRecordAlways</startargument>
+    <startargument>-XX:CMSInitiatingOccupancyFraction=75</startargument>
+    <startargument>-XX:+UseCMSInitiatingOccupancyOnly</startargument>
+    <startargument>-jar</startargument>
+    <startargument>%BASE%\lib\${pkg.name}.jar</startargument>
+
+</service>
diff --git a/transport/coap/src/main/scripts/windows/uninstall.bat b/transport/coap/src/main/scripts/windows/uninstall.bat
new file mode 100644
index 0000000..921e4c8
--- /dev/null
+++ b/transport/coap/src/main/scripts/windows/uninstall.bat
@@ -0,0 +1,9 @@
+@ECHO OFF
+
+@ECHO Stopping ${pkg.name} ...
+net stop ${pkg.name}
+
+@ECHO Uninstalling ${pkg.name} ...
+%~dp0${pkg.name}.exe uninstall
+
+@ECHO DONE.
\ No newline at end of file
diff --git a/transport/http/build.gradle b/transport/http/build.gradle
new file mode 100644
index 0000000..6d54cb4
--- /dev/null
+++ b/transport/http/build.gradle
@@ -0,0 +1,140 @@
+/**
+ * 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.
+ */
+import org.apache.tools.ant.filters.ReplaceTokens
+
+buildscript {
+    ext {
+        osPackageVersion = "3.8.0"
+    }
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath("com.netflix.nebula:gradle-ospackage-plugin:${osPackageVersion}")
+    }
+}
+
+apply plugin: "nebula.ospackage"
+
+buildDir = projectBuildDir
+version = projectVersion
+distsDirName = "./"
+
+// OS Package plugin configuration
+ospackage {
+    packageName = pkgName
+    version = "${project.version}"
+    release = 1
+    os = LINUX
+    type = BINARY
+
+    into pkgInstallFolder
+
+    user pkgName
+    permissionGroup pkgName
+
+    // Copy the actual .jar file
+    from(mainJar) {
+        // Strip the version from the jar filename
+        rename { String fileName ->
+            "${pkgName}.jar"
+        }
+        fileMode 0500
+        into "bin"
+    }
+
+    // Copy the config files
+    from("target/conf") {
+        exclude "${pkgName}.conf"
+        fileType CONFIG | NOREPLACE
+        fileMode 0754
+        into "conf"
+    }
+
+}
+
+// Configure our RPM build task
+buildRpm {
+
+    arch = NOARCH
+
+    version = projectVersion.replace('-', '')
+    archiveName = "${pkgName}.rpm"
+
+    requires("java-1.8.0")
+
+    from("target/conf") {
+        include "${pkgName}.conf"
+        filter(ReplaceTokens, tokens: ['pkg.platform': 'rpm'])
+        fileType CONFIG | NOREPLACE
+        fileMode 0754
+        into "${pkgInstallFolder}/conf"
+    }
+
+    preInstall file("${buildDir}/control/rpm/preinst")
+    postInstall file("${buildDir}/control/rpm/postinst")
+    preUninstall file("${buildDir}/control/rpm/prerm")
+    postUninstall file("${buildDir}/control/rpm/postrm")
+
+    user pkgName
+    permissionGroup pkgName
+
+    // Copy the system unit files
+    from("${buildDir}/control/${pkgName}.service") {
+        addParentDirs = false
+        fileMode 0644
+        into "/usr/lib/systemd/system"
+    }
+
+    directory(pkgLogFolder, 0755)
+    link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml")
+    link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf")
+}
+
+// Same as the buildRpm task
+buildDeb {
+
+    arch = "all"
+
+    archiveName = "${pkgName}.deb"
+
+    requires("openjdk-8-jre").or("java8-runtime").or("oracle-java8-installer").or("openjdk-8-jre-headless")
+
+    from("target/conf") {
+        include "${pkgName}.conf"
+        filter(ReplaceTokens, tokens: ['pkg.platform': 'deb'])
+        fileType CONFIG | NOREPLACE
+        fileMode 0754
+        into "${pkgInstallFolder}/conf"
+    }
+
+    configurationFile("${pkgInstallFolder}/conf/${pkgName}.conf")
+    configurationFile("${pkgInstallFolder}/conf/${pkgName}.yml")
+    configurationFile("${pkgInstallFolder}/conf/logback.xml")
+
+    preInstall file("${buildDir}/control/deb/preinst")
+    postInstall file("${buildDir}/control/deb/postinst")
+    preUninstall file("${buildDir}/control/deb/prerm")
+    postUninstall file("${buildDir}/control/deb/postrm")
+
+    user pkgName
+    permissionGroup pkgName
+
+    directory(pkgLogFolder, 0755)
+    link("/etc/init.d/${pkgName}", "${pkgInstallFolder}/bin/${pkgName}.jar")
+    link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml")
+    link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf")
+}

transport/http/pom.xml 296(+278 -18)

diff --git a/transport/http/pom.xml b/transport/http/pom.xml
index 9964708..407d995 100644
--- a/transport/http/pom.xml
+++ b/transport/http/pom.xml
@@ -27,39 +27,37 @@
     <artifactId>http</artifactId>
     <packaging>jar</packaging>
 
-    <name>Thingsboard HTTP Transport</name>
+    <name>Thingsboard HTTP Transport Service</name>
     <url>https://thingsboard.io</url>
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <main.dir>${basedir}/../..</main.dir>
+        <pkg.name>tb-http-transport</pkg.name>
+        <pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
+        <pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
+        <pkg.win.dist>${project.build.directory}/windows</pkg.win.dist>
     </properties>
 
     <dependencies>
         <dependency>
-            <groupId>org.thingsboard.common</groupId>
-            <artifactId>transport</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-web</artifactId>
-            <scope>provided</scope>
+            <groupId>org.thingsboard.common.transport</groupId>
+            <artifactId>http</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>log4j-over-slf4j</artifactId>
+            <groupId>org.thingsboard.common</groupId>
+            <artifactId>queue</artifactId>
         </dependency>
         <dependency>
-            <groupId>ch.qos.logback</groupId>
-            <artifactId>logback-core</artifactId>
+            <groupId>com.sun.winsw</groupId>
+            <artifactId>winsw</artifactId>
+            <classifier>bin</classifier>
+            <type>exe</type>
+            <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>ch.qos.logback</groupId>
-            <artifactId>logback-classic</artifactId>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
@@ -78,4 +76,266 @@
         </dependency>
     </dependencies>
 
+    <build>
+        <finalName>${pkg.name}-${project.version}</finalName>
+        <resources>
+            <resource>
+                <directory>${project.basedir}/src/main/resources</directory>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-conf</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/conf</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>src/main/resources</directory>
+                                    <excludes>
+                                        <exclude>logback.xml</exclude>
+                                    </excludes>
+                                    <filtering>false</filtering>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>copy-service-conf</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/conf</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>src/main/conf</directory>
+                                    <filtering>true</filtering>
+                                </resource>
+                            </resources>
+                            <filters>
+                                <filter>src/main/filters/unix.properties</filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>copy-win-conf</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${pkg.win.dist}/conf</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>src/main/resources</directory>
+                                    <excludes>
+                                        <exclude>logback.xml</exclude>
+                                    </excludes>
+                                    <filtering>false</filtering>
+                                </resource>
+                                <resource>
+                                    <directory>src/main/conf</directory>
+                                    <excludes>
+                                        <exclude>tb-http-transport.conf</exclude>
+                                    </excludes>
+                                    <filtering>true</filtering>
+                                </resource>
+                            </resources>
+                            <filters>
+                                <filter>src/main/filters/windows.properties</filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>copy-control</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/control</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>src/main/scripts/control</directory>
+                                    <filtering>true</filtering>
+                                </resource>
+                            </resources>
+                            <filters>
+                                <filter>src/main/filters/unix.properties</filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>copy-windows-control</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${pkg.win.dist}</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>src/main/scripts/windows</directory>
+                                    <filtering>true</filtering>
+                                </resource>
+                            </resources>
+                            <filters>
+                                <filter>src/main/filters/windows.properties</filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-winsw-service</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>copy</goal>
+                        </goals>
+                        <configuration>
+                            <artifactItems>
+                                <artifactItem>
+                                    <groupId>com.sun.winsw</groupId>
+                                    <artifactId>winsw</artifactId>
+                                    <classifier>bin</classifier>
+                                    <type>exe</type>
+                                    <destFileName>service.exe</destFileName>
+                                </artifactItem>
+                            </artifactItems>
+                            <outputDirectory>${pkg.win.dist}</outputDirectory>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>**/logback.xml</exclude>
+                    </excludes>
+                    <archive>
+                        <manifestEntries>
+                            <Implementation-Title>ThingsBoard HTTP Transport Service</Implementation-Title>
+                            <Implementation-Version>${project.version}</Implementation-Version>
+                        </manifestEntries>
+                    </archive>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <mainClass>org.thingsboard.server.http.ThingsboardHttpTransportApplication</mainClass>
+                    <classifier>boot</classifier>
+                    <layout>ZIP</layout>
+                    <executable>true</executable>
+                    <excludeDevtools>true</excludeDevtools>
+                    <embeddedLaunchScriptProperties>
+                        <confFolder>${pkg.installFolder}/conf</confFolder>
+                        <logFolder>${pkg.unixLogFolder}</logFolder>
+                        <logFilename>${pkg.name}.out</logFilename>
+                        <initInfoProvides>${pkg.name}</initInfoProvides>
+                    </embeddedLaunchScriptProperties>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.fortasoft</groupId>
+                <artifactId>gradle-maven-plugin</artifactId>
+                <configuration>
+                    <tasks>
+                        <task>build</task>
+                        <task>buildDeb</task>
+                        <task>buildRpm</task>
+                    </tasks>
+                    <args>
+                        <arg>-PprojectBuildDir=${project.build.directory}</arg>
+                        <arg>-PprojectVersion=${project.version}</arg>
+                        <arg>-PmainJar=${project.build.directory}/${project.build.finalName}-boot.${project.packaging}</arg>
+                        <arg>-PpkgName=${pkg.name}</arg>
+                        <arg>-PpkgInstallFolder=${pkg.installFolder}</arg>
+                        <arg>-PpkgLogFolder=${pkg.unixLogFolder}</arg>
+                    </args>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>invoke</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <finalName>${pkg.name}</finalName>
+                    <descriptors>
+                        <descriptor>src/main/assembly/windows.xml</descriptor>
+                    </descriptors>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-install-plugin</artifactId>
+                <configuration>
+                    <file>${project.build.directory}/${pkg.name}.deb</file>
+                    <artifactId>${project.artifactId}</artifactId>
+                    <groupId>${project.groupId}</groupId>
+                    <version>${project.version}</version>
+                    <classifier>deb</classifier>
+                    <packaging>deb</packaging>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>install-deb</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>install-file</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+    <repositories>
+        <repository>
+            <id>jenkins</id>
+            <name>Jenkins Repository</name>
+            <url>http://repo.jenkins-ci.org/releases</url>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </repository>
+    </repositories>
 </project>
diff --git a/transport/http/src/main/assembly/windows.xml b/transport/http/src/main/assembly/windows.xml
new file mode 100644
index 0000000..82da34e
--- /dev/null
+++ b/transport/http/src/main/assembly/windows.xml
@@ -0,0 +1,71 @@
+<!--
+
+    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.
+
+-->
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
+    <id>windows</id>
+
+    <formats>
+        <format>zip</format>
+    </formats>
+
+    <!-- Workaround to create logs directory -->
+    <fileSets>
+        <fileSet>
+            <directory>${pkg.win.dist}</directory>
+            <outputDirectory>logs</outputDirectory>
+            <excludes>
+                <exclude>*/**</exclude>
+            </excludes>
+        </fileSet>
+        <fileSet>
+            <directory>${pkg.win.dist}/conf</directory>
+            <outputDirectory>conf</outputDirectory>
+            <lineEnding>windows</lineEnding>
+        </fileSet>
+    </fileSets>
+
+    <files>
+        <file>
+            <source>${project.build.directory}/${project.build.finalName}-boot.${project.packaging}</source>
+            <outputDirectory>lib</outputDirectory>
+            <destName>${pkg.name}.jar</destName>
+        </file>
+        <file>
+            <source>${pkg.win.dist}/service.exe</source>
+            <outputDirectory/>
+            <destName>${pkg.name}.exe</destName>
+        </file>
+        <file>
+            <source>${pkg.win.dist}/service.xml</source>
+            <outputDirectory/>
+            <destName>${pkg.name}.xml</destName>
+            <lineEnding>windows</lineEnding>
+        </file>
+        <file>
+            <source>${pkg.win.dist}/install.bat</source>
+            <outputDirectory/>
+            <lineEnding>windows</lineEnding>
+        </file>
+        <file>
+            <source>${pkg.win.dist}/uninstall.bat</source>
+            <outputDirectory/>
+            <lineEnding>windows</lineEnding>
+        </file>
+    </files>
+</assembly>
diff --git a/transport/http/src/main/conf/logback.xml b/transport/http/src/main/conf/logback.xml
new file mode 100644
index 0000000..f36469d
--- /dev/null
+++ b/transport/http/src/main/conf/logback.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+
+    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.
+
+-->
+<!DOCTYPE configuration>
+<configuration>
+
+    <appender name="fileLogAppender"
+              class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${pkg.logFolder}/${pkg.name}.log</file>
+        <rollingPolicy
+                class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${pkg.logFolder}/${pkg.name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <maxFileSize>100MB</maxFileSize>
+            <maxHistory>30</maxHistory>
+            <totalSizeCap>3GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <logger name="org.thingsboard.server" level="INFO" />
+
+    <root level="INFO">
+        <appender-ref ref="fileLogAppender"/>
+    </root>
+
+</configuration>
diff --git a/transport/http/src/main/conf/tb-http-transport.conf b/transport/http/src/main/conf/tb-http-transport.conf
new file mode 100644
index 0000000..0afa91c
--- /dev/null
+++ b/transport/http/src/main/conf/tb-http-transport.conf
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+
+export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
+export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"
+export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
+export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"
+export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly"
+export LOG_FILENAME=${pkg.name}.out
+export LOADER_PATH=${pkg.installFolder}/conf
diff --git a/transport/http/src/main/filters/unix.properties b/transport/http/src/main/filters/unix.properties
new file mode 100644
index 0000000..8967278
--- /dev/null
+++ b/transport/http/src/main/filters/unix.properties
@@ -0,0 +1 @@
+pkg.logFolder=${pkg.unixLogFolder}
\ No newline at end of file
diff --git a/transport/http/src/main/filters/windows.properties b/transport/http/src/main/filters/windows.properties
new file mode 100644
index 0000000..a6e48d9
--- /dev/null
+++ b/transport/http/src/main/filters/windows.properties
@@ -0,0 +1,2 @@
+pkg.logFolder=${BASE}\\logs
+pkg.winWrapperLogFolder=%BASE%\\logs
diff --git a/transport/http/src/main/resources/logback.xml b/transport/http/src/main/resources/logback.xml
new file mode 100644
index 0000000..18864a9
--- /dev/null
+++ b/transport/http/src/main/resources/logback.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+
+    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.
+
+-->
+<!DOCTYPE configuration>
+<configuration scan="true" scanPeriod="10 seconds">
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <logger name="org.thingsboard.server" level="TRACE" />
+
+    <root level="INFO">
+        <appender-ref ref="STDOUT"/>
+    </root>
+
+</configuration>
\ No newline at end of file
diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml
new file mode 100644
index 0000000..65a1ad9
--- /dev/null
+++ b/transport/http/src/main/resources/tb-http-transport.yml
@@ -0,0 +1,69 @@
+#
+# 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.
+#
+
+server:
+  # Server bind address
+  address: "${HTTP_BIND_ADDRESS:0.0.0.0}"
+  # Server bind port
+  port: "${HTTP_BIND_PORT:8081}"
+
+# HTTP server parameters
+transport:
+  http:
+    request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}"
+
+#Quota parameters
+quota:
+  host:
+    # Max allowed number of API requests in interval for single host
+    limit: "${QUOTA_HOST_LIMIT:10000}"
+    # Interval duration
+    intervalMs: "${QUOTA_HOST_INTERVAL_MS:60000}"
+    # Maximum silence duration for host after which Host removed from QuotaService. Must be bigger than intervalMs
+    ttlMs: "${QUOTA_HOST_TTL_MS:60000}"
+    # Interval for scheduled task that cleans expired records. TTL is used for expiring
+    cleanPeriodMs: "${QUOTA_HOST_CLEAN_PERIOD_MS:300000}"
+    # Enable Host API Limits
+    enabled: "${QUOTA_HOST_ENABLED:true}"
+    # Array of whitelist hosts
+    whitelist: "${QUOTA_HOST_WHITELIST:localhost,127.0.0.1}"
+    # Array of blacklist hosts
+    blacklist: "${QUOTA_HOST_BLACKLIST:}"
+    log:
+      topSize: 10
+      intervalMin: 2
+
+kafka:
+  enabled: true
+  bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"
+  acks: "${TB_KAFKA_ACKS:all}"
+  retries: "${TB_KAFKA_RETRIES:1}"
+  batch.size: "${TB_KAFKA_BATCH_SIZE:16384}"
+  linger.ms: "${TB_KAFKA_LINGER_MS:1}"
+  buffer.memory: "${TB_BUFFER_MEMORY:33554432}"
+  transport_api:
+    requests_topic: "${TB_TRANSPORT_API_REQUEST_TOPIC:tb.transport.api.requests}"
+    responses_topic: "${TB_TRANSPORT_API_RESPONSE_TOPIC:tb.transport.api.responses}"
+    max_pending_requests: "${TB_TRANSPORT_MAX_PENDING_REQUESTS:10000}"
+    max_requests_timeout: "${TB_TRANSPORT_MAX_REQUEST_TIMEOUT:10000}"
+    response_poll_interval: "${TB_TRANSPORT_RESPONSE_POLL_INTERVAL_MS:25}"
+    response_auto_commit_interval: "${TB_TRANSPORT_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}"
+  rule_engine:
+    topic: "${TB_RULE_ENGINE_TOPIC:tb.rule-engine}"
+  notifications:
+    topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}"
+    poll_interval: "${TB_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}"
+    auto_commit_interval: "${TB_TRANSPORT_NOTIFICATIONS_AUTO_COMMIT_INTERVAL_MS:100}"
diff --git a/transport/http/src/main/scripts/control/deb/postinst b/transport/http/src/main/scripts/control/deb/postinst
new file mode 100644
index 0000000..d4066c0
--- /dev/null
+++ b/transport/http/src/main/scripts/control/deb/postinst
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+chown -R ${pkg.name}: ${pkg.logFolder}
+chown -R ${pkg.name}: ${pkg.installFolder}
+update-rc.d ${pkg.name} defaults
+
diff --git a/transport/http/src/main/scripts/control/deb/postrm b/transport/http/src/main/scripts/control/deb/postrm
new file mode 100644
index 0000000..6186580
--- /dev/null
+++ b/transport/http/src/main/scripts/control/deb/postrm
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+update-rc.d -f ${pkg.name} remove
diff --git a/transport/http/src/main/scripts/control/deb/preinst b/transport/http/src/main/scripts/control/deb/preinst
new file mode 100644
index 0000000..6be5959
--- /dev/null
+++ b/transport/http/src/main/scripts/control/deb/preinst
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+if ! getent group ${pkg.name} >/dev/null; then
+    addgroup --system ${pkg.name}
+fi
+
+if ! getent passwd ${pkg.name} >/dev/null; then
+    adduser --quiet \
+            --system \
+            --ingroup ${pkg.name} \
+            --quiet \
+            --disabled-login \
+            --disabled-password \
+            --home ${pkg.installFolder} \
+            --no-create-home \
+            -gecos "Thingsboard application" \
+            ${pkg.name}
+fi
diff --git a/transport/http/src/main/scripts/control/deb/prerm b/transport/http/src/main/scripts/control/deb/prerm
new file mode 100644
index 0000000..898d3ef
--- /dev/null
+++ b/transport/http/src/main/scripts/control/deb/prerm
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+if [ -e /var/run/${pkg.name}/${pkg.name}.pid ]; then
+    service ${pkg.name} stop
+fi
diff --git a/transport/http/src/main/scripts/control/rpm/postinst b/transport/http/src/main/scripts/control/rpm/postinst
new file mode 100644
index 0000000..8a7a88f
--- /dev/null
+++ b/transport/http/src/main/scripts/control/rpm/postinst
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+chown -R ${pkg.name}: ${pkg.logFolder}
+chown -R ${pkg.name}: ${pkg.installFolder}
+
+if [ $1 -eq 1 ] ; then
+        # Initial installation
+        systemctl --no-reload enable ${pkg.name}.service >/dev/null 2>&1 || :
+fi
diff --git a/transport/http/src/main/scripts/control/rpm/postrm b/transport/http/src/main/scripts/control/rpm/postrm
new file mode 100644
index 0000000..8e1f8a2
--- /dev/null
+++ b/transport/http/src/main/scripts/control/rpm/postrm
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+if [ $1 -ge 1 ] ; then
+        # Package upgrade, not uninstall
+        systemctl try-restart ${pkg.name}.service >/dev/null 2>&1 || :
+fi
diff --git a/transport/http/src/main/scripts/control/rpm/preinst b/transport/http/src/main/scripts/control/rpm/preinst
new file mode 100644
index 0000000..e19fc88
--- /dev/null
+++ b/transport/http/src/main/scripts/control/rpm/preinst
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+getent group ${pkg.name} >/dev/null || groupadd -r ${pkg.name}
+getent passwd ${pkg.name} >/dev/null || \
+useradd -d ${pkg.installFolder} -g ${pkg.name} -M -r ${pkg.name} -s /sbin/nologin \
+-c "Thingsboard application"
diff --git a/transport/http/src/main/scripts/control/rpm/prerm b/transport/http/src/main/scripts/control/rpm/prerm
new file mode 100644
index 0000000..accb487
--- /dev/null
+++ b/transport/http/src/main/scripts/control/rpm/prerm
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+if [ $1 -eq 0 ] ; then
+        # Package removal, not upgrade
+        systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :
+fi
diff --git a/transport/http/src/main/scripts/control/tb-http-transport.service b/transport/http/src/main/scripts/control/tb-http-transport.service
new file mode 100644
index 0000000..d456fc0
--- /dev/null
+++ b/transport/http/src/main/scripts/control/tb-http-transport.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=${pkg.name}
+After=syslog.target
+
+[Service]
+User=${pkg.name}
+ExecStart=${pkg.installFolder}/bin/${pkg.name}.jar
+SuccessExitStatus=143
+
+[Install]
+WantedBy=multi-user.target
diff --git a/transport/http/src/main/scripts/windows/install.bat b/transport/http/src/main/scripts/windows/install.bat
new file mode 100644
index 0000000..dba7736
--- /dev/null
+++ b/transport/http/src/main/scripts/windows/install.bat
@@ -0,0 +1,87 @@
+@ECHO OFF
+
+setlocal ENABLEEXTENSIONS
+
+@ECHO Detecting Java version installed.
+:CHECK_JAVA_64
+@ECHO Detecting if it is 64 bit machine
+set KEY_NAME="HKEY_LOCAL_MACHINE\Software\Wow6432Node\JavaSoft\Java Runtime Environment"
+set VALUE_NAME=CurrentVersion
+
+FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
+    set ValueName=%%A
+    set ValueType=%%B
+    set ValueValue=%%C
+)
+@ECHO CurrentVersion %ValueValue%
+
+SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%"
+SET VALUE_NAME=JavaHome
+
+if defined ValueName (
+    FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
+        set ValueName2=%%A
+        set ValueType2=%%B
+        set JRE_PATH2=%%C
+
+        if defined ValueName2 (
+            set ValueName = %ValueName2%
+            set ValueType = %ValueType2%
+            set ValueValue =  %JRE_PATH2%
+        )
+    )
+)
+
+IF NOT "%JRE_PATH2%" == "" GOTO JAVA_INSTALLED
+
+:CHECK_JAVA_32
+@ECHO Detecting if it is 32 bit machine
+set KEY_NAME="HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment"
+set VALUE_NAME=CurrentVersion
+
+FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
+    set ValueName=%%A
+    set ValueType=%%B
+    set ValueValue=%%C
+)
+@ECHO CurrentVersion %ValueValue%
+
+SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%"
+SET VALUE_NAME=JavaHome
+
+if defined ValueName (
+    FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
+        set ValueName2=%%A
+        set ValueType2=%%B
+        set JRE_PATH2=%%C
+
+        if defined ValueName2 (
+            set ValueName = %ValueName2%
+            set ValueType = %ValueType2%
+            set ValueValue =  %JRE_PATH2%
+        )
+    )
+)
+
+IF "%JRE_PATH2%" == ""  GOTO JAVA_NOT_INSTALLED
+
+:JAVA_INSTALLED
+
+@ECHO Java 1.8 found!
+@ECHO Installing ${pkg.name} ...
+
+%BASE%${pkg.name}.exe install
+
+@ECHO ${pkg.name} installed successfully!
+
+GOTO END
+
+:JAVA_NOT_INSTALLED
+@ECHO Java 1.8 or above is not installed
+@ECHO Please go to https://java.com/ and install Java. Then retry installation.
+PAUSE
+GOTO END
+
+:END
+
+
diff --git a/transport/http/src/main/scripts/windows/service.xml b/transport/http/src/main/scripts/windows/service.xml
new file mode 100644
index 0000000..f7b9d30
--- /dev/null
+++ b/transport/http/src/main/scripts/windows/service.xml
@@ -0,0 +1,36 @@
+<service>
+    <id>${pkg.name}</id>
+    <name>${project.name}</name>
+    <description>${project.description}</description>
+    <workingdirectory>%BASE%\conf</workingdirectory>
+    <logpath>${pkg.winWrapperLogFolder}</logpath>
+    <logmode>rotate</logmode>
+    <env name="LOADER_PATH" value="%BASE%\conf" />
+    <executable>java</executable>
+    <startargument>-Xloggc:%BASE%\logs\gc.log</startargument>
+    <startargument>-XX:+HeapDumpOnOutOfMemoryError</startargument>
+    <startargument>-XX:+PrintGCDetails</startargument>
+    <startargument>-XX:+PrintGCDateStamps</startargument>
+    <startargument>-XX:+PrintHeapAtGC</startargument>
+    <startargument>-XX:+PrintTenuringDistribution</startargument>
+    <startargument>-XX:+PrintGCApplicationStoppedTime</startargument>
+    <startargument>-XX:+UseGCLogFileRotation</startargument>
+    <startargument>-XX:NumberOfGCLogFiles=10</startargument>
+    <startargument>-XX:GCLogFileSize=10M</startargument>
+    <startargument>-XX:-UseBiasedLocking</startargument>
+    <startargument>-XX:+UseTLAB</startargument>
+    <startargument>-XX:+ResizeTLAB</startargument>
+    <startargument>-XX:+PerfDisableSharedMem</startargument>
+    <startargument>-XX:+UseCondCardMark</startargument>
+    <startargument>-XX:CMSWaitDuration=10000</startargument>
+    <startargument>-XX:+UseParNewGC</startargument>
+    <startargument>-XX:+UseConcMarkSweepGC</startargument>
+    <startargument>-XX:+CMSParallelRemarkEnabled</startargument>
+    <startargument>-XX:+CMSParallelInitialMarkEnabled</startargument>
+    <startargument>-XX:+CMSEdenChunksRecordAlways</startargument>
+    <startargument>-XX:CMSInitiatingOccupancyFraction=75</startargument>
+    <startargument>-XX:+UseCMSInitiatingOccupancyOnly</startargument>
+    <startargument>-jar</startargument>
+    <startargument>%BASE%\lib\${pkg.name}.jar</startargument>
+
+</service>
diff --git a/transport/http/src/main/scripts/windows/uninstall.bat b/transport/http/src/main/scripts/windows/uninstall.bat
new file mode 100644
index 0000000..921e4c8
--- /dev/null
+++ b/transport/http/src/main/scripts/windows/uninstall.bat
@@ -0,0 +1,9 @@
+@ECHO OFF
+
+@ECHO Stopping ${pkg.name} ...
+net stop ${pkg.name}
+
+@ECHO Uninstalling ${pkg.name} ...
+%~dp0${pkg.name}.exe uninstall
+
+@ECHO DONE.
\ No newline at end of file
diff --git a/transport/mqtt/build.gradle b/transport/mqtt/build.gradle
new file mode 100644
index 0000000..6d54cb4
--- /dev/null
+++ b/transport/mqtt/build.gradle
@@ -0,0 +1,140 @@
+/**
+ * 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.
+ */
+import org.apache.tools.ant.filters.ReplaceTokens
+
+buildscript {
+    ext {
+        osPackageVersion = "3.8.0"
+    }
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath("com.netflix.nebula:gradle-ospackage-plugin:${osPackageVersion}")
+    }
+}
+
+apply plugin: "nebula.ospackage"
+
+buildDir = projectBuildDir
+version = projectVersion
+distsDirName = "./"
+
+// OS Package plugin configuration
+ospackage {
+    packageName = pkgName
+    version = "${project.version}"
+    release = 1
+    os = LINUX
+    type = BINARY
+
+    into pkgInstallFolder
+
+    user pkgName
+    permissionGroup pkgName
+
+    // Copy the actual .jar file
+    from(mainJar) {
+        // Strip the version from the jar filename
+        rename { String fileName ->
+            "${pkgName}.jar"
+        }
+        fileMode 0500
+        into "bin"
+    }
+
+    // Copy the config files
+    from("target/conf") {
+        exclude "${pkgName}.conf"
+        fileType CONFIG | NOREPLACE
+        fileMode 0754
+        into "conf"
+    }
+
+}
+
+// Configure our RPM build task
+buildRpm {
+
+    arch = NOARCH
+
+    version = projectVersion.replace('-', '')
+    archiveName = "${pkgName}.rpm"
+
+    requires("java-1.8.0")
+
+    from("target/conf") {
+        include "${pkgName}.conf"
+        filter(ReplaceTokens, tokens: ['pkg.platform': 'rpm'])
+        fileType CONFIG | NOREPLACE
+        fileMode 0754
+        into "${pkgInstallFolder}/conf"
+    }
+
+    preInstall file("${buildDir}/control/rpm/preinst")
+    postInstall file("${buildDir}/control/rpm/postinst")
+    preUninstall file("${buildDir}/control/rpm/prerm")
+    postUninstall file("${buildDir}/control/rpm/postrm")
+
+    user pkgName
+    permissionGroup pkgName
+
+    // Copy the system unit files
+    from("${buildDir}/control/${pkgName}.service") {
+        addParentDirs = false
+        fileMode 0644
+        into "/usr/lib/systemd/system"
+    }
+
+    directory(pkgLogFolder, 0755)
+    link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml")
+    link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf")
+}
+
+// Same as the buildRpm task
+buildDeb {
+
+    arch = "all"
+
+    archiveName = "${pkgName}.deb"
+
+    requires("openjdk-8-jre").or("java8-runtime").or("oracle-java8-installer").or("openjdk-8-jre-headless")
+
+    from("target/conf") {
+        include "${pkgName}.conf"
+        filter(ReplaceTokens, tokens: ['pkg.platform': 'deb'])
+        fileType CONFIG | NOREPLACE
+        fileMode 0754
+        into "${pkgInstallFolder}/conf"
+    }
+
+    configurationFile("${pkgInstallFolder}/conf/${pkgName}.conf")
+    configurationFile("${pkgInstallFolder}/conf/${pkgName}.yml")
+    configurationFile("${pkgInstallFolder}/conf/logback.xml")
+
+    preInstall file("${buildDir}/control/deb/preinst")
+    postInstall file("${buildDir}/control/deb/postinst")
+    preUninstall file("${buildDir}/control/deb/prerm")
+    postUninstall file("${buildDir}/control/deb/postrm")
+
+    user pkgName
+    permissionGroup pkgName
+
+    directory(pkgLogFolder, 0755)
+    link("/etc/init.d/${pkgName}", "${pkgInstallFolder}/bin/${pkgName}.jar")
+    link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml")
+    link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf")
+}

transport/mqtt/pom.xml 303(+278 -25)

diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml
index 785e9a5..6395b40 100644
--- a/transport/mqtt/pom.xml
+++ b/transport/mqtt/pom.xml
@@ -27,46 +27,37 @@
     <artifactId>mqtt</artifactId>
     <packaging>jar</packaging>
 
-    <name>Thingsboard MQTT Transport</name>
+    <name>Thingsboard MQTT Transport Service</name>
     <url>https://thingsboard.io</url>
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <main.dir>${basedir}/../..</main.dir>
+        <pkg.name>tb-mqtt-transport</pkg.name>
+        <pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
+        <pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
+        <pkg.win.dist>${project.build.directory}/windows</pkg.win.dist>
     </properties>
 
     <dependencies>
         <dependency>
-            <groupId>org.thingsboard.common</groupId>
-            <artifactId>transport</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>io.netty</groupId>
-            <artifactId>netty-all</artifactId>
+            <groupId>org.thingsboard.common.transport</groupId>
+            <artifactId>mqtt</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.springframework</groupId>
-            <artifactId>spring-context</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>log4j-over-slf4j</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>ch.qos.logback</groupId>
-            <artifactId>logback-core</artifactId>
+            <groupId>org.thingsboard.common</groupId>
+            <artifactId>queue</artifactId>
         </dependency>
         <dependency>
-            <groupId>ch.qos.logback</groupId>
-            <artifactId>logback-classic</artifactId>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
         <dependency>
-            <groupId>com.google.guava</groupId>
-            <artifactId>guava</artifactId>
+            <groupId>com.sun.winsw</groupId>
+            <artifactId>winsw</artifactId>
+            <classifier>bin</classifier>
+            <type>exe</type>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
@@ -85,4 +76,266 @@
         </dependency>
     </dependencies>
 
+    <build>
+        <finalName>${pkg.name}-${project.version}</finalName>
+        <resources>
+            <resource>
+                <directory>${project.basedir}/src/main/resources</directory>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-conf</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/conf</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>src/main/resources</directory>
+                                    <excludes>
+                                        <exclude>logback.xml</exclude>
+                                    </excludes>
+                                    <filtering>false</filtering>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>copy-service-conf</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/conf</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>src/main/conf</directory>
+                                    <filtering>true</filtering>
+                                </resource>
+                            </resources>
+                            <filters>
+                                <filter>src/main/filters/unix.properties</filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>copy-win-conf</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${pkg.win.dist}/conf</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>src/main/resources</directory>
+                                    <excludes>
+                                        <exclude>logback.xml</exclude>
+                                    </excludes>
+                                    <filtering>false</filtering>
+                                </resource>
+                                <resource>
+                                    <directory>src/main/conf</directory>
+                                    <excludes>
+                                        <exclude>tb-mqtt-transport.conf</exclude>
+                                    </excludes>
+                                    <filtering>true</filtering>
+                                </resource>
+                            </resources>
+                            <filters>
+                                <filter>src/main/filters/windows.properties</filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>copy-control</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/control</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>src/main/scripts/control</directory>
+                                    <filtering>true</filtering>
+                                </resource>
+                            </resources>
+                            <filters>
+                                <filter>src/main/filters/unix.properties</filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>copy-windows-control</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${pkg.win.dist}</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>src/main/scripts/windows</directory>
+                                    <filtering>true</filtering>
+                                </resource>
+                            </resources>
+                            <filters>
+                                <filter>src/main/filters/windows.properties</filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-winsw-service</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>copy</goal>
+                        </goals>
+                        <configuration>
+                            <artifactItems>
+                                <artifactItem>
+                                    <groupId>com.sun.winsw</groupId>
+                                    <artifactId>winsw</artifactId>
+                                    <classifier>bin</classifier>
+                                    <type>exe</type>
+                                    <destFileName>service.exe</destFileName>
+                                </artifactItem>
+                            </artifactItems>
+                            <outputDirectory>${pkg.win.dist}</outputDirectory>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>**/logback.xml</exclude>
+                    </excludes>
+                    <archive>
+                        <manifestEntries>
+                            <Implementation-Title>ThingsBoard MQTT Transport Service</Implementation-Title>
+                            <Implementation-Version>${project.version}</Implementation-Version>
+                        </manifestEntries>
+                    </archive>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <mainClass>org.thingsboard.server.mqtt.ThingsboardMqttTransportApplication</mainClass>
+                    <classifier>boot</classifier>
+                    <layout>ZIP</layout>
+                    <executable>true</executable>
+                    <excludeDevtools>true</excludeDevtools>
+                    <embeddedLaunchScriptProperties>
+                        <confFolder>${pkg.installFolder}/conf</confFolder>
+                        <logFolder>${pkg.unixLogFolder}</logFolder>
+                        <logFilename>${pkg.name}.out</logFilename>
+                        <initInfoProvides>${pkg.name}</initInfoProvides>
+                    </embeddedLaunchScriptProperties>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.fortasoft</groupId>
+                <artifactId>gradle-maven-plugin</artifactId>
+                <configuration>
+                    <tasks>
+                        <task>build</task>
+                        <task>buildDeb</task>
+                        <task>buildRpm</task>
+                    </tasks>
+                    <args>
+                        <arg>-PprojectBuildDir=${project.build.directory}</arg>
+                        <arg>-PprojectVersion=${project.version}</arg>
+                        <arg>-PmainJar=${project.build.directory}/${project.build.finalName}-boot.${project.packaging}</arg>
+                        <arg>-PpkgName=${pkg.name}</arg>
+                        <arg>-PpkgInstallFolder=${pkg.installFolder}</arg>
+                        <arg>-PpkgLogFolder=${pkg.unixLogFolder}</arg>
+                    </args>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>invoke</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <finalName>${pkg.name}</finalName>
+                    <descriptors>
+                        <descriptor>src/main/assembly/windows.xml</descriptor>
+                    </descriptors>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-install-plugin</artifactId>
+                <configuration>
+                    <file>${project.build.directory}/${pkg.name}.deb</file>
+                    <artifactId>${project.artifactId}</artifactId>
+                    <groupId>${project.groupId}</groupId>
+                    <version>${project.version}</version>
+                    <classifier>deb</classifier>
+                    <packaging>deb</packaging>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>install-deb</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>install-file</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+    <repositories>
+        <repository>
+            <id>jenkins</id>
+            <name>Jenkins Repository</name>
+            <url>http://repo.jenkins-ci.org/releases</url>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </repository>
+    </repositories>
 </project>
diff --git a/transport/mqtt/src/main/assembly/windows.xml b/transport/mqtt/src/main/assembly/windows.xml
new file mode 100644
index 0000000..82da34e
--- /dev/null
+++ b/transport/mqtt/src/main/assembly/windows.xml
@@ -0,0 +1,71 @@
+<!--
+
+    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.
+
+-->
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
+    <id>windows</id>
+
+    <formats>
+        <format>zip</format>
+    </formats>
+
+    <!-- Workaround to create logs directory -->
+    <fileSets>
+        <fileSet>
+            <directory>${pkg.win.dist}</directory>
+            <outputDirectory>logs</outputDirectory>
+            <excludes>
+                <exclude>*/**</exclude>
+            </excludes>
+        </fileSet>
+        <fileSet>
+            <directory>${pkg.win.dist}/conf</directory>
+            <outputDirectory>conf</outputDirectory>
+            <lineEnding>windows</lineEnding>
+        </fileSet>
+    </fileSets>
+
+    <files>
+        <file>
+            <source>${project.build.directory}/${project.build.finalName}-boot.${project.packaging}</source>
+            <outputDirectory>lib</outputDirectory>
+            <destName>${pkg.name}.jar</destName>
+        </file>
+        <file>
+            <source>${pkg.win.dist}/service.exe</source>
+            <outputDirectory/>
+            <destName>${pkg.name}.exe</destName>
+        </file>
+        <file>
+            <source>${pkg.win.dist}/service.xml</source>
+            <outputDirectory/>
+            <destName>${pkg.name}.xml</destName>
+            <lineEnding>windows</lineEnding>
+        </file>
+        <file>
+            <source>${pkg.win.dist}/install.bat</source>
+            <outputDirectory/>
+            <lineEnding>windows</lineEnding>
+        </file>
+        <file>
+            <source>${pkg.win.dist}/uninstall.bat</source>
+            <outputDirectory/>
+            <lineEnding>windows</lineEnding>
+        </file>
+    </files>
+</assembly>
diff --git a/transport/mqtt/src/main/conf/logback.xml b/transport/mqtt/src/main/conf/logback.xml
new file mode 100644
index 0000000..f36469d
--- /dev/null
+++ b/transport/mqtt/src/main/conf/logback.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+
+    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.
+
+-->
+<!DOCTYPE configuration>
+<configuration>
+
+    <appender name="fileLogAppender"
+              class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${pkg.logFolder}/${pkg.name}.log</file>
+        <rollingPolicy
+                class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${pkg.logFolder}/${pkg.name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <maxFileSize>100MB</maxFileSize>
+            <maxHistory>30</maxHistory>
+            <totalSizeCap>3GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <logger name="org.thingsboard.server" level="INFO" />
+
+    <root level="INFO">
+        <appender-ref ref="fileLogAppender"/>
+    </root>
+
+</configuration>
diff --git a/transport/mqtt/src/main/conf/tb-mqtt-transport.conf b/transport/mqtt/src/main/conf/tb-mqtt-transport.conf
new file mode 100644
index 0000000..0afa91c
--- /dev/null
+++ b/transport/mqtt/src/main/conf/tb-mqtt-transport.conf
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+
+export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
+export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"
+export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
+export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"
+export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly"
+export LOG_FILENAME=${pkg.name}.out
+export LOADER_PATH=${pkg.installFolder}/conf
diff --git a/transport/mqtt/src/main/filters/unix.properties b/transport/mqtt/src/main/filters/unix.properties
new file mode 100644
index 0000000..8967278
--- /dev/null
+++ b/transport/mqtt/src/main/filters/unix.properties
@@ -0,0 +1 @@
+pkg.logFolder=${pkg.unixLogFolder}
\ No newline at end of file
diff --git a/transport/mqtt/src/main/filters/windows.properties b/transport/mqtt/src/main/filters/windows.properties
new file mode 100644
index 0000000..a6e48d9
--- /dev/null
+++ b/transport/mqtt/src/main/filters/windows.properties
@@ -0,0 +1,2 @@
+pkg.logFolder=${BASE}\\logs
+pkg.winWrapperLogFolder=%BASE%\\logs
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/mqtt/ThingsboardMqttTransportApplication.java b/transport/mqtt/src/main/java/org/thingsboard/server/mqtt/ThingsboardMqttTransportApplication.java
new file mode 100644
index 0000000..2bae2ff
--- /dev/null
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/mqtt/ThingsboardMqttTransportApplication.java
@@ -0,0 +1,49 @@
+/**
+ * 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.mqtt;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+import java.util.Arrays;
+
+@SpringBootConfiguration
+@EnableAsync
+@EnableScheduling
+@ComponentScan({"org.thingsboard.server.mqtt", "org.thingsboard.server.common", "org.thingsboard.server.transport.mqtt", "org.thingsboard.server.kafka"})
+public class ThingsboardMqttTransportApplication {
+
+    private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name";
+    private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "tb-mqtt-transport";
+
+    public static void main(String[] args) {
+        SpringApplication.run(ThingsboardMqttTransportApplication.class, updateArguments(args));
+    }
+
+    private static String[] updateArguments(String[] args) {
+        if (Arrays.stream(args).noneMatch(arg -> arg.startsWith(SPRING_CONFIG_NAME_KEY))) {
+            String[] modifiedArgs = new String[args.length + 1];
+            System.arraycopy(args, 0, modifiedArgs, 0, args.length);
+            modifiedArgs[args.length] = DEFAULT_SPRING_CONFIG_PARAM;
+            return modifiedArgs;
+        }
+        return args;
+    }
+}
diff --git a/transport/mqtt/src/main/resources/logback.xml b/transport/mqtt/src/main/resources/logback.xml
new file mode 100644
index 0000000..18864a9
--- /dev/null
+++ b/transport/mqtt/src/main/resources/logback.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+
+    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.
+
+-->
+<!DOCTYPE configuration>
+<configuration scan="true" scanPeriod="10 seconds">
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <logger name="org.thingsboard.server" level="TRACE" />
+
+    <root level="INFO">
+        <appender-ref ref="STDOUT"/>
+    </root>
+
+</configuration>
\ No newline at end of file
diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml
new file mode 100644
index 0000000..a0e86bb
--- /dev/null
+++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml
@@ -0,0 +1,95 @@
+#
+# 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.
+#
+
+spring.main.web-environment: false
+spring.main.web-application-type: none
+
+# MQTT server parameters
+transport:
+  mqtt:
+    bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}"
+    bind_port: "${MQTT_BIND_PORT:1883}"
+    adaptor: "${MQTT_ADAPTOR_NAME:JsonMqttAdaptor}"
+    timeout: "${MQTT_TIMEOUT:10000}"
+    netty:
+      leak_detector_level: "${NETTY_LEAK_DETECTOR_LVL:DISABLED}"
+      boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}"
+      worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}"
+      max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}"
+    # MQTT SSL configuration
+    ssl:
+      # Enable/disable SSL support
+      enabled: "${MQTT_SSL_ENABLED:false}"
+      # SSL protocol: See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext
+      protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}"
+      # Path to the key store that holds the SSL certificate
+      key_store: "${MQTT_SSL_KEY_STORE:mqttserver.jks}"
+      # Password used to access the key store
+      key_store_password: "${MQTT_SSL_KEY_STORE_PASSWORD:server_ks_password}"
+      # Password used to access the key
+      key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}"
+      # Type of the key store
+      key_store_type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}"
+  sessions:
+    max_per_tenant: "${TB_TRANSPORT_SESSIONS_MAX_PER_TENANT:1000}"
+    max_per_device: "${TB_TRANSPORT_SESSIONS_MAX_PER_DEVICE:2}"
+  rate_limits:
+    enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}"
+    tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}"
+    device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}"
+
+#Quota parameters
+quota:
+  host:
+    # Max allowed number of API requests in interval for single host
+    limit: "${QUOTA_HOST_LIMIT:10000}"
+    # Interval duration
+    intervalMs: "${QUOTA_HOST_INTERVAL_MS:60000}"
+    # Maximum silence duration for host after which Host removed from QuotaService. Must be bigger than intervalMs
+    ttlMs: "${QUOTA_HOST_TTL_MS:60000}"
+    # Interval for scheduled task that cleans expired records. TTL is used for expiring
+    cleanPeriodMs: "${QUOTA_HOST_CLEAN_PERIOD_MS:300000}"
+    # Enable Host API Limits
+    enabled: "${QUOTA_HOST_ENABLED:true}"
+    # Array of whitelist hosts
+    whitelist: "${QUOTA_HOST_WHITELIST:localhost,127.0.0.1}"
+    # Array of blacklist hosts
+    blacklist: "${QUOTA_HOST_BLACKLIST:}"
+    log:
+      topSize: 10
+      intervalMin: 2
+
+kafka:
+  enabled: true
+  bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"
+  acks: "${TB_KAFKA_ACKS:all}"
+  retries: "${TB_KAFKA_RETRIES:1}"
+  batch.size: "${TB_KAFKA_BATCH_SIZE:16384}"
+  linger.ms: "${TB_KAFKA_LINGER_MS:1}"
+  buffer.memory: "${TB_BUFFER_MEMORY:33554432}"
+  transport_api:
+    requests_topic: "${TB_TRANSPORT_API_REQUEST_TOPIC:tb.transport.api.requests}"
+    responses_topic: "${TB_TRANSPORT_API_RESPONSE_TOPIC:tb.transport.api.responses}"
+    max_pending_requests: "${TB_TRANSPORT_MAX_PENDING_REQUESTS:10000}"
+    max_requests_timeout: "${TB_TRANSPORT_MAX_REQUEST_TIMEOUT:10000}"
+    response_poll_interval: "${TB_TRANSPORT_RESPONSE_POLL_INTERVAL_MS:25}"
+    response_auto_commit_interval: "${TB_TRANSPORT_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}"
+  rule_engine:
+    topic: "${TB_RULE_ENGINE_TOPIC:tb.rule-engine}"
+  notifications:
+    topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}"
+    poll_interval: "${TB_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}"
+    auto_commit_interval: "${TB_TRANSPORT_NOTIFICATIONS_AUTO_COMMIT_INTERVAL_MS:100}"
diff --git a/transport/mqtt/src/main/scripts/control/deb/postinst b/transport/mqtt/src/main/scripts/control/deb/postinst
new file mode 100644
index 0000000..d4066c0
--- /dev/null
+++ b/transport/mqtt/src/main/scripts/control/deb/postinst
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+chown -R ${pkg.name}: ${pkg.logFolder}
+chown -R ${pkg.name}: ${pkg.installFolder}
+update-rc.d ${pkg.name} defaults
+
diff --git a/transport/mqtt/src/main/scripts/control/deb/postrm b/transport/mqtt/src/main/scripts/control/deb/postrm
new file mode 100644
index 0000000..6186580
--- /dev/null
+++ b/transport/mqtt/src/main/scripts/control/deb/postrm
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+update-rc.d -f ${pkg.name} remove
diff --git a/transport/mqtt/src/main/scripts/control/deb/preinst b/transport/mqtt/src/main/scripts/control/deb/preinst
new file mode 100644
index 0000000..6be5959
--- /dev/null
+++ b/transport/mqtt/src/main/scripts/control/deb/preinst
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+if ! getent group ${pkg.name} >/dev/null; then
+    addgroup --system ${pkg.name}
+fi
+
+if ! getent passwd ${pkg.name} >/dev/null; then
+    adduser --quiet \
+            --system \
+            --ingroup ${pkg.name} \
+            --quiet \
+            --disabled-login \
+            --disabled-password \
+            --home ${pkg.installFolder} \
+            --no-create-home \
+            -gecos "Thingsboard application" \
+            ${pkg.name}
+fi
diff --git a/transport/mqtt/src/main/scripts/control/deb/prerm b/transport/mqtt/src/main/scripts/control/deb/prerm
new file mode 100644
index 0000000..898d3ef
--- /dev/null
+++ b/transport/mqtt/src/main/scripts/control/deb/prerm
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+if [ -e /var/run/${pkg.name}/${pkg.name}.pid ]; then
+    service ${pkg.name} stop
+fi
diff --git a/transport/mqtt/src/main/scripts/control/rpm/postinst b/transport/mqtt/src/main/scripts/control/rpm/postinst
new file mode 100644
index 0000000..8a7a88f
--- /dev/null
+++ b/transport/mqtt/src/main/scripts/control/rpm/postinst
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+chown -R ${pkg.name}: ${pkg.logFolder}
+chown -R ${pkg.name}: ${pkg.installFolder}
+
+if [ $1 -eq 1 ] ; then
+        # Initial installation
+        systemctl --no-reload enable ${pkg.name}.service >/dev/null 2>&1 || :
+fi
diff --git a/transport/mqtt/src/main/scripts/control/rpm/postrm b/transport/mqtt/src/main/scripts/control/rpm/postrm
new file mode 100644
index 0000000..8e1f8a2
--- /dev/null
+++ b/transport/mqtt/src/main/scripts/control/rpm/postrm
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+if [ $1 -ge 1 ] ; then
+        # Package upgrade, not uninstall
+        systemctl try-restart ${pkg.name}.service >/dev/null 2>&1 || :
+fi
diff --git a/transport/mqtt/src/main/scripts/control/rpm/preinst b/transport/mqtt/src/main/scripts/control/rpm/preinst
new file mode 100644
index 0000000..e19fc88
--- /dev/null
+++ b/transport/mqtt/src/main/scripts/control/rpm/preinst
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+getent group ${pkg.name} >/dev/null || groupadd -r ${pkg.name}
+getent passwd ${pkg.name} >/dev/null || \
+useradd -d ${pkg.installFolder} -g ${pkg.name} -M -r ${pkg.name} -s /sbin/nologin \
+-c "Thingsboard application"
diff --git a/transport/mqtt/src/main/scripts/control/rpm/prerm b/transport/mqtt/src/main/scripts/control/rpm/prerm
new file mode 100644
index 0000000..accb487
--- /dev/null
+++ b/transport/mqtt/src/main/scripts/control/rpm/prerm
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+if [ $1 -eq 0 ] ; then
+        # Package removal, not upgrade
+        systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :
+fi
diff --git a/transport/mqtt/src/main/scripts/control/tb-mqtt-transport.service b/transport/mqtt/src/main/scripts/control/tb-mqtt-transport.service
new file mode 100644
index 0000000..d456fc0
--- /dev/null
+++ b/transport/mqtt/src/main/scripts/control/tb-mqtt-transport.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=${pkg.name}
+After=syslog.target
+
+[Service]
+User=${pkg.name}
+ExecStart=${pkg.installFolder}/bin/${pkg.name}.jar
+SuccessExitStatus=143
+
+[Install]
+WantedBy=multi-user.target
diff --git a/transport/mqtt/src/main/scripts/windows/install.bat b/transport/mqtt/src/main/scripts/windows/install.bat
new file mode 100644
index 0000000..dba7736
--- /dev/null
+++ b/transport/mqtt/src/main/scripts/windows/install.bat
@@ -0,0 +1,87 @@
+@ECHO OFF
+
+setlocal ENABLEEXTENSIONS
+
+@ECHO Detecting Java version installed.
+:CHECK_JAVA_64
+@ECHO Detecting if it is 64 bit machine
+set KEY_NAME="HKEY_LOCAL_MACHINE\Software\Wow6432Node\JavaSoft\Java Runtime Environment"
+set VALUE_NAME=CurrentVersion
+
+FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
+    set ValueName=%%A
+    set ValueType=%%B
+    set ValueValue=%%C
+)
+@ECHO CurrentVersion %ValueValue%
+
+SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%"
+SET VALUE_NAME=JavaHome
+
+if defined ValueName (
+    FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
+        set ValueName2=%%A
+        set ValueType2=%%B
+        set JRE_PATH2=%%C
+
+        if defined ValueName2 (
+            set ValueName = %ValueName2%
+            set ValueType = %ValueType2%
+            set ValueValue =  %JRE_PATH2%
+        )
+    )
+)
+
+IF NOT "%JRE_PATH2%" == "" GOTO JAVA_INSTALLED
+
+:CHECK_JAVA_32
+@ECHO Detecting if it is 32 bit machine
+set KEY_NAME="HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment"
+set VALUE_NAME=CurrentVersion
+
+FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
+    set ValueName=%%A
+    set ValueType=%%B
+    set ValueValue=%%C
+)
+@ECHO CurrentVersion %ValueValue%
+
+SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%"
+SET VALUE_NAME=JavaHome
+
+if defined ValueName (
+    FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
+        set ValueName2=%%A
+        set ValueType2=%%B
+        set JRE_PATH2=%%C
+
+        if defined ValueName2 (
+            set ValueName = %ValueName2%
+            set ValueType = %ValueType2%
+            set ValueValue =  %JRE_PATH2%
+        )
+    )
+)
+
+IF "%JRE_PATH2%" == ""  GOTO JAVA_NOT_INSTALLED
+
+:JAVA_INSTALLED
+
+@ECHO Java 1.8 found!
+@ECHO Installing ${pkg.name} ...
+
+%BASE%${pkg.name}.exe install
+
+@ECHO ${pkg.name} installed successfully!
+
+GOTO END
+
+:JAVA_NOT_INSTALLED
+@ECHO Java 1.8 or above is not installed
+@ECHO Please go to https://java.com/ and install Java. Then retry installation.
+PAUSE
+GOTO END
+
+:END
+
+
diff --git a/transport/mqtt/src/main/scripts/windows/service.xml b/transport/mqtt/src/main/scripts/windows/service.xml
new file mode 100644
index 0000000..f7b9d30
--- /dev/null
+++ b/transport/mqtt/src/main/scripts/windows/service.xml
@@ -0,0 +1,36 @@
+<service>
+    <id>${pkg.name}</id>
+    <name>${project.name}</name>
+    <description>${project.description}</description>
+    <workingdirectory>%BASE%\conf</workingdirectory>
+    <logpath>${pkg.winWrapperLogFolder}</logpath>
+    <logmode>rotate</logmode>
+    <env name="LOADER_PATH" value="%BASE%\conf" />
+    <executable>java</executable>
+    <startargument>-Xloggc:%BASE%\logs\gc.log</startargument>
+    <startargument>-XX:+HeapDumpOnOutOfMemoryError</startargument>
+    <startargument>-XX:+PrintGCDetails</startargument>
+    <startargument>-XX:+PrintGCDateStamps</startargument>
+    <startargument>-XX:+PrintHeapAtGC</startargument>
+    <startargument>-XX:+PrintTenuringDistribution</startargument>
+    <startargument>-XX:+PrintGCApplicationStoppedTime</startargument>
+    <startargument>-XX:+UseGCLogFileRotation</startargument>
+    <startargument>-XX:NumberOfGCLogFiles=10</startargument>
+    <startargument>-XX:GCLogFileSize=10M</startargument>
+    <startargument>-XX:-UseBiasedLocking</startargument>
+    <startargument>-XX:+UseTLAB</startargument>
+    <startargument>-XX:+ResizeTLAB</startargument>
+    <startargument>-XX:+PerfDisableSharedMem</startargument>
+    <startargument>-XX:+UseCondCardMark</startargument>
+    <startargument>-XX:CMSWaitDuration=10000</startargument>
+    <startargument>-XX:+UseParNewGC</startargument>
+    <startargument>-XX:+UseConcMarkSweepGC</startargument>
+    <startargument>-XX:+CMSParallelRemarkEnabled</startargument>
+    <startargument>-XX:+CMSParallelInitialMarkEnabled</startargument>
+    <startargument>-XX:+CMSEdenChunksRecordAlways</startargument>
+    <startargument>-XX:CMSInitiatingOccupancyFraction=75</startargument>
+    <startargument>-XX:+UseCMSInitiatingOccupancyOnly</startargument>
+    <startargument>-jar</startargument>
+    <startargument>%BASE%\lib\${pkg.name}.jar</startargument>
+
+</service>
diff --git a/transport/mqtt/src/main/scripts/windows/uninstall.bat b/transport/mqtt/src/main/scripts/windows/uninstall.bat
new file mode 100644
index 0000000..921e4c8
--- /dev/null
+++ b/transport/mqtt/src/main/scripts/windows/uninstall.bat
@@ -0,0 +1,9 @@
+@ECHO OFF
+
+@ECHO Stopping ${pkg.name} ...
+net stop ${pkg.name}
+
+@ECHO Uninstalling ${pkg.name} ...
+%~dp0${pkg.name}.exe uninstall
+
+@ECHO DONE.
\ No newline at end of file

transport/pom.xml 18(+1 -17)

diff --git a/transport/pom.xml b/transport/pom.xml
index 95b0112..0bb9300 100644
--- a/transport/pom.xml
+++ b/transport/pom.xml
@@ -23,7 +23,6 @@
         <version>2.2.0-SNAPSHOT</version>
         <artifactId>thingsboard</artifactId>
     </parent>
-    <groupId>org.thingsboard</groupId>
     <artifactId>transport</artifactId>
     <packaging>pom</packaging>
 
@@ -36,23 +35,8 @@
 
     <modules>
         <module>http</module>
-        <module>coap</module>
         <module>mqtt</module>
+        <module>coap</module>
     </modules>
 
-    <dependencies>
-        <dependency>
-            <groupId>org.thingsboard</groupId>
-            <artifactId>dao</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-autoconfigure</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.bouncycastle</groupId>
-            <artifactId>bcprov-jdk15on</artifactId>
-        </dependency>
-    </dependencies>
-
 </project>
diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js
index 205abc9..00c7ecf 100644
--- a/ui/src/app/api/entity.service.js
+++ b/ui/src/app/api/entity.service.js
@@ -135,6 +135,10 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
             case types.entityType.asset:
                 promise = assetService.getAssets(entityIds, config);
                 break;
+            case types.entityType.entityView:
+                promise = getEntitiesByIdsPromise(
+                    (id) => entityViewService.getEntityView(id, config), entityIds);
+                break;
             case types.entityType.tenant:
                 promise = getEntitiesByIdsPromise(
                     (id) => tenantService.getTenant(id, config), entityIds);
diff --git a/ui/src/app/api/entity-view.service.js b/ui/src/app/api/entity-view.service.js
index e34d3a4..23fefbc 100644
--- a/ui/src/app/api/entity-view.service.js
+++ b/ui/src/app/api/entity-view.service.js
@@ -27,7 +27,6 @@ function EntityViewService($http, $q, $window, userService, attributeService, cu
         deleteEntityView: deleteEntityView,
         getCustomerEntityViews: getCustomerEntityViews,
         getEntityView: getEntityView,
-        getEntityViews: getEntityViews,
         getTenantEntityViews: getTenantEntityViews,
         saveEntityView: saveEntityView,
         unassignEntityViewFromCustomer: unassignEntityViewFromCustomer,
@@ -126,32 +125,6 @@ function EntityViewService($http, $q, $window, userService, attributeService, cu
         return deferred.promise;
     }
 
-    function getEntityViews(entityViewIds, config) {
-        var deferred = $q.defer();
-        var ids = '';
-        for (var i=0;i<entityViewIds.length;i++) {
-            if (i>0) {
-                ids += ',';
-            }
-            ids += entityViewIds[i];
-        }
-        var url = '/api/entityViews?entityViewIds=' + ids;
-        $http.get(url, config).then(function success(response) {
-            var entityViews = response.data;
-            entityViews.sort(function (entityView1, entityView2) {
-               var id1 =  entityView1.id.id;
-               var id2 =  entityView2.id.id;
-               var index1 = entityViewIds.indexOf(id1);
-               var index2 = entityViewIds.indexOf(id2);
-               return index1 - index2;
-            });
-            deferred.resolve(entityViews);
-        }, function fail(response) {
-            deferred.reject(response.data);
-        });
-        return deferred.promise;
-    }
-
     function saveEntityView(entityView) {
         var deferred = $q.defer();
         var url = '/api/entityView';
diff --git a/ui/src/app/entity-view/add-entity-view.tpl.html b/ui/src/app/entity-view/add-entity-view.tpl.html
index 48a1788..ebf1f1d 100644
--- a/ui/src/app/entity-view/add-entity-view.tpl.html
+++ b/ui/src/app/entity-view/add-entity-view.tpl.html
@@ -15,7 +15,7 @@
     limitations under the License.
 
 -->
-<md-dialog aria-label="{{ 'entity-view.add' | translate }}" tb-help="'entityViews'" help-container-id="help-container">
+<md-dialog aria-label="{{ 'entity-view.add' | translate }}" style="width: 800px;" tb-help="'entityViews'" help-container-id="help-container">
 	<form name="theForm" ng-submit="vm.add()">
 	    <md-toolbar>
 	      <div class="md-toolbar-tools">
diff --git a/ui/src/app/entity-view/entity-view.directive.js b/ui/src/app/entity-view/entity-view.directive.js
index e1ae82f..25377f4 100644
--- a/ui/src/app/entity-view/entity-view.directive.js
+++ b/ui/src/app/entity-view/entity-view.directive.js
@@ -13,6 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+import './entity-view.scss';
+
 /* eslint-disable import/no-unresolved, import/default */
 
 import entityViewFieldsetTemplate from './entity-view-fieldset.tpl.html';
@@ -20,12 +23,16 @@ import entityViewFieldsetTemplate from './entity-view-fieldset.tpl.html';
 /* eslint-enable import/no-unresolved, import/default */
 
 /*@ngInject*/
-export default function EntityViewDirective($compile, $templateCache, $filter, toast, $translate, $mdConstant,
-                                            types, clipboardService, entityViewService, customerService) {
+export default function EntityViewDirective($q, $compile, $templateCache, $filter, toast, $translate, $mdConstant, $mdExpansionPanel,
+                                            types, clipboardService, entityViewService, customerService, entityService) {
     var linker = function (scope, element) {
         var template = $templateCache.get(entityViewFieldsetTemplate);
         element.html(template);
 
+        scope.attributesPanelId = (Math.random()*1000).toFixed(0);
+        scope.timeseriesPanelId = (Math.random()*1000).toFixed(0);
+        scope.$mdExpansionPanel = $mdExpansionPanel;
+
         scope.types = types;
         scope.isAssignedToCustomer = false;
         scope.isPublic = false;
@@ -53,9 +60,13 @@ export default function EntityViewDirective($compile, $templateCache, $filter, t
                 }
                 if (scope.entityView.startTimeMs > 0) {
                     scope.startTimeMs = new Date(scope.entityView.startTimeMs);
+                } else {
+                    scope.startTimeMs = null;
                 }
                 if (scope.entityView.endTimeMs > 0) {
                     scope.endTimeMs = new Date(scope.entityView.endTimeMs);
+                } else {
+                    scope.endTimeMs = null;
                 }
                 if (!scope.entityView.keys) {
                     scope.entityView.keys = {};
@@ -68,6 +79,19 @@ export default function EntityViewDirective($compile, $templateCache, $filter, t
             }
         });
 
+        scope.dataKeysSearch = function (searchText, type) {
+            var deferred = $q.defer();
+            entityService.getEntityKeys(scope.entityView.entityId.entityType, scope.entityView.entityId.id, searchText, type, {ignoreLoading: true}).then(
+                function success(keys) {
+                    deferred.resolve(keys);
+                },
+                function fail() {
+                    deferred.resolve([]);
+                }
+            );
+            return deferred.promise;
+
+        };
 
         scope.$watch('startTimeMs', function (newDate) {
             if (newDate) {
diff --git a/ui/src/app/entity-view/entity-view-fieldset.tpl.html b/ui/src/app/entity-view/entity-view-fieldset.tpl.html
index 66dc116..766f8c8 100644
--- a/ui/src/app/entity-view/entity-view-fieldset.tpl.html
+++ b/ui/src/app/entity-view/entity-view-fieldset.tpl.html
@@ -60,7 +60,7 @@
                 entity-type="types.entityType.entityView">
         </tb-entity-subtype-autocomplete>
         <section layout="column">
-            <label translate class="tb-title no-padding">entity-view.related-entity</label>
+            <label translate class="tb-title no-padding">entity-view.target-entity</label>
             <tb-entity-select flex ng-disabled="!isEdit"
                               the-form="theForm"
                               tb-required="true"
@@ -68,62 +68,145 @@
                               ng-model="entityView.entityId">
             </tb-entity-select>
         </section>
+        <md-expansion-panel-group class="tb-entity-view-panel-group" ng-class="{'disabled': $root.loading || !isEdit}"
+                                  auto-expand="true"
+                                  multiple="true"
+                                  md-component-id="attributesPanelGroup">
+            <md-expansion-panel md-component-id="{{attributesPanelId}}">
+                <md-expansion-panel-collapsed>
+                    <div class="tb-panel-title">{{ 'entity-view.attributes-propagation' | 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(attributesPanelId).collapse()">
+                        <div class="tb-panel-title">{{ 'entity-view.attributes-propagation' | translate }}</div>
+                        <span flex></span>
+                        <md-expansion-panel-icon></md-expansion-panel-icon>
+                    </md-expansion-panel-header>
+                    <md-expansion-panel-content>
+                        <div translate class="tb-hint">entity-view.attributes-propagation-hint</div>
+                        <label translate class="tb-title no-padding">entity-view.client-attributes</label>
+                        <md-chips style="padding-bottom: 15px;"
+                                  ng-required="false"
+                                  readonly="!isEdit"
+                                  ng-model="entityView.keys.attributes.cs"
+                                  placeholder="{{'entity-view.client-attributes-placeholder' | translate}}"
+                                  md-separator-keys="separatorKeys">
+                            <md-autocomplete
+                                    md-no-cache="true"
+                                    id="ca_datakey"
+                                    md-selected-item="selectedAttributeDataKey"
+                                    md-search-text="attributeDataKeySearchText"
+                                    md-items="item in dataKeysSearch(attributeDataKeySearchText, types.dataKeyType.attribute)"
+                                    md-item-text="item.name"
+                                    md-min-length="0"
+                                    placeholder="{{'entity-view.client-attributes-placeholder' | translate }}"
+                                    md-menu-class="tb-attribute-datakey-autocomplete">
+                                <span md-highlight-text="attributeDataKeySearchText" md-highlight-flags="^i">{{item}}</span>
+                            </md-autocomplete>
+                        </md-chips>
+                        <label translate class="tb-title no-padding">entity-view.shared-attributes</label>
+                        <md-chips style="padding-bottom: 15px;"
+                                  ng-required="false"
+                                  readonly="!isEdit"
+                                  ng-model="entityView.keys.attributes.sh"
+                                  placeholder="{{'entity-view.shared-attributes-placeholder' | translate}}"
+                                  md-separator-keys="separatorKeys">
+                            <md-autocomplete
+                                    md-no-cache="true"
+                                    id="sh_datakey"
+                                    md-selected-item="selectedAttributeDataKey"
+                                    md-search-text="attributeDataKeySearchText"
+                                    md-items="item in dataKeysSearch(attributeDataKeySearchText, types.dataKeyType.attribute)"
+                                    md-item-text="item.name"
+                                    md-min-length="0"
+                                    placeholder="{{'entity-view.shared-attributes-placeholder' | translate }}"
+                                    md-menu-class="tb-attribute-datakey-autocomplete">
+                                <span md-highlight-text="attributeDataKeySearchText" md-highlight-flags="^i">{{item}}</span>
+                            </md-autocomplete>
+                        </md-chips>
+                        <label translate class="tb-title no-padding">entity-view.server-attributes</label>
+                        <md-chips style="padding-bottom: 15px;"
+                                  ng-required="false"
+                                  readonly="!isEdit"
+                                  ng-model="entityView.keys.attributes.ss"
+                                  placeholder="{{'entity-view.server-attributes-placeholder' | translate}}"
+                                  md-separator-keys="separatorKeys">
+                            <md-autocomplete
+                                    md-no-cache="true"
+                                    id="ss_datakey"
+                                    md-selected-item="selectedAttributeDataKey"
+                                    md-search-text="attributeDataKeySearchText"
+                                    md-items="item in dataKeysSearch(attributeDataKeySearchText, types.dataKeyType.attribute)"
+                                    md-item-text="item.name"
+                                    md-min-length="0"
+                                    placeholder="{{'entity-view.server-attributes-placeholder' | translate }}"
+                                    md-menu-class="tb-attribute-datakey-autocomplete">
+                                <span md-highlight-text="attributeDataKeySearchText" md-highlight-flags="^i">{{item}}</span>
+                            </md-autocomplete>
+                        </md-chips>
+                    </md-expansion-panel-content>
+                </md-expansion-panel-expanded>
+            </md-expansion-panel>
+            <md-expansion-panel md-component-id="{{timeseriesPanelId}}">
+                <md-expansion-panel-collapsed>
+                    <div class="tb-panel-title">{{ 'entity-view.timeseries-data' | 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(timeseriesPanelId).collapse()">
+                        <div class="tb-panel-title">{{ 'entity-view.timeseries-data' | translate }}</div>
+                        <span flex></span>
+                        <md-expansion-panel-icon></md-expansion-panel-icon>
+                    </md-expansion-panel-header>
+                    <md-expansion-panel-content>
+                        <div translate class="tb-hint">entity-view.timeseries-data-hint</div>
+                        <label translate class="tb-title no-padding">entity-view.timeseries</label>
+                        <md-chips ng-required="false"
+                                  readonly="!isEdit"
+                                  ng-model="entityView.keys.timeseries"
+                                  placeholder="{{'entity-view.timeseries-placeholder' | translate}}"
+                                  md-separator-keys="separatorKeys">
+                            <md-autocomplete
+                                    md-no-cache="true"
+                                    id="timeseries_datakey"
+                                    md-selected-item="selectedTimeseriesDataKey"
+                                    md-search-text="timeseriesDataKeySearchText"
+                                    md-items="item in dataKeysSearch(timeseriesDataKeySearchText, types.dataKeyType.timeseries)"
+                                    md-item-text="item.name"
+                                    md-min-length="0"
+                                    placeholder="{{'entity-view.timeseries-placeholder' | translate }}"
+                                    md-menu-class="tb-timeseries-datakey-autocomplete">
+                                <span md-highlight-text="timeseriesDataKeySearchText" md-highlight-flags="^i">{{item}}</span>
+                            </md-autocomplete>
+                        </md-chips>
+                    </md-expansion-panel-content>
+                </md-expansion-panel-expanded>
+            </md-expansion-panel>
+        </md-expansion-panel-group>
+        <section layout="row" layout-align="start start">
+            <mdp-date-picker ng-model="startTimeMs"
+                             mdp-max-date="maxStartTimeMs"
+                             mdp-placeholder="{{ 'entity-view.start-date' | translate }}"></mdp-date-picker>
+            <mdp-time-picker ng-model="startTimeMs"
+                             mdp-max-date="maxStartTimeMs"
+                             mdp-placeholder="{{ 'entity-view.start-ts' | translate }}"
+                             mdp-auto-switch="true"></mdp-time-picker>
+        </section>
+        <section layout="row" layout-align="start start">
+            <mdp-date-picker ng-model="endTimeMs"
+                             mdp-min-date="minEndTimeMs"
+                             mdp-placeholder="{{ 'entity-view.end-date' | translate }}"></mdp-date-picker>
+            <mdp-time-picker ng-model="endTimeMs"
+                             mdp-min-date="minEndTimeMs"
+                             mdp-placeholder="{{ 'entity-view.end-ts' | translate }}"
+                             mdp-auto-switch="true"></mdp-time-picker>
+        </section>
         <md-input-container class="md-block">
             <label translate>entity-view.description</label>
             <textarea ng-model="entityView.additionalInfo.description" rows="2"></textarea>
         </md-input-container>
-        <section layout="column">
-            <label translate class="tb-title no-padding">entity-view.client-attributes</label>
-            <md-chips style="padding-bottom: 15px;"
-                      ng-required="false"
-                      readonly="!isEdit"
-                      ng-model="entityView.keys.attributes.cs"
-                      placeholder="{{'entity-view.client-attributes' | translate}}"
-                      md-separator-keys="separatorKeys">
-            </md-chips>
-            <label translate class="tb-title no-padding">entity-view.shared-attributes</label>
-            <md-chips style="padding-bottom: 15px;"
-                      ng-required="false"
-                      readonly="!isEdit"
-                      ng-model="entityView.keys.attributes.sh"
-                      placeholder="{{'entity-view.shared-attributes' | translate}}"
-                      md-separator-keys="separatorKeys">
-            </md-chips>
-            <label translate class="tb-title no-padding">entity-view.server-attributes</label>
-            <md-chips style="padding-bottom: 15px;"
-                      ng-required="false"
-                      readonly="!isEdit"
-                      ng-model="entityView.keys.attributes.ss"
-                      placeholder="{{'entity-view.server-attributes' | translate}}"
-                      md-separator-keys="separatorKeys">
-            </md-chips>
-            <label translate class="tb-title no-padding">entity-view.latest-timeseries</label>
-            <md-chips ng-required="false"
-                      readonly="!isEdit"
-                      ng-model="entityView.keys.timeseries"
-                      placeholder="{{'entity-view.latest-timeseries' | translate}}"
-                      md-separator-keys="separatorKeys">
-            </md-chips>
-        </section>
-        <section layout="column">
-            <section layout="row" layout-align="start start">
-                <mdp-date-picker ng-model="startTimeMs"
-                                 mdp-max-date="maxStartTimeMs"
-                                 mdp-placeholder="{{ 'entity-view.start-ts' | translate }}"></mdp-date-picker>
-                <mdp-time-picker ng-model="startTimeMs"
-                                 mdp-max-date="maxStartTimeMs"
-                                 mdp-placeholder="{{ 'entity-view.start-ts' | translate }}"
-                                 mdp-auto-switch="true"></mdp-time-picker>
-            </section>
-            <section layout="row" layout-align="start start">
-                <mdp-date-picker ng-model="endTimeMs"
-                                 mdp-min-date="minEndTimeMs"
-                                 mdp-placeholder="{{ 'entity-view.end-ts' | translate }}"></mdp-date-picker>
-                <mdp-time-picker ng-model="endTimeMs"
-                                 mdp-min-date="minEndTimeMs"
-                                 mdp-placeholder="{{ 'entity-view.end-ts' | translate }}"
-                                 mdp-auto-switch="true"></mdp-time-picker>
-            </section>
-        </section>
 	</fieldset>
 </md-content>
diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json
index 55a1722..1fde4b6 100644
--- a/ui/src/app/locale/locale.constant-en_US.json
+++ b/ui/src/app/locale/locale.constant-en_US.json
@@ -838,14 +838,24 @@
         "unable-entity-view-device-alias-text": "Device alias '{{entityViewAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
         "select-entity-view": "Select entity view",
         "make-public": "Make entity view public",
+        "start-date": "Start date",
         "start-ts": "Start time",
+        "end-date": "End date",
         "end-ts": "End time",
         "date-limits": "Date limits",
         "client-attributes": "Client attributes",
         "shared-attributes": "Shared attributes",
         "server-attributes": "Server attributes",
-        "latest-timeseries": "Latest timeseries",
-        "related-entity": "Related entity"
+        "timeseries": "Timeseries",
+        "client-attributes-placeholder": "Client attributes",
+        "shared-attributes-placeholder": "Shared attributes",
+        "server-attributes-placeholder": "Server attributes",
+        "timeseries-placeholder": "Timeseries",
+        "target-entity": "Target entity",
+        "attributes-propagation": "Attributes propagation",
+        "attributes-propagation-hint": "Entity View will automatically copy specified attributes from Target Entity each time you save or update this entity view. For performance reasons target entity attributes are not propagated to entity view on each attribute change. You can enable automatic propagation by configuring \"copy to view\" rule node in your rule chain and linking \"Post attributes\" and \"Attributes Updated\" messages to the new rule node.",
+        "timeseries-data": "Timeseries data",
+        "timeseries-data-hint": "Configure timeseries data keys of the target entity that will be accessible to the entity view. This timeseries data is read-only."
     },
     "event": {
         "event-type": "Event type",
diff --git a/ui/src/app/locale/locale.constant-es_ES.json b/ui/src/app/locale/locale.constant-es_ES.json
index ec0e227..81cc52e 100644
--- a/ui/src/app/locale/locale.constant-es_ES.json
+++ b/ui/src/app/locale/locale.constant-es_ES.json
@@ -839,7 +839,7 @@
         "client-attributes": "Client attributes",
         "shared-attributes": "Shared attributes",
         "server-attributes": "Server attributes",
-        "latest-timeseries": "Latest timeseries"
+        "timeseries": "Timeseries"
     },
     "event": {
         "event-type": "Tipo de evento",