thingsboard-aplcache

Merged with master

5/16/2018 12:26:18 PM

Details

diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index af5b8d4..ac7a259 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -87,6 +87,7 @@ mqtt:
     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
diff --git a/dao/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java b/dao/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java
index 2e56416..ca94822 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java
@@ -60,6 +60,10 @@ public abstract class AbstractCassandraCluster {
     private long initTimeout;
     @Value("${cassandra.init_retry_interval_ms}")
     private long initRetryInterval;
+    @Value("${cassandra.max_requests_per_connection_local:128}")
+    private int max_requests_local;
+    @Value("${cassandra.max_requests_per_connection_remote:128}")
+    private int max_requests_remote;
 
     @Autowired
     private CassandraSocketOptions socketOpts;
@@ -90,8 +94,8 @@ public abstract class AbstractCassandraCluster {
                 .withClusterName(clusterName)
                 .withSocketOptions(socketOpts.getOpts())
                 .withPoolingOptions(new PoolingOptions()
-                        .setMaxRequestsPerConnection(HostDistance.LOCAL, 32768)
-                        .setMaxRequestsPerConnection(HostDistance.REMOTE, 32768));
+                        .setMaxRequestsPerConnection(HostDistance.LOCAL, max_requests_local)
+                        .setMaxRequestsPerConnection(HostDistance.REMOTE, max_requests_remote));
         this.clusterBuilder.withQueryOptions(queryOpts.getOpts());
         this.clusterBuilder.withCompression(StringUtils.isEmpty(compression) ? Compression.NONE : Compression.valueOf(compression.toUpperCase()));
         if (ssl) {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
index 836bd3d..a0cb1fb 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
@@ -170,12 +170,12 @@ public class BaseRelationService implements RelationService {
         Cache cache = cacheManager.getCache(RELATIONS_CACHE);
         log.trace("Executing deleteEntityRelations [{}]", entity);
         validate(entity);
-        List<ListenableFuture<List<EntityRelation>>> inboundRelationsListTo = new ArrayList<>();
+        List<ListenableFuture<List<EntityRelation>>> inboundRelationsList = new ArrayList<>();
         for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) {
-            inboundRelationsListTo.add(relationDao.findAllByTo(entity, typeGroup));
+            inboundRelationsList.add(relationDao.findAllByTo(entity, typeGroup));
         }
-        ListenableFuture<List<List<EntityRelation>>> inboundRelationsTo = Futures.allAsList(inboundRelationsListTo);
-        ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelationsTo, (List<List<EntityRelation>> relations) ->
+        ListenableFuture<List<List<EntityRelation>>> inboundRelations = Futures.allAsList(inboundRelationsList);
+        ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelations, (List<List<EntityRelation>> relations) ->
                 getBooleans(relations, cache, true));
 
         ListenableFuture<Boolean> inboundFuture = Futures.transform(inboundDeletions, getListToBooleanFunction());
@@ -186,12 +186,12 @@ public class BaseRelationService implements RelationService {
             log.error("Error deleting entity inbound relations", e);
         }
 
-        List<ListenableFuture<List<EntityRelation>>> inboundRelationsListFrom = new ArrayList<>();
+        List<ListenableFuture<List<EntityRelation>>> outboundRelationsList = new ArrayList<>();
         for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) {
-            inboundRelationsListFrom.add(relationDao.findAllByFrom(entity, typeGroup));
+            outboundRelationsList.add(relationDao.findAllByFrom(entity, typeGroup));
         }
