thingsboard-memoizeit

CoAP transport implementation

10/18/2018 7:47:42 AM

Changes

pom.xml 2(+1 -1)

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

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

transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java 242(+0 -242)

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 127(+0 -127)

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

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

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)

Details

diff --git a/application/pom.xml b/application/pom.xml
index 309f7de..d99f607 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -60,10 +60,6 @@
             <groupId>org.thingsboard.common.transport</groupId>
             <artifactId>transport-api</artifactId>
         </dependency>
-        <!--<dependency>-->
-        <!--<groupId>org.thingsboard.transport</groupId>-->
-        <!--<artifactId>coap</artifactId>-->
-        <!--</dependency>-->
         <dependency>
             <groupId>org.thingsboard.common.transport</groupId>
             <artifactId>mqtt</artifactId>
@@ -73,6 +69,10 @@
             <artifactId>http</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.thingsboard.common.transport</groupId>
+            <artifactId>coap</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.thingsboard</groupId>
             <artifactId>dao</artifactId>
         </dependency>
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index 6238a3e..90d1500 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -455,7 +455,6 @@ transport:
     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}"
@@ -482,5 +481,4 @@ transport:
     enabled: "${COAP_ENABLED:true}"
     bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
     bind_port: "${COAP_BIND_PORT:5683}"
