diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index af5b8d4de2..ac7a25923d 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 2e56416351..ca94822301 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 836bd3d314..a0cb1fbdcb 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>> inboundRelationsListTo = new ArrayList<>(); + List>> inboundRelationsList = new ArrayList<>(); for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) { - inboundRelationsListTo.add(relationDao.findAllByTo(entity, typeGroup)); + inboundRelationsList.add(relationDao.findAllByTo(entity, typeGroup)); } - ListenableFuture>> inboundRelationsTo = Futures.allAsList(inboundRelationsListTo); - ListenableFuture> inboundDeletions = Futures.transform(inboundRelationsTo, (List> relations) -> + ListenableFuture>> inboundRelations = Futures.allAsList(inboundRelationsList); + ListenableFuture> inboundDeletions = Futures.transform(inboundRelations, (List> relations) -> getBooleans(relations, cache, true)); ListenableFuture inboundFuture = Futures.transform(inboundDeletions, getListToBooleanFunction()); @@ -186,12 +186,12 @@ public class BaseRelationService implements RelationService { log.error("Error deleting entity inbound relations", e); } - List>> inboundRelationsListFrom = new ArrayList<>(); + List>> outboundRelationsList = new ArrayList<>(); for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) { - inboundRelationsListFrom.add(relationDao.findAllByFrom(entity, typeGroup)); + outboundRelationsList.add(relationDao.findAllByFrom(entity, typeGroup)); } - ListenableFuture>> inboundRelationsFrom = Futures.allAsList(inboundRelationsListFrom); - Futures.transform(inboundRelationsFrom, (Function>, List>) relations -> + ListenableFuture>> outboundRelations = Futures.allAsList(outboundRelationsList); + Futures.transform(outboundRelations, (Function>, List>) relations -> getBooleans(relations, cache, false)); boolean outboundDeleteResult = relationDao.deleteOutboundRelations(entity); @@ -201,9 +201,7 @@ public class BaseRelationService implements RelationService { private List getBooleans(List> relations, Cache cache, boolean isRemove) { List results = new ArrayList<>(); for (List 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 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>> inboundRelationsListTo = new ArrayList<>(); + List>> inboundRelationsList = new ArrayList<>(); for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) { - inboundRelationsListTo.add(relationDao.findAllByTo(entity, typeGroup)); + inboundRelationsList.add(relationDao.findAllByTo(entity, typeGroup)); } - ListenableFuture>> inboundRelationsTo = Futures.allAsList(inboundRelationsListTo); - ListenableFuture> inboundDeletions = Futures.transform(inboundRelationsTo, + ListenableFuture>> inboundRelations = Futures.allAsList(inboundRelationsList); + ListenableFuture> inboundDeletions = Futures.transform(inboundRelations, (AsyncFunction>, List>) relations -> { List> results = getListenableFutures(relations, cache, true); return Futures.allAsList(results); @@ -235,12 +231,12 @@ public class BaseRelationService implements RelationService { ListenableFuture inboundFuture = Futures.transform(inboundDeletions, getListToBooleanFunction()); - List>> inboundRelationsListFrom = new ArrayList<>(); + List>> outboundRelationsList = new ArrayList<>(); for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) { - inboundRelationsListFrom.add(relationDao.findAllByTo(entity, typeGroup)); + outboundRelationsList.add(relationDao.findAllByFrom(entity, typeGroup)); } - ListenableFuture>> inboundRelationsFrom = Futures.allAsList(inboundRelationsListFrom); - Futures.transform(inboundRelationsFrom, (AsyncFunction>, List>) relations -> { + ListenableFuture>> outboundRelations = Futures.allAsList(outboundRelationsList); + Futures.transform(outboundRelations, (AsyncFunction>, List>) relations -> { List> results = getListenableFutures(relations, cache, false); return Futures.allAsList(results); }); @@ -252,9 +248,7 @@ public class BaseRelationService implements RelationService { private List> getListenableFutures(List> relations, Cache cache, boolean isRemove) { List> results = new ArrayList<>(); for (List 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> 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 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 toAndTypeGroup = new ArrayList<>(); - toAndTypeGroup.add(relation.getTo()); - toAndTypeGroup.add(relation.getTypeGroup()); - cache.evict(toAndTypeGroup); - - List toTypeAndTypeGroup = new ArrayList<>(); - fromTypeAndTypeGroup.add(relation.getTo()); - fromTypeAndTypeGroup.add(relation.getType()); - fromTypeAndTypeGroup.add(relation.getTypeGroup()); - cache.evict(toTypeAndTypeGroup); - } + List toAndTypeGroup = new ArrayList<>(); + toAndTypeGroup.add(relation.getTo()); + toAndTypeGroup.add(relation.getTypeGroup()); + cache.evict(toAndTypeGroup); + + List 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 0000000000..3af810e1e3 --- /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 diff --git a/docker/k8s/tb.yaml b/docker/k8s/tb.yaml index 74b6702d3d..1d94cb11ac 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 976d8bacad..432966eeff 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 { - 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
+
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 0000000000..2dfc961d02 --- /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 0000000000..989d3f07db --- /dev/null +++ b/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html @@ -0,0 +1,774 @@ + + + + + extension.configuration + + + + + + + + {{ 'extension.modbus-server' | translate }} + + + +
+ extension.modbus-add-server-prompt +
+ +
+
    +
  1. + + + + {{ 'action.remove' | translate }} + + + + + + +
    + + + + + + +
    +
    extension.field-required
    +
    +
    + +
    + +
    + + + +
    +
    extension.field-required
    +
    +
    + + + + +
    +
    extension.field-required
    +
    extension.port-range
    +
    extension.port-range
    +
    +
    + + + + +
    +
    extension.field-required
    +
    +
    +
    + +
    + +
    + + + +
    +
    extension.field-required
    +
    +
    + + + + +
    +
    extension.field-required
    +
    +
    +
    + +
    + + + + + +
    +
    extension.field-required
    +
    +
    + + + + + + {{parityValue}} + + +
    +
    extension.field-required
    +
    +
    + +
    + +
    + + + +
    +
    extension.field-required
    +
    +
    + + + + +
    +
    extension.field-required
    +
    extension.modbus-databits-range
    +
    extension.modbus-databits-range
    +
    +
    + + + + +
    +
    extension.field-required
    +
    extension.modbus-stopbits-range
    +
    extension.modbus-stopbits-range
    +
    +
    +
    +
    + + + + + {{ 'extension.mapping' | translate }} + + +
    +
      +
    1. + + + + {{ 'action.remove' | translate }} + + + + + +
      + + + +
      +
      extension.field-required
      +
      +
      + + + + + +
      +
      extension.field-required
      +
      extension.modbus-unit-id-range
      +
      extension.modbus-unit-id-range
      +
      +
      +
      + +
      + + + + +
      +
      extension.field-required
      +
      extension.modbus-poll-period-range
      +
      +
      + + + + + +
      +
      extension.field-required
      +
      extension.modbus-poll-period-range
      +
      +
      + +
      + + + + + + {{ 'extension.attributes' | translate }} + + +
      +
        +
      1. + + + + {{ 'action.remove' | translate }} + + + + + +
        + + + +
        +
        extension.field-required
        +
        +
        + + + + + + {{attrTypeValue | translate}} + + +
        +
        extension.field-required
        +
        +
        + + + + + +
        +
        extension.modbus-poll-period-range
        +
        +
        +
        + +
        + + + + + {{functionName}} + + +
        +
        extension.field-required
        +
        +
        + + + + + +
        +
        extension.field-required
        +
        extension.modbus-register-address-range
        +
        extension.modbus-register-address-range
        +
        +
        + +
        + +
        + + + + +
        +
        extension.modbus-register-count-range
        +
        +
        + + + + + +
        +
        extension.field-required
        +
        extension.modbus-register-bit-index-range
        +
        extension.modbus-register-bit-index-range
        +
        +
        +
        + +
        + + + +
        +
        extension.field-required
        +
        +
        + +
        + + +
        +
        +
      2. +
      +
      +
      + + add + extension.add-attribute + +
      +
      +
      +
      + + + + + {{ 'extension.timeseries' | translate }} + + +
      +
        +
      1. + + + + {{ 'action.remove' | translate }} + + + + + +
        + + + +
        +
        extension.field-required
        +
        +
        + + + + + + {{attrTypeValue | translate}} + + +
        +
        extension.field-required
        +
        +
        + + + + + +
        +
        extension.modbus-poll-period-range
        +
        +
        +
        + +
        + + + + + {{functionName}} + + +
        +
        extension.field-required
        +
        +
        + + + + + +
        +
        extension.field-required
        +
        extension.modbus-register-address-range
        +
        extension.modbus-register-address-range
        +
        +
        +
        + +
        + + + + +
        +
        extension.modbus-register-count-range
        +
        +
        + + + + + +
        +
        extension.field-required
        +
        extension.modbus-register-bit-index-range
        +
        extension.modbus-register-bit-index-range
        +
        +
        +
        + +
        + + + +
        +
        extension.field-required
        +
        +
        + +
        +
        +
        +
      2. +
      +
      +
      + + add + extension.add-timeseries + +
      +
      +
      +
      + + +
      +
      +
    2. +
    +
    +
    + + add + extension.add-device + +
    +
    +
    +
    + +
    +
    +
  2. +
+ +
+ + add + extension.modbus-add-server + +
+ +
+
+
+
+ +
+
\ 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 5a7c00ba3b..59d8a7e0b4 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 @@ - + {{ 'extension.opc-keystore' | translate }} diff --git a/ui/src/app/extension/index.js b/ui/src/app/extension/index.js index f04880cb97..8e293482f4 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 9d1052f3eb..2a5eab4825 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 7ce59872e9..1f363d627c 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",