-        ListenableFuture<List<List<EntityRelation>>> inboundRelationsFrom = Futures.allAsList(inboundRelationsListFrom);
-        Futures.transform(inboundRelationsFrom, (Function<List<List<EntityRelation>>, List<Boolean>>) relations ->
+        ListenableFuture<List<List<EntityRelation>>> outboundRelations = Futures.allAsList(outboundRelationsList);
+        Futures.transform(outboundRelations, (Function<List<List<EntityRelation>>, List<Boolean>>) relations ->
                 getBooleans(relations, cache, false));
 
         boolean outboundDeleteResult = relationDao.deleteOutboundRelations(entity);
@@ -201,9 +201,7 @@ public class BaseRelationService implements RelationService {
     private List<Boolean> getBooleans(List<List<EntityRelation>> relations, Cache cache, boolean isRemove) {
         List<Boolean> results = new ArrayList<>();
         for (List<EntityRelation> relationList : relations) {
-            relationList.stream().forEach(relation -> {
-                checkFromDeleteSync(cache, results, relation, isRemove);
-            });
+            relationList.stream().forEach(relation -> checkFromDeleteSync(cache, results, relation, isRemove));
         }
         return results;
     }
@@ -211,10 +209,8 @@ public class BaseRelationService implements RelationService {
     private void checkFromDeleteSync(Cache cache, List<Boolean> results, EntityRelation relation, boolean isRemove) {
         if (isRemove) {
             results.add(relationDao.deleteRelation(relation));
-            cacheEviction(relation, false, cache);
-        } else {
-            cacheEviction(relation, true, cache);
         }
+        cacheEviction(relation, cache);
     }
 
     @Override
@@ -222,12 +218,12 @@ public class BaseRelationService implements RelationService {
         Cache cache = cacheManager.getCache(RELATIONS_CACHE);
         log.trace("Executing deleteEntityRelationsAsync [{}]", entity);
         validate(entity);
-        List<ListenableFuture<List<EntityRelation>>> inboundRelationsListTo = new ArrayList<>();
+        List<ListenableFuture<List<EntityRelation>>> inboundRelationsList = new ArrayList<>();
         for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) {
-            inboundRelationsListTo.add(relationDao.findAllByTo(entity, typeGroup));
+            inboundRelationsList.add(relationDao.findAllByTo(entity, typeGroup));
         }
-        ListenableFuture<List<List<EntityRelation>>> inboundRelationsTo = Futures.allAsList(inboundRelationsListTo);
-        ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelationsTo,
+        ListenableFuture<List<List<EntityRelation>>> inboundRelations = Futures.allAsList(inboundRelationsList);
+        ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelations,
                 (AsyncFunction<List<List<EntityRelation>>, List<Boolean>>) relations -> {
                     List<ListenableFuture<Boolean>> results = getListenableFutures(relations, cache, true);
                     return Futures.allAsList(results);
@@ -235,12 +231,12 @@ public class BaseRelationService implements RelationService {
 
         ListenableFuture<Boolean> inboundFuture = Futures.transform(inboundDeletions, getListToBooleanFunction());
 
-        List<ListenableFuture<List<EntityRelation>>> inboundRelationsListFrom = new ArrayList<>();
+        List<ListenableFuture<List<EntityRelation>>> outboundRelationsList = new ArrayList<>();
         for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) {
-            inboundRelationsListFrom.add(relationDao.findAllByTo(entity, typeGroup));
+            outboundRelationsList.add(relationDao.findAllByFrom(entity, typeGroup));
         }
-        ListenableFuture<List<List<EntityRelation>>> inboundRelationsFrom = Futures.allAsList(inboundRelationsListFrom);
-        Futures.transform(inboundRelationsFrom, (AsyncFunction<List<List<EntityRelation>>, List<Boolean>>) relations -> {
+        ListenableFuture<List<List<EntityRelation>>> outboundRelations = Futures.allAsList(outboundRelationsList);
+        Futures.transform(outboundRelations, (AsyncFunction<List<List<EntityRelation>>, List<Boolean>>) relations -> {
             List<ListenableFuture<Boolean>> results = getListenableFutures(relations, cache, false);
             return Futures.allAsList(results);
         });
@@ -252,9 +248,7 @@ public class BaseRelationService implements RelationService {
     private List<ListenableFuture<Boolean>> getListenableFutures(List<List<EntityRelation>> relations, Cache cache, boolean isRemove) {
         List<ListenableFuture<Boolean>> results = new ArrayList<>();
         for (List<EntityRelation> relationList : relations) {
-            relationList.stream().forEach(relation -> {
-                checkFromDeleteAsync(cache, results, relation, isRemove);
-            });
+            relationList.stream().forEach(relation -> checkFromDeleteAsync(cache, results, relation, isRemove));
         }
         return results;
     }
@@ -262,13 +256,11 @@ public class BaseRelationService implements RelationService {
     private void checkFromDeleteAsync(Cache cache, List<ListenableFuture<Boolean>> results, EntityRelation relation, boolean isRemove) {
         if (isRemove) {
             results.add(relationDao.deleteRelationAsync(relation));
-            cacheEviction(relation, false, cache);
-        } else {
-            cacheEviction(relation, true, cache);
         }
+        cacheEviction(relation, cache);
     }
 
-    private void cacheEviction(EntityRelation relation, boolean outboundOnly, Cache cache) {
+    private void cacheEviction(EntityRelation relation, Cache cache) {
         List<Object> fromToTypeAndTypeGroup = new ArrayList<>();
         fromToTypeAndTypeGroup.add(relation.getFrom());
         fromToTypeAndTypeGroup.add(relation.getTo());
@@ -287,18 +279,16 @@ public class BaseRelationService implements RelationService {
         fromAndTypeGroup.add(relation.getTypeGroup());
         cache.evict(fromAndTypeGroup);
 
-        if (!outboundOnly) {
-            List<Object> toAndTypeGroup = new ArrayList<>();
-            toAndTypeGroup.add(relation.getTo());
-            toAndTypeGroup.add(relation.getTypeGroup());
-            cache.evict(toAndTypeGroup);
-
-            List<Object> toTypeAndTypeGroup = new ArrayList<>();
-            fromTypeAndTypeGroup.add(relation.getTo());
-            fromTypeAndTypeGroup.add(relation.getType());
-            fromTypeAndTypeGroup.add(relation.getTypeGroup());
-            cache.evict(toTypeAndTypeGroup);
-        }
+        List<Object> toAndTypeGroup = new ArrayList<>();
+        toAndTypeGroup.add(relation.getTo());
+        toAndTypeGroup.add(relation.getTypeGroup());
+        cache.evict(toAndTypeGroup);
+
+        List<Object> toTypeAndTypeGroup = new ArrayList<>();
+        fromTypeAndTypeGroup.add(relation.getTo());
+        fromTypeAndTypeGroup.add(relation.getType());
+        fromTypeAndTypeGroup.add(relation.getTypeGroup());
+        cache.evict(toTypeAndTypeGroup);
     }
 
     @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup}")
diff --git a/docker/k8s/redis.yaml b/docker/k8s/redis.yaml
new file mode 100644
index 0000000..3af810e
--- /dev/null
+++ b/docker/k8s/redis.yaml
@@ -0,0 +1,93 @@
+#
+# 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.
+#
+
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    name: redis-service
+  name: redis-service
+spec:
+  ports:
+    - name: redis-service
+      protocol: TCP
+      port: 6379
+      targetPort: 6379
+  selector:
+    app: redis
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: redis-conf
+data:
+  redis.conf: |
+    appendonly yes
+    protected-mode no
+    bind 0.0.0.0
+    port 6379
+    dir /var/lib/redis
+---
+apiVersion: apps/v1beta1
+kind: StatefulSet
+metadata:
+  name: redis
+spec:
+  serviceName: redis-service
+  replicas: 1
+  template:
+    metadata:
+      labels:
+        app: redis
+    spec:
+      terminationGracePeriodSeconds: 10
+      containers:
+        - name: redis
+          image: redis:4.0.9
+          command:
+            - redis-server
+          args:
+            - /etc/redis/redis.conf
+          resources:
+            requests:
+              cpu: 100m
+              memory: 100Mi
+          ports:
+            - containerPort: 6379
+              name: redis
+          volumeMounts:
+            - name: redis-data
+              mountPath: /var/lib/redis
+            - name: redis-conf
+              mountPath: /etc/redis
+      volumes:
+        - name: redis-conf
+          configMap:
+            name: redis-conf
+            items:
+              - key: redis.conf
+                path: redis.conf
+  volumeClaimTemplates:
+    - metadata:
+        name: redis-data
+        annotations:
+          volume.beta.kubernetes.io/storage-class: fast
+      spec:
+        accessModes: [ "ReadWriteOnce" ]
+        resources:
+          requests:
+            storage: 1Gi
\ No newline at end of file

docker/k8s/tb.yaml 12(+12 -0)

diff --git a/docker/k8s/tb.yaml b/docker/k8s/tb.yaml
index 74b6702..1d94cb1 100644
--- a/docker/k8s/tb.yaml
+++ b/docker/k8s/tb.yaml
@@ -54,6 +54,8 @@ data:
   cassandra.host: "cassandra-headless"
   cassandra.port: "9042"
   database.type: "cassandra"
+  cache.type: "redis"
+  redis.host: "redis-service"
 ---
 apiVersion: apps/v1beta1
 kind: StatefulSet
@@ -127,6 +129,16 @@ spec:
           valueFrom:
             fieldRef:
               fieldPath: status.podIP
+        - name: CACHE_TYPE
+          valueFrom:
+            configMapKeyRef:
+              name: tb-config
+              key: cache.type
+        - name: REDIS_HOST
+          valueFrom:
+            configMapKeyRef:
+              name: tb-config
+              key: redis.host
         command:
         - sh
         - -c
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java
index 976d8ba..432966e 100644
--- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java
@@ -33,8 +33,6 @@ import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
  */
 public class MqttTransportServerInitializer extends ChannelInitializer<SocketChannel> {
 
-    private static final int MAX_PAYLOAD_SIZE = 64 * 1024 * 1024;
-
     private final SessionMsgProcessor processor;
     private final DeviceService deviceService;
     private final DeviceAuthService authService;
@@ -42,10 +40,11 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
     private final MqttTransportAdaptor adaptor;
     private final MqttSslHandlerProvider sslHandlerProvider;
     private final QuotaService quotaService;
+    private final int maxPayloadSize;
 
     public MqttTransportServerInitializer(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService,
                                           MqttTransportAdaptor adaptor, MqttSslHandlerProvider sslHandlerProvider,
-                                          QuotaService quotaService) {
+                                          QuotaService quotaService, int maxPayloadSize) {
         this.processor = processor;
         this.deviceService = deviceService;
         this.authService = authService;
@@ -53,6 +52,7 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
         this.adaptor = adaptor;
         this.sslHandlerProvider = sslHandlerProvider;
         this.quotaService = quotaService;
+        this.maxPayloadSize = maxPayloadSize;
     }
 
     @Override
@@ -63,7 +63,7 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
             sslHandler = sslHandlerProvider.getSslHandler();
             pipeline.addLast(sslHandler);
         }
-        pipeline.addLast("decoder", new MqttDecoder(MAX_PAYLOAD_SIZE));
+        pipeline.addLast("decoder", new MqttDecoder(maxPayloadSize));
         pipeline.addLast("encoder", MqttEncoder.INSTANCE);
 
         MqttTransportHandler handler = new MqttTransportHandler(processor, deviceService, authService, relationService,
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java
index 1ae7d38..f0129e1 100644
--- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java
@@ -82,7 +82,8 @@ public class MqttTransportService {
     private Integer bossGroupThreadCount;
     @Value("${mqtt.netty.worker_group_thread_count}")
     private Integer workerGroupThreadCount;
-
+    @Value("${mqtt.netty.max_payload_size}")
+    private Integer maxPayloadSize;
 
     private MqttTransportAdaptor adaptor;
 
@@ -106,7 +107,7 @@ public class MqttTransportService {
         b.group(bossGroup, workerGroup)
                 .channel(NioServerSocketChannel.class)
                 .childHandler(new MqttTransportServerInitializer(processor, deviceService, authService, relationService,
-                        adaptor, sslHandlerProvider, quotaService));
+                        adaptor, sslHandlerProvider, quotaService, maxPayloadSize));
 
         serverChannel = b.bind(host, port).sync().channel();
         log.info("Mqtt transport started!");
diff --git a/ui/src/app/app.js b/ui/src/app/app.js
index 88e1448..f021efb 100644
--- a/ui/src/app/app.js
+++ b/ui/src/app/app.js
@@ -82,7 +82,7 @@ import 'font-awesome/css/font-awesome.min.css';
 import 'angular-material/angular-material.min.css';
 import 'angular-material-icons/angular-material-icons.css';
 import 'angular-gridster/dist/angular-gridster.min.css';
-import 'v-accordion/dist/v-accordion.min.css'
+import 'v-accordion/dist/v-accordion.min.css';
 import 'md-color-picker/dist/mdColorPicker.min.css';
 import 'mdPickers/dist/mdPickers.min.css';
 import 'angular-hotkeys/build/hotkeys.min.css';
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index 8d88cb9..7f85f6a 100644
--- a/ui/src/app/common/types.constant.js
+++ b/ui/src/app/common/types.constant.js
@@ -406,7 +406,8 @@ export default angular.module('thingsboard.types', [])
             extensionType: {
                 http: "HTTP",
                 mqtt: "MQTT",
-                opc: "OPC UA"
+                opc: "OPC UA",
+                modbus: "MODBUS"
             },
             extensionValueType: {
                 string: 'value.string',
@@ -450,6 +451,26 @@ export default angular.module('thingsboard.types', [])
                 PKCS12: "PKCS12",
                 JKS: "JKS"
             },
+            extensionModbusFunctionCodes: {
+                1: "Read Coils (1)",
+                2: "Read Discrete Inputs (2)",
+                3: "Read Multiple Holding Registers (3)",
+                4: "Read Input Registers (4)"
+            },
+            extensionModbusTransports: {
+                tcp: "TCP",
+                udp: "UDP",
+                rtu: "RTU"
+            },
+            extensionModbusRtuParities: {
+                none: "none",
+                even: "even",
+                odd: "odd"
+            },
+            extensionModbusRtuEncodings: {
+                ascii: "ascii",
+                rtu: "rtu"
+            },
             latestTelemetry: {
                 value: "LATEST_TELEMETRY",
                 name: "attribute.scope-latest-telemetry",
diff --git a/ui/src/app/extension/extension-dialog.controller.js b/ui/src/app/extension/extension-dialog.controller.js
index 2b0eded..4b8221a 100644
--- a/ui/src/app/extension/extension-dialog.controller.js
+++ b/ui/src/app/extension/extension-dialog.controller.js
@@ -49,7 +49,7 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
                 "brokers": []
             };
         }
-        if (vm.extension.type === "OPC UA") {
+        if (vm.extension.type === "OPC UA" || vm.extension.type === "MODBUS") {
             vm.extension.configuration = {
                 "servers": []
             };
diff --git a/ui/src/app/extension/extension-dialog.tpl.html b/ui/src/app/extension/extension-dialog.tpl.html
index 4e200d5..fcf7840 100644
--- a/ui/src/app/extension/extension-dialog.tpl.html
+++ b/ui/src/app/extension/extension-dialog.tpl.html
@@ -62,6 +62,7 @@
                         <div tb-extension-form-http config="vm.extension.configuration" is-add="vm.isAdd" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.http"></div>
                         <div tb-extension-form-mqtt config="vm.extension.configuration" is-add="vm.isAdd" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.mqtt"></div>
                         <div tb-extension-form-opc  configuration="vm.extension.configuration"  ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.opc"></div>
+                        <div tb-extension-form-modbus  configuration="vm.extension.configuration"  ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.modbus"></div>
                     </fieldset>
                 </md-content>
             </div>
diff --git a/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js b/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js
new file mode 100644
index 0000000..2dfc961
--- /dev/null
+++ b/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js
@@ -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 'brace/ext/language_tools';
+import 'brace/mode/json';
+import 'brace/theme/github';
+
+import './extension-form.scss';
+
+/* eslint-disable angular/log */
+
+import extensionFormModbusTemplate from './extension-form-modbus.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function ExtensionFormModbusDirective($compile, $templateCache, $translate, types) {
+
+
+    var linker = function(scope, element) {
+
+
+        function Server() {
+            this.transport = {
+                "type": "tcp",
+                "host": "localhost",
+                "port": 502,
+                "timeout": 3000
+            };
+            this.devices = []
+        }
+
+        function Device() {
+            this.unitId = 1;
+            this.deviceName = "";
+            this.attributesPollPeriod = 1000;
+            this.timeseriesPollPeriod = 1000;
+            this.attributes = [];
+            this.timeseries = [];
+        }
+
+        function Tag(globalPollPeriod) {
+            this.tag = "";
+            this.type = "long";
+            this.pollPeriod = globalPollPeriod;
+            this.functionCode = 3;
+            this.address = 0;
+            this.registerCount = 1;
+            this.bit = 0;
+            this.byteOrder = "BIG";
+        }
+
+
+        var template = $templateCache.get(extensionFormModbusTemplate);
+        element.html(template);
+
+        scope.types = types;
+        scope.theForm = scope.$parent.theForm;
+
+
+        if (!scope.configuration.servers.length) {
+            scope.configuration.servers.push(new Server());
+        }
+
+        scope.addServer = function(serversList) {
+            serversList.push(new Server());
+            scope.theForm.$setDirty();
+        };
+
+        scope.addDevice = function(deviceList) {
+            deviceList.push(new Device());
+            scope.theForm.$setDirty();
+        };
+
+        scope.addNewAttribute = function(device) {
+            device.attributes.push(new Tag(device.attributesPollPeriod));
+            scope.theForm.$setDirty();
+        };
+
+        scope.addNewTimeseries = function(device) {
+            device.timeseries.push(new Tag(device.timeseriesPollPeriod));
+            scope.theForm.$setDirty();
+        };
+
+        scope.removeItem = (item, itemList) => {
+            var index = itemList.indexOf(item);
+            if (index > -1) {
+                itemList.splice(index, 1);
+            }
+            scope.theForm.$setDirty();
+        };
+
+        scope.onTransportChanged = function(server) {
+            var type = server.transport.type;
+
+            server.transport = {};
+            server.transport.type = type;
+            server.transport.timeout = 3000;
+
+            scope.theForm.$setDirty();
+        };
+        
+        $compile(element.contents())(scope);
+
+
+        scope.collapseValidation = function(index, id) {
+            var invalidState = angular.element('#'+id+':has(.ng-invalid)');
+            if(invalidState.length) {
+                invalidState.addClass('inner-invalid');
+            }
+        };
+
+        scope.expandValidation = function (index, id) {
+            var invalidState = angular.element('#'+id);
+            invalidState.removeClass('inner-invalid');
+        };
+
+    };
+
+    return {
+        restrict: "A",
+        link: linker,
+        scope: {
+            configuration: "=",
+            isAdd: "="
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html
new file mode 100644
index 0000000..989d3f0
--- /dev/null
+++ b/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html
@@ -0,0 +1,774 @@
+<!--
+
+    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.
+
+-->
+<md-card class="extension-form extension-modbus">
+    <md-card-title>
+        <md-card-title-text>
+            <span translate class="md-headline">extension.configuration</span>
+        </md-card-title-text>
+    </md-card-title>
+
+    <md-card-content>
+        <v-accordion id="modbus-server-configs-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
+            <v-pane id="modbus-servers-pane" expanded="true">
+                <v-pane-header>
+                    {{ 'extension.modbus-server' | translate }}
+                </v-pane-header>
+
+                <v-pane-content>
+                    <div ng-if="configuration.servers.length === 0">
+                        <span translate layout-align="center center" class="tb-prompt">extension.modbus-add-server-prompt</span>
+                    </div>
+
+                    <div ng-if="configuration.servers.length > 0">
+                        <ol class="list-group">
+                            <li class="list-group-item" ng-repeat="(serverIndex, server) in configuration.servers">
+                                <md-button aria-label="{{ 'action.remove' | translate }}"
+                                           class="md-icon-button"
+                                           ng-click="removeItem(server, configuration.servers)"
+                                           ng-hide="configuration.servers.length < 2"
+                                >
+                                    <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
+                                    <md-tooltip md-direction="top">
+                                        {{ 'action.remove' | translate }}
+                                    </md-tooltip>
+                                </md-button>
+
+                                <md-card>
+                                    <md-card-content>
+                                        
+                                        <div layout="row">
+    
+                                            <md-input-container flex="50" class="md-block tb-container-for-select">
+                                                <label translate>extension.modbus-transport</label>
+                                                <md-select required
+                                                           name="transportType_{{serverIndex}}"
+                                                           ng-model="server.transport.type"
+                                                           ng-change="onTransportChanged(server)"
+                                                >
+                                                    <md-option ng-value="transportType"
+                                                               ng-repeat="(transportType, transportValue) in types.extensionModbusTransports"
+                                                    ><span ng-bind="transportValue"></span></md-option>
+                                                </md-select>
+                                                <div ng-messages="theForm['transportType_' + serverIndex].$error">
+                                                    <div translate
+                                                         ng-message="required"
+                                                    >extension.field-required</div>
+                                                </div>
+                                            </md-input-container>
+                                            
+                                        </div>
+                                        
+                                        <div layout="row" ng-if="server.transport.type == 'tcp' || server.transport.type == 'udp'">
+                                            <md-input-container flex="33" class="md-block">
+                                                <label translate>extension.host</label>
+                                                <input required name="transportHost_{{serverIndex}}" ng-model="server.transport.host">
+                                                <div ng-messages="theForm['transportHost_' + serverIndex].$error">
+                                                    <div translate ng-message="required">extension.field-required</div>
+                                                </div>
+                                            </md-input-container>
+
+                                            <md-input-container flex="33" class="md-block">
+                                                <label translate>extension.port</label>
+                                                <input type="number"
+                                                       required
+                                                       name="transportPort_{{serverIndex}}"
+                                                       ng-model="server.transport.port"
+                                                       min="1"
+                                                       max="65535"
+                                                >
+                                                <div ng-messages="theForm['transportPort_' + serverIndex].$error">
+                                                    <div translate
+                                                         ng-message="required"
+                                                    >extension.field-required</div>
+                                                    <div translate
+                                                         ng-message="min"
+                                                    >extension.port-range</div>
+                                                    <div translate
+                                                         ng-message="max"
+                                                    >extension.port-range</div>
+                                                </div>
+                                            </md-input-container>
+                                            
+                                            <md-input-container flex="33" class="md-block">
+                                                <label translate>extension.timeout</label>
+                                                <input type="number"
+                                                       required name="transportTimeout_{{serverIndex}}"
+                                                       ng-model="server.transport.timeout"
+                                                >
+                                                <div ng-messages="theForm['transportTimeout_' + serverIndex].$error">
+                                                    <div translate
+                                                         ng-message="required"
+                                                    >extension.field-required</div>
+                                                </div>
+                                            </md-input-container>
+                                        </div>
+                                        
+                                        <div ng-if="server.transport.type == 'rtu'">
+                                        
+                                            <div layout="row">
+                                                <md-input-container flex="70" class="md-block">
+                                                    <label translate>extension.modbus-port-name</label>
+                                                    <input required name="transportPortName_{{serverIndex}}" ng-model="server.transport.portName">
+                                                    <div ng-messages="theForm['transportPortName_' + serverIndex].$error">
+                                                        <div translate ng-message="required">extension.field-required</div>
+                                                    </div>
+                                                </md-input-container>
+                                                
+                                                <md-input-container flex="30" class="md-block">
+                                                    <label translate>extension.timeout</label>
+                                                    <input type="number"
+                                                           required name="transportTimeout_{{serverIndex}}"
+                                                           ng-model="server.transport.timeout"
+                                                    >
+                                                    <div ng-messages="theForm['transportTimeout_' + serverIndex].$error">
+                                                        <div translate
+                                                             ng-message="required"
+                                                        >extension.field-required</div>
+                                                    </div>
+                                                </md-input-container>
+                                            </div>
+                                            
+                                            <div layout="row">
+                                                <md-input-container flex="50" class="md-block tb-container-for-select">
+                                                    <label translate>extension.modbus-encoding</label>
+                                                    <md-select required
+                                                               name="transportEncoding_{{serverIndex}}"
+                                                               ng-model="server.transport.encoding"
+                                                    >
+                                                        <md-option ng-value="encodingType"
+                                                                   ng-repeat="(encodingType, encodingValue) in types.extensionModbusRtuEncodings"
+                                                        ><span ng-bind="encodingValue"></span></md-option>
+                                                    </md-select>
+                                                    <div ng-messages="theForm['transportEncoding_' + serverIndex].$error">
+                                                        <div translate
+                                                             ng-message="required"
+                                                        >extension.field-required</div>
+                                                    </div>
+                                                </md-input-container>
+                                                
+                                                <md-input-container flex="50" class="md-block tb-container-for-select">
+                                                    <label translate>extension.modbus-parity</label>
+                                                    <md-select name="transportParity_{{serverIndex}}" ng-model="server.transport.parity">
+                                                        <md-option ng-repeat="(parityKey, parityValue) in types.extensionModbusRtuParities"
+                                                                   ng-value="parityValue"
+                                                        >
+                                                            {{parityValue}}
+                                                        </md-option>
+                                                    </md-select>
+                                                    <div ng-messages="theForm['transportParity_' + serverIndex].$error">
+                                                        <div translate
+                                                             ng-message="required"
+                                                        >extension.field-required</div>
+                                                    </div>
+                                                </md-input-container>
+                                            
+                                            </div>
+                                            
+                                            <div layout="row">
+                                                <md-input-container flex="33" class="md-block">
+                                                    <label translate>extension.modbus-baudrate</label>
+                                                    <input type="number"
+                                                           required name="transportBaudRate_{{serverIndex}}"
+                                                           ng-model="server.transport.baudRate"
+                                                    >
+                                                    <div ng-messages="theForm['transportBaudRate_' + serverIndex].$error">
+                                                        <div translate
+                                                             ng-message="required"
+                                                        >extension.field-required</div>
+                                                    </div>
+                                                </md-input-container>
+                                                
+                                                <md-input-container flex="33" class="md-block">
+                                                    <label translate>extension.modbus-databits</label>
+                                                    <input type="number"
+                                                           required
+                                                           name="transportDataBits_{{serverIndex}}"
+                                                           ng-model="server.transport.dataBits"
+                                                           min="7"
+                                                           max="8"
+                                                    >
+                                                    <div ng-messages="theForm['transportDataBits_' + serverIndex].$error">
+                                                        <div translate
+                                                             ng-message="required"
+                                                        >extension.field-required</div>
+                                                        <div translate
+                                                             ng-message="min"
+                                                        >extension.modbus-databits-range</div>
+                                                        <div translate
+                                                             ng-message="max"
+                                                        >extension.modbus-databits-range</div>
+                                                    </div>
+                                                </md-input-container>
+                                                
+                                                <md-input-container flex="33" class="md-block">
+                                                    <label translate>extension.modbus-stopbits</label>
+                                                    <input type="number"
+                                                           required
+                                                           name="transportStopBits_{{serverIndex}}"
+                                                           ng-model="server.transport.stopBits"
+                                                           min="1"
+                                                           max="2"
+                                                    >
+                                                    <div ng-messages="theForm['transportStopBits_' + serverIndex].$error">
+                                                        <div translate
+                                                             ng-message="required"
+                                                        >extension.field-required</div>
+                                                        <div translate
+                                                             ng-message="min"
+                                                        >extension.modbus-stopbits-range</div>
+                                                        <div translate
+                                                             ng-message="max"
+                                                        >extension.modbus-stopbits-range</div>
+                                                    </div>
+                                                </md-input-container>
+                                            </div>                                          
+                                        </div>
+
+                                        <v-accordion id="modbus-mapping-accordion"
+                                                     class="vAccordion--default"
+                                                     onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
+                                            <v-pane id="modbus-mapping-pane_{{serverIndex}}">
+                                                <v-pane-header>
+                                                    {{ 'extension.mapping' | translate }}
+                                                </v-pane-header>
+                                                <v-pane-content>
+                                                    <div ng-if="server.devices.length > 0">
+                                                        <ol class="list-group">
+                                                            <li class="list-group-item"
+                                                                ng-repeat="(deviceIndex, device) in server.devices"
+                                                            >
+                                                                <md-button aria-label="{{ 'action.remove' | translate }}"
+                                                                           class="md-icon-button"
+                                                                           ng-click="removeItem(device, server.devices)"
+                                                                >
+                                                                    <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
+                                                                    <md-tooltip md-direction="top">
+                                                                        {{ 'action.remove' | translate }}
+                                                                    </md-tooltip>
+                                                                </md-button>
+
+                                                                <md-card>
+                                                                    <md-card-content>
+                                                                        <div flex layout="row">
+                                                                            <md-input-container flex="80" class="md-block">
+                                                                                <label translate>extension.modbus-device-name</label>
+                                                                                <input required
+                                                                                       name="deviceName_{{serverIndex}}{{deviceIndex}}"
+                                                                                       ng-model="device.deviceName"
+                                                                                >
+                                                                                <div ng-messages="theForm['deviceName_' + serverIndex + deviceIndex].$error">
+                                                                                    <div translate
+                                                                                         ng-message="required"
+                                                                                    >extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                        
+                                                                            <md-input-container flex="20" class="md-block">
+                                                                                <label translate>extension.modbus-unit-id</label>
+                                                                                <input type="number"
+                                                                                       required
+                                                                                       name="unitId_{{serverIndex}}{{deviceIndex}}"
+                                                                                       ng-model="device.unitId"
+                                                                                       min="1"
+                                                                                       max="247"
+                                                                                >
+                                                                                
+                                                                                <div ng-messages="theForm['unitId_' + serverIndex + deviceIndex].$error">
+                                                                                    <div translate
+                                                                                         ng-message="required"
+                                                                                    >extension.field-required</div>
+                                                                                    <div translate
+                                                                                         ng-message="min"
+                                                                                    >extension.modbus-unit-id-range</div>
+                                                                                    <div translate
+                                                                                         ng-message="max"
+                                                                                    >extension.modbus-unit-id-range</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                        </div>
+                                                                          
+                                                                        <div flex layout="row">  
+                                                                            <md-input-container flex="50" class="md-block">
+                                                                                <label translate>extension.modbus-attributes-poll-period</label>
+                                                                                <input type="number"
+                                                                                       required
+                                                                                       name="attributesPollPeriod_{{serverIndex}}{{deviceIndex}}"
+                                                                                       ng-model="device.attributesPollPeriod"
+                                                                                       min="1"
+                                                                                >
+                                                                                
+                                                                                <div ng-messages="theForm['attributesPollPeriod_' + serverIndex + deviceIndex].$error">
+                                                                                    <div translate
+                                                                                         ng-message="required"
+                                                                                    >extension.field-required</div>
+                                                                                    <div translate
+                                                                                         ng-message="min"
+                                                                                    >extension.modbus-poll-period-range</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                            
+                                                                            <md-input-container flex="50" class="md-block">
+                                                                                <label translate>extension.modbus-timeseries-poll-period</label>
+                                                                                <input type="number"
+                                                                                       required
+                                                                                       name="timeseriesPollPeriod_{{serverIndex}}{{deviceIndex}}"
+                                                                                       ng-model="device.timeseriesPollPeriod"
+                                                                                       min="1"
+                                                                                >
+                                                                                
+                                                                                <div ng-messages="theForm['timeseriesPollPeriod_' + serverIndex + deviceIndex].$error">
+                                                                                    <div translate
+                                                                                         ng-message="required"
+                                                                                    >extension.field-required</div>
+                                                                                    <div translate
+                                                                                         ng-message="min"
+                                                                                    >extension.modbus-poll-period-range</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                            
+                                                                        </div>
+
+
+                                                                        <v-accordion id="modbus-attributes-accordion"
+                                                                                     class="vAccordion--default"
+                                                                                     onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
+                                                                            <v-pane id="modbus-attributes-pane_{{serverIndex}}{{deviceIndex}}">
+                                                                                <v-pane-header>
+                                                                                    {{ 'extension.attributes' | translate }}
+                                                                                </v-pane-header>
+                                                                                <v-pane-content>
+                                                                                    <div ng-show="device.attributes.length > 0">
+                                                                                        <ol class="list-group">
+                                                                                            <li class="list-group-item"
+                                                                                                ng-repeat="(attributeIndex, attribute) in device.attributes"
+                                                                                            >
+                                                                                                <md-button aria-label="{{ 'action.remove' | translate }}"
+                                                                                                           class="md-icon-button"
+                                                                                                           ng-click="removeItem(attribute, device.attributes)">
+                                                                                                    <ng-md-icon icon="close"
+                                                                                                                aria-label="{{ 'action.remove' | translate }}"
+                                                                                                    ></ng-md-icon>
+                                                                                                    <md-tooltip md-direction="top">
+                                                                                                        {{ 'action.remove' | translate }}
+                                                                                                    </md-tooltip>
+                                                                                                </md-button>
+                                                                                                <md-card>
+                                                                                                    <md-card-content>
+
+                                                                                                        <section flex layout="row">
+                                                                                                            <md-input-container flex="50" class="md-block">
+                                                                                                                <label translate>extension.modbus-tag</label>
+                                                                                                                <input required
+                                                                                                                       name="modbusAttributeTag_{{serverIndex}}{{deviceIndex}}{{attributeIndex}}"
+                                                                                                                       ng-model="attribute.tag"
+                                                                                                                >
+                                                                                                                <div ng-messages="theForm['modbusAttributeTag_' + serverIndex + deviceIndex + attributeIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+
+                                                                                                            <md-input-container flex="30" class="md-block tb-container-for-select">
+                                                                                                                <label translate>extension.type</label>
+                                                                                                                <md-select required name="modbusAttributeType_{{serverIndex}}{{deviceIndex}}{{attributeIndex}}"
+                                                                                                                           ng-model="attribute.type"
+                                                                                                                >
+                                                                                                                    <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType"
+                                                                                                                               ng-value="attrType"
+                                                                                                                    >
+                                                                                                                        {{attrTypeValue | translate}}
+                                                                                                                    </md-option>
+                                                                                                                </md-select>
+                                                                                                                <div ng-messages="theForm['modbusAttributeType_' + serverIndex + deviceIndex + attributeIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                            
+                                                                                                            <md-input-container flex="20" class="md-block">
+                                                                                                                <label translate>extension.modbus-poll-period</label>
+                                                                                                                <input type="number"
+                                                                                                                       name="pollPeriod_{{serverIndex}}{{deviceIndex}}"
+                                                                                                                       ng-model="attribute.pollPeriod"
+                                                                                                                       min="1"
+                                                                                                                >
+                                                                                                                
+                                                                                                                <div ng-messages="theForm['pollPeriod_' + serverIndex + deviceIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="min"
+                                                                                                                    >extension.modbus-poll-period-range</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                        </section>
+                                                                                                        
+                                                                                                        <section flex layout="row">                                                                                                        
+                                                                                                            <md-input-container flex="50" class="md-block tb-container-for-select">
+                                                                                                                <label translate>extension.type</label>
+                                                                                                                <md-select required name="modbusAttributeFunctionCode_{{serverIndex}}{{deviceIndex}}{{attributeIndex}}"
+                                                                                                                           ng-model="attribute.functionCode"
+                                                                                                                >
+                                                                                                                    <md-option ng-repeat="(functionCode, functionName) in types.extensionModbusFunctionCodes"
+                                                                                                                               ng-value="functionCode"
+                                                                                                                    >
+                                                                                                                        {{functionName}}
+                                                                                                                    </md-option>
+                                                                                                                </md-select>
+                                                                                                                <div ng-messages="theForm['modbusAttributeFunctionCode_' + serverIndex + deviceIndex + attributeIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+
+                                                                                                            <md-input-container flex="50" class="md-block">
+                                                                                                                <label translate>extension.modbus-register-address</label>
+                                                                                                                <input type="number"
+                                                                                                                       required
+                                                                                                                       name="address_{{serverIndex}}{{deviceIndex}}"
+                                                                                                                       ng-model="attribute.address"
+                                                                                                                       min="0"
+                                                                                                                       max="65535"
+                                                                                                                >
+                                                                                                                
+                                                                                                                <div ng-messages="theForm['address_' + serverIndex + deviceIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                    <div translate
+                                                                                                                         ng-message="min"
+                                                                                                                    >extension.modbus-register-address-range</div>
+                                                                                                                    <div translate
+                                                                                                                         ng-message="max"
+                                                                                                                    >extension.modbus-register-address-range</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                            
+                                                                                                        </section>
+                                                                                                        
+                                                                                                        <section flex layout="row">                                                                                                        
+                                                                                                            <md-input-container flex="50" class="md-block" ng-if="attribute.type != 'boolean'">
+                                                                                                                <label translate>extension.modbus-register-count</label>
+                                                                                                                <input type="number"
+                                                                                                                       name="registerCount_{{serverIndex}}{{deviceIndex}}"
+                                                                                                                       ng-model="attribute.registerCount"
+                                                                                                                       min="1"
+                                                                                                                >
+                                                                                                                
+                                                                                                                <div ng-messages="theForm['registerCount_' + serverIndex + deviceIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="min"
+                                                                                                                    >extension.modbus-register-count-range</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                        
+                                                                                                            <md-input-container flex="50" class="md-block" ng-if="attribute.type == 'boolean' && attribute.functionCode >= 3">
+                                                                                                                <label translate>extension.modbus-register-bit-index</label>
+                                                                                                                <input type="number"
+                                                                                                                       required
+                                                                                                                       name="bit_{{serverIndex}}{{deviceIndex}}"
+                                                                                                                       ng-model="attribute.bit"
+                                                                                                                       min="0"
+                                                                                                                       max="15"
+                                                                                                                >
+                                                                                                                
+                                                                                                                <div ng-messages="theForm['bit_' + serverIndex + deviceIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                    <div translate
+                                                                                                                         ng-message="min"
+                                                                                                                    >extension.modbus-register-bit-index-range</div>
+                                                                                                                    <div translate
+                                                                                                                         ng-message="max"
+                                                                                                                    >extension.modbus-register-bit-index-range</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                        </section>
+                                                                                                        
+                                                                                                        <section flex layout="row">    
+                                                                                                            <md-input-container flex="100" class="md-block" ng-if="attribute.functionCode >= 3">
+                                                                                                                <label translate>extension.modbus-byte-order</label>
+                                                                                                                <input required
+                                                                                                                       name="modbusByteOrder_{{serverIndex}}{{deviceIndex}}{{attributeIndex}}"
+                                                                                                                       ng-model="attribute.byteOrder"
+                                                                                                                >
+                                                                                                                <div ng-messages="theForm['modbusByteOrder_' + serverIndex + deviceIndex + attributeIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container> 
+                                                                                                            
+                                                                                                        </section>
+
+
+                                                                                                    </md-card-content>
+                                                                                                </md-card>
+                                                                                            </li>
+                                                                                        </ol>
+                                                                                    </div>
+                                                                                    <div flex layout="row" layout-align="start center">
+                                                                                        <md-button class="md-primary md-raised"
+                                                                                                   ng-click="addNewAttribute(device)"
+                                                                                                   aria-label="{{ 'action.add' | translate }}"
+                                                                                        >
+                                                                                            <md-icon class="material-icons">add</md-icon>
+                                                                                            <span translate>extension.add-attribute</span>
+                                                                                        </md-button>
+                                                                                    </div>
+                                                                                </v-pane-content>
+                                                                            </v-pane>
+                                                                        </v-accordion>
+
+                                                                        <v-accordion id="modbus-timeseries-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
+                                                                            <v-pane id="modbus-timeseries-pane_{{serverIndex}}{{deviceIndex}}">
+                                                                                <v-pane-header>
+                                                                                    {{ 'extension.timeseries' | translate }}
+                                                                                </v-pane-header>
+                                                                                <v-pane-content>
+                                                                                    <div ng-show="device.timeseries.length > 0">
+                                                                                        <ol class="list-group">
+                                                                                            <li class="list-group-item"
+                                                                                                ng-repeat="(timeseriesIndex, timeserie) in device.timeseries"
+                                                                                            >
+                                                                                                <md-button aria-label="{{ 'action.remove' | translate }}"
+                                                                                                           class="md-icon-button"
+                                                                                                           ng-click="removeItem(timeserie, device.timeseries)">
+                                                                                                    <ng-md-icon icon="close"
+                                                                                                                aria-label="{{ 'action.remove' | translate }}"
+                                                                                                    ></ng-md-icon>
+                                                                                                    <md-tooltip md-direction="top">
+                                                                                                        {{ 'action.remove' | translate }}
+                                                                                                    </md-tooltip>
+                                                                                                </md-button>
+                                                                                                <md-card>
+                                                                                                    <md-card-content>
+
+                                                                                                        <section flex layout="row">
+                                                                                                            <md-input-container flex="50" class="md-block">
+                                                                                                                <label translate>extension.modbus-tag</label>
+                                                                                                                <input required
+                                                                                                                       name="modbusTimeserieTag_{{serverIndex}}{{deviceIndex}}{{timeseriesIndex}}"
+                                                                                                                       ng-model="timeserie.tag"
+                                                                                                                >
+                                                                                                                <div ng-messages="theForm['modbusTimeserieTag_' + serverIndex + deviceIndex + timeseriesIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+
+                                                                                                            <md-input-container flex="30" class="md-block tb-container-for-select">
+                                                                                                                <label translate>extension.type</label>
+                                                                                                                <md-select required name="modbusTimeserieType_{{serverIndex}}{{deviceIndex}}{{timeseriesIndex}}"
+                                                                                                                           ng-model="timeserie.type"
+                                                                                                                >
+                                                                                                                    <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType"
+                                                                                                                               ng-value="attrType"
+                                                                                                                    >
+                                                                                                                        {{attrTypeValue | translate}}
+                                                                                                                    </md-option>
+                                                                                                                </md-select>
+                                                                                                                <div ng-messages="theForm['modbusTimeserieType_' + serverIndex + deviceIndex + timeseriesIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                            
+                                                                                                            <md-input-container flex="20" class="md-block">
+                                                                                                                <label translate>extension.modbus-poll-period</label>
+                                                                                                                <input type="number"
+                                                                                                                       name="pollPeriod_{{serverIndex}}{{deviceIndex}}"
+                                                                                                                       ng-model="timeserie.pollPeriod"
+                                                                                                                       min="1"
+                                                                                                                >
+                                                                                                                
+                                                                                                                <div ng-messages="theForm['pollPeriod_' + serverIndex + deviceIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="min"
+                                                                                                                    >extension.modbus-poll-period-range</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                        </section>
+                                                                                                        
+                                                                                                        <section flex layout="row">  
+                                                                                                            <md-input-container flex="50" class="md-block tb-container-for-select">
+                                                                                                                <label translate>extension.type</label>
+                                                                                                                <md-select required name="modbusTimeserieFunctionCode_{{serverIndex}}{{deviceIndex}}{{timeseriesIndex}}"
+                                                                                                                           ng-model="timeserie.functionCode"
+                                                                                                                >
+                                                                                                                    <md-option ng-repeat="(functionCode, functionName) in types.extensionModbusFunctionCodes"
+                                                                                                                               ng-value="functionCode"
+                                                                                                                    >
+                                                                                                                        {{functionName}}
+                                                                                                                    </md-option>
+                                                                                                                </md-select>
+                                                                                                                <div ng-messages="theForm['modbusTimeserieFunctionCode_' + serverIndex + deviceIndex + timeseriesIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+
+                                                                                                            <md-input-container flex="50" class="md-block">
+                                                                                                                <label translate>extension.modbus-register-address</label>
+                                                                                                                <input type="number"
+                                                                                                                       required
+                                                                                                                       name="address_{{serverIndex}}{{deviceIndex}}"
+                                                                                                                       ng-model="timeserie.address"
+                                                                                                                       min="0"
+                                                                                                                       max="65535"
+                                                                                                                >
+                                                                                                                
+                                                                                                                <div ng-messages="theForm['address_' + serverIndex + deviceIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                    <div translate
+                                                                                                                         ng-message="min"
+                                                                                                                    >extension.modbus-register-address-range</div>
+                                                                                                                    <div translate
+                                                                                                                         ng-message="max"
+                                                                                                                    >extension.modbus-register-address-range</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                        </section>
+                                                                                                        
+                                                                                                        <section flex layout="row">
+                                                                                                            <md-input-container flex="50" class="md-block" ng-if="timeserie.type != 'boolean'">
+                                                                                                                <label translate>extension.modbus-register-count</label>
+                                                                                                                <input type="number"
+                                                                                                                       name="registerCount_{{serverIndex}}{{deviceIndex}}"
+                                                                                                                       ng-model="timeserie.registerCount"
+                                                                                                                       min="1"
+                                                                                                                >
+                                                                                                                
+                                                                                                                <div ng-messages="theForm['registerCount_' + serverIndex + deviceIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="min"
+                                                                                                                    >extension.modbus-register-count-range</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+
+                                                                                                            <md-input-container flex="50" class="md-block" ng-if="timeserie.type == 'boolean' && timeserie.functionCode >= 3">
+                                                                                                                <label translate>extension.modbus-register-bit-index</label>
+                                                                                                                <input type="number"
+                                                                                                                       required
+                                                                                                                       name="bit_{{serverIndex}}{{deviceIndex}}"
+                                                                                                                       ng-model="timeserie.bit"
+                                                                                                                       min="0"
+                                                                                                                       max="15"
+                                                                                                                >
+                                                                                                                
+                                                                                                                <div ng-messages="theForm['bit_' + serverIndex + deviceIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                    <div translate
+                                                                                                                         ng-message="min"
+                                                                                                                    >extension.modbus-register-bit-index-range</div>
+                                                                                                                    <div translate
+                                                                                                                         ng-message="max"
+                                                                                                                    >extension.modbus-register-bit-index-range</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                        </section>
+                                                                                                        
+                                                                                                        <section flex layout="row">                                                                                                           
+                                                                                                            <md-input-container flex="100" class="md-block" ng-if="timeserie.functionCode >= 3">
+                                                                                                                <label translate>extension.modbus-byte-order</label>
+                                                                                                                <input required
+                                                                                                                       name="modbusByteOrder_{{serverIndex}}{{deviceIndex}}{{timeseriesIndex}}"
+                                                                                                                       ng-model="timeserie.byteOrder"
+                                                                                                                >
+                                                                                                                <div ng-messages="theForm['modbusByteOrder_' + serverIndex + deviceIndex + timeseriesIndex].$error">
+                                                                                                                    <div translate
+                                                                                                                         ng-message="required"
+                                                                                                                    >extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container> 
+                                                                                                            
+                                                                                                        </section>
+                                                                                                    </md-card-content>
+                                                                                                </md-card>
+                                                                                            </li>
+                                                                                        </ol>
+                                                                                    </div>
+                                                                                    <div flex layout="row" layout-align="start center">
+                                                                                        <md-button class="md-primary md-raised"
+                                                                                                   ng-click="addNewTimeseries(device)"
+                                                                                                   aria-label="{{ 'action.add' | translate }}"
+                                                                                        >
+                                                                                            <md-icon class="material-icons">add</md-icon>
+                                                                                            <span translate>extension.add-timeseries</span>
+                                                                                        </md-button>
+                                                                                    </div>
+                                                                                </v-pane-content>
+                                                                            </v-pane>
+                                                                        </v-accordion>
+
+
+                                                                    </md-card-content>
+                                                                </md-card>
+                                                            </li>
+                                                        </ol>
+                                                    </div>
+                                                    <div flex
+                                                         layout="row"
+                                                         layout-align="start center"
+                                                    >
+                                                        <md-button class="md-primary md-raised"
+                                                                   ng-click="addDevice(server.devices)"
+                                                                   aria-label="{{ 'action.add' | translate }}"
+                                                        >
+                                                            <md-icon class="material-icons">add</md-icon>
+                                                            <span translate>extension.add-device</span>
+                                                        </md-button>
+                                                    </div>
+                                                </v-pane-content>
+                                            </v-pane>
+                                        </v-accordion>
+
+                                    </md-card-content>
+                                </md-card>
+                            </li>
+                        </ol>
+
+                        <div flex
+                             layout="row"
+                             layout-align="start center"
+                        >
+                            <md-button class="md-primary md-raised"
+                                       ng-click="addServer(configuration.servers)"
+                                       aria-label="{{ 'action.add' | translate }}"
+                            >
+                                <md-icon class="material-icons">add</md-icon>
+                                <span translate>extension.modbus-add-server</span>
+                            </md-button>
+                        </div>
+
+                    </div>
+                </v-pane-content>
+            </v-pane>
+        </v-accordion>
+        <!--{{config}}-->
+    </md-card-content>
+</md-card>
\ No newline at end of file
diff --git a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html
index 5a7c00b..59d8a7e 100644
--- a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html
+++ b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html
@@ -194,7 +194,7 @@
                                             </md-input-container>
                                         </div>
 
-                                        <v-accordion id="opc-keystore-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
+                                        <v-accordion id="opc-keystore-accordion" class="vAccordion--default" ng-if="server.security != 'None'" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
                                             <v-pane id="opc-keystore-pane__{{serverIndex}}" expanded="true">
                                                 <v-pane-header>
                                                     {{ 'extension.opc-keystore' | translate }}
diff --git a/ui/src/app/extension/index.js b/ui/src/app/extension/index.js
index f04880c..8e29348 100644
--- a/ui/src/app/extension/index.js
+++ b/ui/src/app/extension/index.js
@@ -17,6 +17,8 @@ import ExtensionTableDirective from './extension-table.directive';
 import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive';
 import ExtensionFormMqttDirective from './extensions-forms/extension-form-mqtt.directive'
 import ExtensionFormOpcDirective from './extensions-forms/extension-form-opc.directive';
+import ExtensionFormModbusDirective from './extensions-forms/extension-form-modbus.directive';
+
 import {ParseToNull} from './extension-dialog.controller';
 
 export default angular.module('thingsboard.extension', [])
@@ -24,5 +26,6 @@ export default angular.module('thingsboard.extension', [])
     .directive('tbExtensionFormHttp', ExtensionFormHttpDirective)
     .directive('tbExtensionFormMqtt', ExtensionFormMqttDirective)
     .directive('tbExtensionFormOpc', ExtensionFormOpcDirective)
+    .directive('tbExtensionFormModbus', ExtensionFormModbusDirective)
     .directive('parseToNull', ParseToNull)
     .name;
\ No newline at end of file
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 9d1052f..2a5eab4 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -886,8 +886,10 @@ export default angular.module('thingsboard.locale', [])
                     "response-timeout": "Response timeout in milliseconds",
                     "topic-expression": "Topic expression",
                     "client-scope": "Client scope",
+                    "add-device": "Add device",
                     "opc-server": "Servers",
                     "opc-add-server": "Add server",
+                    "opc-add-server-prompt": "Please add server",
                     "opc-application-name": "Application name",
                     "opc-application-uri": "Application uri",
                     "opc-scan-period-in-seconds": "Scan period in seconds",
@@ -902,6 +904,34 @@ export default angular.module('thingsboard.locale', [])
                     "opc-keystore-key-password":"Key password",
                     "opc-device-node-pattern":"Device node pattern",
                     "opc-device-name-pattern":"Device name pattern",
+                    "modbus-server": "Servers/slaves",
+                    "modbus-add-server": "Add server/slave",
+                    "modbus-add-server-prompt": "Please add server/slave",
+                    "modbus-transport": "Transport",
+                    "modbus-port-name": "Serial port name",
+                    "modbus-encoding": "Encoding",
+                    "modbus-parity": "Parity",
+                    "modbus-baudrate": "Baud rate",
+                    "modbus-databits": "Data bits",
+                    "modbus-stopbits": "Stop bits",
+                    "modbus-databits-range": "Data bits should be in a range from 7 to 8.",
+                    "modbus-stopbits-range": "Stop bits should be in a range from 1 to 2.",
+                    "modbus-unit-id": "Unit ID",
+                    "modbus-unit-id-range": "Unit ID should be in a range from 1 to 247.",
+                    "modbus-device-name":"Device name",
+                    "modbus-poll-period": "Poll period (ms)",
+                    "modbus-attributes-poll-period": "Attributes poll period (ms)",
+                    "modbus-timeseries-poll-period": "Timeseries poll period (ms)",
+                    "modbus-poll-period-range": "Poll period should be positive value.",
+                    "modbus-tag": "Tag",
+                    "modbus-function": "Function",
+                    "modbus-register-address": "Register address",
+                    "modbus-register-address-range": "Register address should be in a range from 0 to 65535.",
+                    "modbus-register-bit-index": "Bit index",
+                    "modbus-register-bit-index-range": "Bit index should be in a range from 0 to 15.",
+                    "modbus-register-count": "Register count",
+                    "modbus-register-count-range": "Register count should be a positive value.",
+                    "modbus-byte-order": "Byte order",
 
                     "sync": {
                         "status": "Status",
diff --git a/ui/src/app/widget/lib/flot-widget.js b/ui/src/app/widget/lib/flot-widget.js
index 7ce5987..1f363d6 100644
--- a/ui/src/app/widget/lib/flot-widget.js
+++ b/ui/src/app/widget/lib/flot-widget.js
@@ -251,7 +251,7 @@ export default class TbFlot {
                 if (this.tickUnits) {
                      formatted += ' ' + this.tickUnits;
                 }
-             
+
                 return formatted;
             };
 
@@ -1097,7 +1097,7 @@ export default class TbFlot {
                     "axisTickDecimals": {
                         "title": "Axis tick number of digits after floating point",
                         "type": "number",
-                        "default": 0
+                        "default": null
                     },
                     "axisTickSize": {
                         "title": "Axis step size between ticks",