-    adaptor:  "${COAP_ADAPTOR_NAME:JsonCoapAdaptor}"
     timeout: "${COAP_TIMEOUT:10000}"
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/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportContext.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportContext.java
new file mode 100644
index 0000000..13662cf
--- /dev/null
+++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportContext.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.transport.coap;
+
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.transport.TransportContext;
+import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor;
+
+/**
+ * Created by ashvayka on 18.10.18.
+ */
+@Slf4j
+@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.coap.enabled}'=='true')")
+@Component
+public class CoapTransportContext extends TransportContext {
+
+    @Getter
+    @Value("${transport.coap.bind_address}")
+    private String host;
+
+    @Getter
+    @Value("${transport.coap.bind_port}")
+    private Integer port;
+
+    @Getter
+    @Value("${transport.coap.timeout}")
+    private Long timeout;
+
+    @Getter
+    @Autowired
+    private CoapTransportAdaptor adaptor;
+
+}
diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
new file mode 100644
index 0000000..0e2a794
--- /dev/null
+++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
@@ -0,0 +1,445 @@
+/**
+ * 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;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.californium.core.CoapResource;
+import org.eclipse.californium.core.coap.CoAP.ResponseCode;
+import org.eclipse.californium.core.coap.Request;
+import org.eclipse.californium.core.coap.Response;
+import org.eclipse.californium.core.network.Exchange;
+import org.eclipse.californium.core.network.ExchangeObserver;
+import org.eclipse.californium.core.server.resources.CoapExchange;
+import org.eclipse.californium.core.server.resources.Resource;
+import org.springframework.util.ReflectionUtils;
+import org.thingsboard.server.common.data.security.DeviceTokenCredentials;
+import org.thingsboard.server.common.msg.session.FeatureType;
+import org.thingsboard.server.common.msg.session.SessionMsgType;
+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.AdaptorException;
+import org.thingsboard.server.gen.transport.TransportProtos;
+
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+@Slf4j
+public class CoapTransportResource extends CoapResource {
+    // coap://localhost:port/api/v1/DEVICE_TOKEN/[attributes|telemetry|rpc[/requestId]]
+    private static final int ACCESS_TOKEN_POSITION = 3;
+    private static final int FEATURE_TYPE_POSITION = 4;
+    private static final int REQUEST_ID_POSITION = 5;
+
+    private final CoapTransportContext transportContext;
+    private final TransportService transportService;
+    private final Field observerField;
+    private final long timeout;
+    private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionIdMap = new ConcurrentHashMap<>();
+
+    public CoapTransportResource(CoapTransportContext context, String name) {
+        super(name);
+        this.transportContext = context;
+        this.transportService = context.getTransportService();
+        this.timeout = context.getTimeout();
+        // This is important to turn off existing observable logic in
+        // CoapResource. We will have our own observe monitoring due to 1:1
+        // observe relationship.
+        this.setObservable(false);
+        observerField = ReflectionUtils.findField(Exchange.class, "observer");
+        observerField.setAccessible(true);
+    }
+
+    @Override
+    public void handleGET(CoapExchange exchange) {
+        if (transportContext.getQuotaService().isQuotaExceeded(exchange.getSourceAddress().getHostAddress())) {
+            log.warn("CoAP Quota exceeded for [{}:{}] . Disconnect", exchange.getSourceAddress().getHostAddress(), exchange.getSourcePort());
+            exchange.respond(ResponseCode.BAD_REQUEST);
+            return;
+        }
+
+        Optional<FeatureType> featureType = getFeatureType(exchange.advanced().getRequest());
+        if (!featureType.isPresent()) {
+            log.trace("Missing feature type parameter");
+            exchange.respond(ResponseCode.BAD_REQUEST);
+        } else if (featureType.get() == FeatureType.TELEMETRY) {
+            log.trace("Can't fetch/subscribe to timeseries updates");
+            exchange.respond(ResponseCode.BAD_REQUEST);
+        } else if (exchange.getRequestOptions().hasObserve()) {
+            processExchangeGetRequest(exchange, featureType.get());
+        } else if (featureType.get() == FeatureType.ATTRIBUTES) {
+            processRequest(exchange, SessionMsgType.GET_ATTRIBUTES_REQUEST);
+        } else {
+            log.trace("Invalid feature type parameter");
+            exchange.respond(ResponseCode.BAD_REQUEST);
+        }
+    }
+
+    private void processExchangeGetRequest(CoapExchange exchange, FeatureType featureType) {
+        boolean unsubscribe = exchange.getRequestOptions().getObserve() == 1;
+        SessionMsgType sessionMsgType;
+        if (featureType == FeatureType.RPC) {
+            sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST : SessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST;
+        } else {
+            sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST : SessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST;
+        }
+        processRequest(exchange, sessionMsgType);
+    }
+
+    @Override
+    public void handlePOST(CoapExchange exchange) {
+        if (transportContext.getQuotaService().isQuotaExceeded(exchange.getSourceAddress().getHostAddress())) {
+            log.warn("CoAP Quota exceeded for [{}:{}] . Disconnect", exchange.getSourceAddress().getHostAddress(), exchange.getSourcePort());
+            exchange.respond(ResponseCode.BAD_REQUEST);
+            return;
+        }
+
+        Optional<FeatureType> featureType = getFeatureType(exchange.advanced().getRequest());
+        if (!featureType.isPresent()) {
+            log.trace("Missing feature type parameter");
+            exchange.respond(ResponseCode.BAD_REQUEST);
+        } else {
+            switch (featureType.get()) {
+                case ATTRIBUTES:
+                    processRequest(exchange, SessionMsgType.POST_ATTRIBUTES_REQUEST);
+                    break;
+                case TELEMETRY:
+                    processRequest(exchange, SessionMsgType.POST_TELEMETRY_REQUEST);
+                    break;
+                case RPC:
+                    Optional<Integer> requestId = getRequestId(exchange.advanced().getRequest());
+                    if (requestId.isPresent()) {
+                        processRequest(exchange, SessionMsgType.TO_DEVICE_RPC_RESPONSE);
+                    } else {
+                        processRequest(exchange, SessionMsgType.TO_SERVER_RPC_REQUEST);
+                    }
+                    break;
+            }
+        }
+    }
+
+    private void processRequest(CoapExchange exchange, SessionMsgType type) {
+        log.trace("Processing {}", exchange.advanced().getRequest());
+        exchange.accept();
+        Exchange advanced = exchange.advanced();
+        Request request = advanced.getRequest();
+
+        Optional<DeviceTokenCredentials> credentials = decodeCredentials(request);
+        if (!credentials.isPresent()) {
+            exchange.respond(ResponseCode.BAD_REQUEST);
+            return;
+        }
+
+        transportService.process(TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(),
+                new DeviceAuthCallback(transportContext, exchange, sessionInfo -> {
+                    UUID sessionId = new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB());
+                    try {
+                        switch (type) {
+                            case POST_ATTRIBUTES_REQUEST:
+                                transportService.process(sessionInfo,
+                                        transportContext.getAdaptor().convertToPostAttributes(sessionId, request),
+                                        new CoapOkCallback(exchange));
+                                break;
+                            case POST_TELEMETRY_REQUEST:
+                                transportService.process(sessionInfo,
+                                        transportContext.getAdaptor().convertToPostTelemetry(sessionId, request),
+                                        new CoapOkCallback(exchange));
+                                break;
+                            case SUBSCRIBE_ATTRIBUTES_REQUEST:
+                                advanced.setObserver(new CoapExchangeObserverProxy((ExchangeObserver) observerField.get(advanced),
+                                        registerAsyncCoapSession(exchange, request, sessionInfo, sessionId)));
+                                transportService.process(sessionInfo,
+                                        TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(),
+                                        new CoapNoOpCallback(exchange));
+                                break;
+                            case UNSUBSCRIBE_ATTRIBUTES_REQUEST:
+                                TransportProtos.SessionInfoProto attrSession = lookupAsyncSessionInfo(request);
+                                if (attrSession != null) {
+                                    transportService.process(attrSession,
+                                            TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(),
+                                            new CoapOkCallback(exchange));
+                                    closeAndDeregister(sessionInfo);
+                                }
+                                break;
+                            case SUBSCRIBE_RPC_COMMANDS_REQUEST:
+                                advanced.setObserver(new CoapExchangeObserverProxy((ExchangeObserver) observerField.get(advanced),
+                                        registerAsyncCoapSession(exchange, request, sessionInfo, sessionId)));
+                                transportService.process(sessionInfo,
+                                        TransportProtos.SubscribeToRPCMsg.getDefaultInstance(),
+                                        new CoapNoOpCallback(exchange));
+                                break;
+                            case UNSUBSCRIBE_RPC_COMMANDS_REQUEST:
+                                TransportProtos.SessionInfoProto rpcSession = lookupAsyncSessionInfo(request);
+                                if (rpcSession != null) {
+                                    transportService.process(rpcSession,
+                                            TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(),
+                                            new CoapOkCallback(exchange));
+                                    transportService.process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null);
+                                    transportService.deregisterSession(rpcSession);
+                                }
+                                break;
+                            case TO_DEVICE_RPC_RESPONSE:
+                                transportService.process(sessionInfo,
+                                        transportContext.getAdaptor().convertToDeviceRpcResponse(sessionId, request),
+                                        new CoapOkCallback(exchange));
+                                break;
+                            case TO_SERVER_RPC_REQUEST:
+                                transportService.process(sessionInfo,
+                                        transportContext.getAdaptor().convertToServerRpcRequest(sessionId, request),
+                                        new CoapNoOpCallback(exchange));
+                                break;
+                            case GET_ATTRIBUTES_REQUEST:
+                                transportService.registerSyncSession(sessionInfo, new CoapSessionListener(sessionId, exchange), transportContext.getTimeout());
+                                transportService.process(sessionInfo,
+                                        transportContext.getAdaptor().convertToGetAttributes(sessionId, request),
+                                        new CoapNoOpCallback(exchange));
+                                break;
+                        }
+                    } catch (AdaptorException e) {
+                        log.trace("[{}] Failed to decode message: ", sessionId, e);
+                        exchange.respond(ResponseCode.BAD_REQUEST);
+                    } catch (IllegalAccessException e) {
+                        log.trace("[{}] Failed to process message: ", sessionId, e);
+                        exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR);
+                    }
+                }));
+    }
+
+    private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(Request request) {
+        String token = request.getSource().getHostAddress() + ":" + request.getSourcePort() + ":" + request.getTokenString();
+        return tokenToSessionIdMap.remove(token);
+    }
+
+    private String registerAsyncCoapSession(CoapExchange exchange, Request request, TransportProtos.SessionInfoProto sessionInfo, UUID sessionId) {
+        String token = request.getSource().getHostAddress() + ":" + request.getSourcePort() + ":" + request.getTokenString();
+        tokenToSessionIdMap.putIfAbsent(token, sessionInfo);
+        CoapSessionListener attrListener = new CoapSessionListener(sessionId, exchange);
+        transportService.registerAsyncSession(sessionInfo, attrListener);
+        transportService.process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null);
+        return token;
+    }
+
+    private static TransportProtos.SessionEventMsg getSessionEventMsg(TransportProtos.SessionEvent event) {
+        return TransportProtos.SessionEventMsg.newBuilder()
+                .setSessionType(TransportProtos.SessionType.ASYNC)
+                .setEvent(event).build();
+    }
+
+    private Optional<DeviceTokenCredentials> decodeCredentials(Request request) {
+        List<String> uriPath = request.getOptions().getUriPath();
+        if (uriPath.size() >= ACCESS_TOKEN_POSITION) {
+            return Optional.of(new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1)));
+        } else {
+            return Optional.empty();
+        }
+    }
+
+    private Optional<FeatureType> getFeatureType(Request request) {
+        List<String> uriPath = request.getOptions().getUriPath();
+        try {
+            if (uriPath.size() >= FEATURE_TYPE_POSITION) {
+                return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase()));
+            }
+        } catch (RuntimeException e) {
+            log.warn("Failed to decode feature type: {}", uriPath);
+        }
+        return Optional.empty();
+    }
+
+    public static Optional<Integer> getRequestId(Request request) {
+        List<String> uriPath = request.getOptions().getUriPath();
+        try {
+            if (uriPath.size() >= REQUEST_ID_POSITION) {
+                return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION - 1)));
+            }
+        } catch (RuntimeException e) {
+            log.warn("Failed to decode feature type: {}", uriPath);
+        }
+        return Optional.empty();
+    }
+
+    @Override
+    public Resource getChild(String name) {
+        return this;
+    }
+
+    private static class DeviceAuthCallback implements TransportServiceCallback<TransportProtos.ValidateDeviceCredentialsResponseMsg> {
+        private final TransportContext transportContext;
+        private final CoapExchange exchange;
+        private final Consumer<TransportProtos.SessionInfoProto> onSuccess;
+
+        DeviceAuthCallback(TransportContext transportContext, CoapExchange exchange, Consumer<TransportProtos.SessionInfoProto> onSuccess) {
+            this.transportContext = transportContext;
+            this.exchange = exchange;
+            this.onSuccess = onSuccess;
+        }
+
+        @Override
+        public void onSuccess(TransportProtos.ValidateDeviceCredentialsResponseMsg msg) {
+            if (msg.hasDeviceInfo()) {
+                UUID sessionId = UUID.randomUUID();
+                TransportProtos.DeviceInfoProto deviceInfoProto = msg.getDeviceInfo();
+                TransportProtos.SessionInfoProto sessionInfo = TransportProtos.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 {
+                exchange.respond(ResponseCode.UNAUTHORIZED);
+            }
+        }
+
+        @Override
+        public void onError(Throwable e) {
+            log.warn("Failed to process request", e);
+            exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR);
+        }
+    }
+
+    private static class CoapOkCallback implements TransportServiceCallback<Void> {
+        private final CoapExchange exchange;
+
+        CoapOkCallback(CoapExchange exchange) {
+            this.exchange = exchange;
+        }
+
+        @Override
+        public void onSuccess(Void msg) {
+                exchange.respond(ResponseCode.VALID);
+        }
+
+        @Override
+        public void onError(Throwable e) {
+            exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR);
+        }
+    }
+
+    private static class CoapNoOpCallback implements TransportServiceCallback<Void> {
+        private final CoapExchange exchange;
+
+        CoapNoOpCallback(CoapExchange exchange) {
+            this.exchange = exchange;
+        }
+
+        @Override
+        public void onSuccess(Void msg) {
+
+        }
+
+        @Override
+        public void onError(Throwable e) {
+            exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR);
+        }
+    }
+
+    public class CoapSessionListener implements SessionMsgListener {
+
+        private final CoapExchange exchange;
+        private final AtomicInteger seqNumber = new AtomicInteger(2);
+
+        CoapSessionListener(UUID sessionId, CoapExchange exchange) {
+            this.exchange = exchange;
+        }
+
+        @Override
+        public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg msg) {
+            try {
+                exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg));
+            } catch (AdaptorException e) {
+                log.trace("Failed to reply due to error", e);
+                exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR);
+            }
+        }
+
+        @Override
+        public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg) {
+            try {
+                exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg));
+            } catch (AdaptorException e) {
+                log.trace("Failed to reply due to error", e);
+                exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR);
+            }
+        }
+
+        @Override
+        public void onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto sessionCloseNotification) {
+            exchange.respond(ResponseCode.SERVICE_UNAVAILABLE);
+        }
+
+        @Override
+        public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg msg) {
+            try {
+                exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg));
+            } catch (AdaptorException e) {
+                log.trace("Failed to reply due to error", e);
+                exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR);
+            }
+        }
+
+        @Override
+        public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg msg) {
+            try {
+                exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg));
+            } catch (AdaptorException e) {
+                log.trace("Failed to reply due to error", e);
+                exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR);
+            }
+        }
+
+        public int getNextSeqNumber() {
+            return seqNumber.getAndIncrement();
+        }
+    }
+
+    public class CoapExchangeObserverProxy implements ExchangeObserver {
+
+        private final ExchangeObserver proxy;
+        private final String token;
+
+        CoapExchangeObserverProxy(ExchangeObserver proxy, String token) {
+            super();
+            this.proxy = proxy;
+            this.token = token;
+        }
+
+        @Override
+        public void completed(Exchange exchange) {
+            proxy.completed(exchange);
+            TransportProtos.SessionInfoProto session = tokenToSessionIdMap.remove(token);
+            if (session != null) {
+                closeAndDeregister(session);
+            }
+        }
+    }
+
+    private void closeAndDeregister(TransportProtos.SessionInfoProto session) {
+        transportService.process(session, getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null);
+        transportService.deregisterSession(session);
+    }
+
+}
diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java
index c1639ce..55df31a 100644
--- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java
+++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java
@@ -43,8 +43,6 @@ public class MqttTransportService {
     private String host;
     @Value("${transport.mqtt.bind_port}")
     private Integer port;
-    @Value("${transport.mqtt.adaptor}")
-    private String adaptorName;
 
     @Value("${transport.mqtt.netty.leak_detector_level}")
     private String leakDetectorLevel;
diff --git a/common/transport/pom.xml b/common/transport/pom.xml
index c9b47e8..83e7ba8 100644
--- a/common/transport/pom.xml
+++ b/common/transport/pom.xml
@@ -38,7 +38,7 @@
         <module>transport-api</module>
         <module>mqtt</module>
         <module>http</module>
-        <!--module>coap</module-->
+        <module>coap</module>
     </modules>
 
 </project>

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index f4c2957..684c3d0 100755
--- a/pom.xml
+++ b/pom.xml
@@ -370,7 +370,7 @@
                 <version>${project.version}</version>
             </dependency>
             <dependency>
-                <groupId>org.thingsboard.transport</groupId>
+                <groupId>org.thingsboard.common.transport</groupId>
                 <artifactId>coap</artifactId>
                 <version>${project.version}</version>
             </dependency>
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/pom.xml b/transport/http/pom.xml
index 225f064..407d995 100644
--- a/transport/http/pom.xml
+++ b/transport/http/pom.xml
@@ -145,7 +145,7 @@
                                 <resource>
                                     <directory>src/main/conf</directory>
                                     <excludes>
-                                        <exclude>tb-mqtt-transport.conf</exclude>
+                                        <exclude>tb-http-transport.conf</exclude>
                                     </excludes>
                                     <filtering>true</filtering>
                                 </resource>
@@ -229,7 +229,7 @@
                     </excludes>
                     <archive>
                         <manifestEntries>
-                            <Implementation-Title>ThingsBoard MQTT Transport Service</Implementation-Title>
+                            <Implementation-Title>ThingsBoard HTTP Transport Service</Implementation-Title>
                             <Implementation-Version>${project.version}</Implementation-Version>
                         </manifestEntries>
                     </archive>
@@ -239,7 +239,7 @@
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-maven-plugin</artifactId>
                 <configuration>
-                    <mainClass>org.thingsboard.server.mqtt.ThingsboardHttpTransportApplication</mainClass>
+                    <mainClass>org.thingsboard.server.http.ThingsboardHttpTransportApplication</mainClass>
                     <classifier>boot</classifier>
                     <layout>ZIP</layout>
                     <executable>true</executable>
diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml
index 2750e1e..6395b40 100644
--- a/transport/mqtt/pom.xml
+++ b/transport/mqtt/pom.xml
@@ -49,6 +49,10 @@
             <artifactId>queue</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.sun.winsw</groupId>
             <artifactId>winsw</artifactId>
             <classifier>bin</classifier>
diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml
index e46de8e..1d425e4 100644
--- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml
+++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml
@@ -14,6 +14,7 @@
 # limitations under the License.
 #
 
+spring.main.web-environment: false
 spring.main.web-application-type: none
 
 # MQTT server parameters
diff --git a/transport/pom.xml b/transport/pom.xml
index 281f511..0bb9300 100644
--- a/transport/pom.xml
+++ b/transport/pom.xml
@@ -36,6 +36,7 @@
     <modules>
         <module>http</module>
         <module>mqtt</module>
+        <module>coap</module>
     </modules>
 
 </project>