Commit 7d179c6af84207423b3bddcf66bd1f9dca2ec592

Authored by Igor Kulikov
2 parents c5de8234 7193dae1

Merged with master

... ... @@ -87,6 +87,7 @@ mqtt:
87 87 leak_detector_level: "${NETTY_LEASK_DETECTOR_LVL:DISABLED}"
88 88 boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}"
89 89 worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}"
  90 + max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}"
90 91 # MQTT SSL configuration
91 92 ssl:
92 93 # Enable/disable SSL support
... ...
... ... @@ -60,6 +60,10 @@ public abstract class AbstractCassandraCluster {
60 60 private long initTimeout;
61 61 @Value("${cassandra.init_retry_interval_ms}")
62 62 private long initRetryInterval;
  63 + @Value("${cassandra.max_requests_per_connection_local:128}")
  64 + private int max_requests_local;
  65 + @Value("${cassandra.max_requests_per_connection_remote:128}")
  66 + private int max_requests_remote;
63 67
64 68 @Autowired
65 69 private CassandraSocketOptions socketOpts;
... ... @@ -90,8 +94,8 @@ public abstract class AbstractCassandraCluster {
90 94 .withClusterName(clusterName)
91 95 .withSocketOptions(socketOpts.getOpts())
92 96 .withPoolingOptions(new PoolingOptions()
93   - .setMaxRequestsPerConnection(HostDistance.LOCAL, 32768)
94   - .setMaxRequestsPerConnection(HostDistance.REMOTE, 32768));
  97 + .setMaxRequestsPerConnection(HostDistance.LOCAL, max_requests_local)
  98 + .setMaxRequestsPerConnection(HostDistance.REMOTE, max_requests_remote));
95 99 this.clusterBuilder.withQueryOptions(queryOpts.getOpts());
96 100 this.clusterBuilder.withCompression(StringUtils.isEmpty(compression) ? Compression.NONE : Compression.valueOf(compression.toUpperCase()));
97 101 if (ssl) {
... ...
... ... @@ -170,12 +170,12 @@ public class BaseRelationService implements RelationService {
170 170 Cache cache = cacheManager.getCache(RELATIONS_CACHE);
171 171 log.trace("Executing deleteEntityRelations [{}]", entity);
172 172 validate(entity);
173   - List<ListenableFuture<List<EntityRelation>>> inboundRelationsListTo = new ArrayList<>();
  173 + List<ListenableFuture<List<EntityRelation>>> inboundRelationsList = new ArrayList<>();
174 174 for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) {
175   - inboundRelationsListTo.add(relationDao.findAllByTo(entity, typeGroup));
  175 + inboundRelationsList.add(relationDao.findAllByTo(entity, typeGroup));
176 176 }
177   - ListenableFuture<List<List<EntityRelation>>> inboundRelationsTo = Futures.allAsList(inboundRelationsListTo);
178   - ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelationsTo, (List<List<EntityRelation>> relations) ->
  177 + ListenableFuture<List<List<EntityRelation>>> inboundRelations = Futures.allAsList(inboundRelationsList);
  178 + ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelations, (List<List<EntityRelation>> relations) ->
179 179 getBooleans(relations, cache, true));
180 180
181 181 ListenableFuture<Boolean> inboundFuture = Futures.transform(inboundDeletions, getListToBooleanFunction());
... ... @@ -186,12 +186,12 @@ public class BaseRelationService implements RelationService {
186 186 log.error("Error deleting entity inbound relations", e);
187 187 }
188 188
189   - List<ListenableFuture<List<EntityRelation>>> inboundRelationsListFrom = new ArrayList<>();
  189 + List<ListenableFuture<List<EntityRelation>>> outboundRelationsList = new ArrayList<>();
190 190 for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) {
191   - inboundRelationsListFrom.add(relationDao.findAllByFrom(entity, typeGroup));
  191 + outboundRelationsList.add(relationDao.findAllByFrom(entity, typeGroup));
192 192 }
193   - ListenableFuture<List<List<EntityRelation>>> inboundRelationsFrom = Futures.allAsList(inboundRelationsListFrom);
194   - Futures.transform(inboundRelationsFrom, (Function<List<List<EntityRelation>>, List<Boolean>>) relations ->
  193 + ListenableFuture<List<List<EntityRelation>>> outboundRelations = Futures.allAsList(outboundRelationsList);
  194 + Futures.transform(outboundRelations, (Function<List<List<EntityRelation>>, List<Boolean>>) relations ->
195 195 getBooleans(relations, cache, false));
196 196
197 197 boolean outboundDeleteResult = relationDao.deleteOutboundRelations(entity);
... ... @@ -201,9 +201,7 @@ public class BaseRelationService implements RelationService {
201 201 private List<Boolean> getBooleans(List<List<EntityRelation>> relations, Cache cache, boolean isRemove) {
202 202 List<Boolean> results = new ArrayList<>();
203 203 for (List<EntityRelation> relationList : relations) {
204   - relationList.stream().forEach(relation -> {
205   - checkFromDeleteSync(cache, results, relation, isRemove);
206   - });
  204 + relationList.stream().forEach(relation -> checkFromDeleteSync(cache, results, relation, isRemove));
207 205 }
208 206 return results;
209 207 }
... ... @@ -211,10 +209,8 @@ public class BaseRelationService implements RelationService {
211 209 private void checkFromDeleteSync(Cache cache, List<Boolean> results, EntityRelation relation, boolean isRemove) {
212 210 if (isRemove) {
213 211 results.add(relationDao.deleteRelation(relation));
214   - cacheEviction(relation, false, cache);
215   - } else {
216   - cacheEviction(relation, true, cache);
217 212 }
  213 + cacheEviction(relation, cache);
218 214 }
219 215
220 216 @Override
... ... @@ -222,12 +218,12 @@ public class BaseRelationService implements RelationService {
222 218 Cache cache = cacheManager.getCache(RELATIONS_CACHE);
223 219 log.trace("Executing deleteEntityRelationsAsync [{}]", entity);
224 220 validate(entity);
225   - List<ListenableFuture<List<EntityRelation>>> inboundRelationsListTo = new ArrayList<>();
  221 + List<ListenableFuture<List<EntityRelation>>> inboundRelationsList = new ArrayList<>();
226 222 for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) {
227   - inboundRelationsListTo.add(relationDao.findAllByTo(entity, typeGroup));
  223 + inboundRelationsList.add(relationDao.findAllByTo(entity, typeGroup));
228 224 }
229   - ListenableFuture<List<List<EntityRelation>>> inboundRelationsTo = Futures.allAsList(inboundRelationsListTo);
230   - ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelationsTo,
  225 + ListenableFuture<List<List<EntityRelation>>> inboundRelations = Futures.allAsList(inboundRelationsList);
  226 + ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelations,
231 227 (AsyncFunction<List<List<EntityRelation>>, List<Boolean>>) relations -> {
232 228 List<ListenableFuture<Boolean>> results = getListenableFutures(relations, cache, true);
233 229 return Futures.allAsList(results);
... ... @@ -235,12 +231,12 @@ public class BaseRelationService implements RelationService {
235 231
236 232 ListenableFuture<Boolean> inboundFuture = Futures.transform(inboundDeletions, getListToBooleanFunction());
237 233
238   - List<ListenableFuture<List<EntityRelation>>> inboundRelationsListFrom = new ArrayList<>();
  234 + List<ListenableFuture<List<EntityRelation>>> outboundRelationsList = new ArrayList<>();
239 235 for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) {
240   - inboundRelationsListFrom.add(relationDao.findAllByTo(entity, typeGroup));
  236 + outboundRelationsList.add(relationDao.findAllByFrom(entity, typeGroup));
241 237 }
242   - ListenableFuture<List<List<EntityRelation>>> inboundRelationsFrom = Futures.allAsList(inboundRelationsListFrom);
243   - Futures.transform(inboundRelationsFrom, (AsyncFunction<List<List<EntityRelation>>, List<Boolean>>) relations -> {
  238 + ListenableFuture<List<List<EntityRelation>>> outboundRelations = Futures.allAsList(outboundRelationsList);
  239 + Futures.transform(outboundRelations, (AsyncFunction<List<List<EntityRelation>>, List<Boolean>>) relations -> {
244 240 List<ListenableFuture<Boolean>> results = getListenableFutures(relations, cache, false);
245 241 return Futures.allAsList(results);
246 242 });
... ... @@ -252,9 +248,7 @@ public class BaseRelationService implements RelationService {
252 248 private List<ListenableFuture<Boolean>> getListenableFutures(List<List<EntityRelation>> relations, Cache cache, boolean isRemove) {
253 249 List<ListenableFuture<Boolean>> results = new ArrayList<>();
254 250 for (List<EntityRelation> relationList : relations) {
255   - relationList.stream().forEach(relation -> {
256   - checkFromDeleteAsync(cache, results, relation, isRemove);
257   - });
  251 + relationList.stream().forEach(relation -> checkFromDeleteAsync(cache, results, relation, isRemove));
258 252 }
259 253 return results;
260 254 }
... ... @@ -262,13 +256,11 @@ public class BaseRelationService implements RelationService {
262 256 private void checkFromDeleteAsync(Cache cache, List<ListenableFuture<Boolean>> results, EntityRelation relation, boolean isRemove) {
263 257 if (isRemove) {
264 258 results.add(relationDao.deleteRelationAsync(relation));
265   - cacheEviction(relation, false, cache);
266   - } else {
267   - cacheEviction(relation, true, cache);
268 259 }
  260 + cacheEviction(relation, cache);
269 261 }
270 262
271   - private void cacheEviction(EntityRelation relation, boolean outboundOnly, Cache cache) {
  263 + private void cacheEviction(EntityRelation relation, Cache cache) {
272 264 List<Object> fromToTypeAndTypeGroup = new ArrayList<>();
273 265 fromToTypeAndTypeGroup.add(relation.getFrom());
274 266 fromToTypeAndTypeGroup.add(relation.getTo());
... ... @@ -287,18 +279,16 @@ public class BaseRelationService implements RelationService {
287 279 fromAndTypeGroup.add(relation.getTypeGroup());
288 280 cache.evict(fromAndTypeGroup);
289 281
290   - if (!outboundOnly) {
291   - List<Object> toAndTypeGroup = new ArrayList<>();
292   - toAndTypeGroup.add(relation.getTo());
293   - toAndTypeGroup.add(relation.getTypeGroup());
294   - cache.evict(toAndTypeGroup);
295   -
296   - List<Object> toTypeAndTypeGroup = new ArrayList<>();
297   - fromTypeAndTypeGroup.add(relation.getTo());
298   - fromTypeAndTypeGroup.add(relation.getType());
299   - fromTypeAndTypeGroup.add(relation.getTypeGroup());
300   - cache.evict(toTypeAndTypeGroup);
301   - }
  282 + List<Object> toAndTypeGroup = new ArrayList<>();
  283 + toAndTypeGroup.add(relation.getTo());
  284 + toAndTypeGroup.add(relation.getTypeGroup());
  285 + cache.evict(toAndTypeGroup);
  286 +
  287 + List<Object> toTypeAndTypeGroup = new ArrayList<>();
  288 + fromTypeAndTypeGroup.add(relation.getTo());
  289 + fromTypeAndTypeGroup.add(relation.getType());
  290 + fromTypeAndTypeGroup.add(relation.getTypeGroup());
  291 + cache.evict(toTypeAndTypeGroup);
302 292 }
303 293
304 294 @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup}")
... ...
  1 +#
  2 +# Copyright © 2016-2018 The Thingsboard Authors
  3 +#
  4 +# Licensed under the Apache License, Version 2.0 (the "License");
  5 +# you may not use this file except in compliance with the License.
  6 +# You may obtain a copy of the License at
  7 +#
  8 +# http://www.apache.org/licenses/LICENSE-2.0
  9 +#
  10 +# Unless required by applicable law or agreed to in writing, software
  11 +# distributed under the License is distributed on an "AS IS" BASIS,
  12 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +# See the License for the specific language governing permissions and
  14 +# limitations under the License.
  15 +#
  16 +
  17 +---
  18 +apiVersion: v1
  19 +kind: Service
  20 +metadata:
  21 + labels:
  22 + name: redis-service
  23 + name: redis-service
  24 +spec:
  25 + ports:
  26 + - name: redis-service
  27 + protocol: TCP
  28 + port: 6379
  29 + targetPort: 6379
  30 + selector:
  31 + app: redis
  32 +---
  33 +apiVersion: v1
  34 +kind: ConfigMap
  35 +metadata:
  36 + name: redis-conf
  37 +data:
  38 + redis.conf: |
  39 + appendonly yes
  40 + protected-mode no
  41 + bind 0.0.0.0
  42 + port 6379
  43 + dir /var/lib/redis
  44 +---
  45 +apiVersion: apps/v1beta1
  46 +kind: StatefulSet
  47 +metadata:
  48 + name: redis
  49 +spec:
  50 + serviceName: redis-service
  51 + replicas: 1
  52 + template:
  53 + metadata:
  54 + labels:
  55 + app: redis
  56 + spec:
  57 + terminationGracePeriodSeconds: 10
  58 + containers:
  59 + - name: redis
  60 + image: redis:4.0.9
  61 + command:
  62 + - redis-server
  63 + args:
  64 + - /etc/redis/redis.conf
  65 + resources:
  66 + requests:
  67 + cpu: 100m
  68 + memory: 100Mi
  69 + ports:
  70 + - containerPort: 6379
  71 + name: redis
  72 + volumeMounts:
  73 + - name: redis-data
  74 + mountPath: /var/lib/redis
  75 + - name: redis-conf
  76 + mountPath: /etc/redis
  77 + volumes:
  78 + - name: redis-conf
  79 + configMap:
  80 + name: redis-conf
  81 + items:
  82 + - key: redis.conf
  83 + path: redis.conf
  84 + volumeClaimTemplates:
  85 + - metadata:
  86 + name: redis-data
  87 + annotations:
  88 + volume.beta.kubernetes.io/storage-class: fast
  89 + spec:
  90 + accessModes: [ "ReadWriteOnce" ]
  91 + resources:
  92 + requests:
  93 + storage: 1Gi
\ No newline at end of file
... ...
... ... @@ -54,6 +54,8 @@ data:
54 54 cassandra.host: "cassandra-headless"
55 55 cassandra.port: "9042"
56 56 database.type: "cassandra"
  57 + cache.type: "redis"
  58 + redis.host: "redis-service"
57 59 ---
58 60 apiVersion: apps/v1beta1
59 61 kind: StatefulSet
... ... @@ -127,6 +129,16 @@ spec:
127 129 valueFrom:
128 130 fieldRef:
129 131 fieldPath: status.podIP
  132 + - name: CACHE_TYPE
  133 + valueFrom:
  134 + configMapKeyRef:
  135 + name: tb-config
  136 + key: cache.type
  137 + - name: REDIS_HOST
  138 + valueFrom:
  139 + configMapKeyRef:
  140 + name: tb-config
  141 + key: redis.host
130 142 command:
131 143 - sh
132 144 - -c
... ...
... ... @@ -33,8 +33,6 @@ import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
33 33 */
34 34 public class MqttTransportServerInitializer extends ChannelInitializer<SocketChannel> {
35 35
36   - private static final int MAX_PAYLOAD_SIZE = 64 * 1024 * 1024;
37   -
38 36 private final SessionMsgProcessor processor;
39 37 private final DeviceService deviceService;
40 38 private final DeviceAuthService authService;
... ... @@ -42,10 +40,11 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
42 40 private final MqttTransportAdaptor adaptor;
43 41 private final MqttSslHandlerProvider sslHandlerProvider;
44 42 private final QuotaService quotaService;
  43 + private final int maxPayloadSize;
45 44
46 45 public MqttTransportServerInitializer(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService,
47 46 MqttTransportAdaptor adaptor, MqttSslHandlerProvider sslHandlerProvider,
48   - QuotaService quotaService) {
  47 + QuotaService quotaService, int maxPayloadSize) {
49 48 this.processor = processor;
50 49 this.deviceService = deviceService;
51 50 this.authService = authService;
... ... @@ -53,6 +52,7 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
53 52 this.adaptor = adaptor;
54 53 this.sslHandlerProvider = sslHandlerProvider;
55 54 this.quotaService = quotaService;
  55 + this.maxPayloadSize = maxPayloadSize;
56 56 }
57 57
58 58 @Override
... ... @@ -63,7 +63,7 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
63 63 sslHandler = sslHandlerProvider.getSslHandler();
64 64 pipeline.addLast(sslHandler);
65 65 }
66   - pipeline.addLast("decoder", new MqttDecoder(MAX_PAYLOAD_SIZE));
  66 + pipeline.addLast("decoder", new MqttDecoder(maxPayloadSize));
67 67 pipeline.addLast("encoder", MqttEncoder.INSTANCE);
68 68
69 69 MqttTransportHandler handler = new MqttTransportHandler(processor, deviceService, authService, relationService,
... ...
... ... @@ -82,7 +82,8 @@ public class MqttTransportService {
82 82 private Integer bossGroupThreadCount;
83 83 @Value("${mqtt.netty.worker_group_thread_count}")
84 84 private Integer workerGroupThreadCount;
85   -
  85 + @Value("${mqtt.netty.max_payload_size}")
  86 + private Integer maxPayloadSize;
86 87
87 88 private MqttTransportAdaptor adaptor;
88 89
... ... @@ -106,7 +107,7 @@ public class MqttTransportService {
106 107 b.group(bossGroup, workerGroup)
107 108 .channel(NioServerSocketChannel.class)
108 109 .childHandler(new MqttTransportServerInitializer(processor, deviceService, authService, relationService,
109   - adaptor, sslHandlerProvider, quotaService));
  110 + adaptor, sslHandlerProvider, quotaService, maxPayloadSize));
110 111
111 112 serverChannel = b.bind(host, port).sync().channel();
112 113 log.info("Mqtt transport started!");
... ...
... ... @@ -82,7 +82,7 @@ import 'font-awesome/css/font-awesome.min.css';
82 82 import 'angular-material/angular-material.min.css';
83 83 import 'angular-material-icons/angular-material-icons.css';
84 84 import 'angular-gridster/dist/angular-gridster.min.css';
85   -import 'v-accordion/dist/v-accordion.min.css'
  85 +import 'v-accordion/dist/v-accordion.min.css';
86 86 import 'md-color-picker/dist/mdColorPicker.min.css';
87 87 import 'mdPickers/dist/mdPickers.min.css';
88 88 import 'angular-hotkeys/build/hotkeys.min.css';
... ...
... ... @@ -406,7 +406,8 @@ export default angular.module('thingsboard.types', [])
406 406 extensionType: {
407 407 http: "HTTP",
408 408 mqtt: "MQTT",
409   - opc: "OPC UA"
  409 + opc: "OPC UA",
  410 + modbus: "MODBUS"
410 411 },
411 412 extensionValueType: {
412 413 string: 'value.string',
... ... @@ -450,6 +451,26 @@ export default angular.module('thingsboard.types', [])
450 451 PKCS12: "PKCS12",
451 452 JKS: "JKS"
452 453 },
  454 + extensionModbusFunctionCodes: {
  455 + 1: "Read Coils (1)",
  456 + 2: "Read Discrete Inputs (2)",
  457 + 3: "Read Multiple Holding Registers (3)",
  458 + 4: "Read Input Registers (4)"
  459 + },
  460 + extensionModbusTransports: {
  461 + tcp: "TCP",
  462 + udp: "UDP",
  463 + rtu: "RTU"
  464 + },
  465 + extensionModbusRtuParities: {
  466 + none: "none",
  467 + even: "even",
  468 + odd: "odd"
  469 + },
  470 + extensionModbusRtuEncodings: {
  471 + ascii: "ascii",
  472 + rtu: "rtu"
  473 + },
453 474 latestTelemetry: {
454 475 value: "LATEST_TELEMETRY",
455 476 name: "attribute.scope-latest-telemetry",
... ...
... ... @@ -49,7 +49,7 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
49 49 "brokers": []
50 50 };
51 51 }
52   - if (vm.extension.type === "OPC UA") {
  52 + if (vm.extension.type === "OPC UA" || vm.extension.type === "MODBUS") {
53 53 vm.extension.configuration = {
54 54 "servers": []
55 55 };
... ...
... ... @@ -62,6 +62,7 @@
62 62 <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>
63 63 <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>
64 64 <div tb-extension-form-opc configuration="vm.extension.configuration" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.opc"></div>
  65 + <div tb-extension-form-modbus configuration="vm.extension.configuration" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.modbus"></div>
65 66 </fieldset>
66 67 </md-content>
67 68 </div>
... ...
  1 +/*
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +import 'brace/ext/language_tools';
  17 +import 'brace/mode/json';
  18 +import 'brace/theme/github';
  19 +
  20 +import './extension-form.scss';
  21 +
  22 +/* eslint-disable angular/log */
  23 +
  24 +import extensionFormModbusTemplate from './extension-form-modbus.tpl.html';
  25 +
  26 +/* eslint-enable import/no-unresolved, import/default */
  27 +
  28 +/*@ngInject*/
  29 +export default function ExtensionFormModbusDirective($compile, $templateCache, $translate, types) {
  30 +
  31 +
  32 + var linker = function(scope, element) {
  33 +
  34 +
  35 + function Server() {
  36 + this.transport = {
  37 + "type": "tcp",
  38 + "host": "localhost",
  39 + "port": 502,
  40 + "timeout": 3000
  41 + };
  42 + this.devices = []
  43 + }
  44 +
  45 + function Device() {
  46 + this.unitId = 1;
  47 + this.deviceName = "";
  48 + this.attributesPollPeriod = 1000;
  49 + this.timeseriesPollPeriod = 1000;
  50 + this.attributes = [];
  51 + this.timeseries = [];
  52 + }
  53 +
  54 + function Tag(globalPollPeriod) {
  55 + this.tag = "";
  56 + this.type = "long";
  57 + this.pollPeriod = globalPollPeriod;
  58 + this.functionCode = 3;
  59 + this.address = 0;
  60 + this.registerCount = 1;
  61 + this.bit = 0;
  62 + this.byteOrder = "BIG";
  63 + }
  64 +
  65 +
  66 + var template = $templateCache.get(extensionFormModbusTemplate);
  67 + element.html(template);
  68 +
  69 + scope.types = types;
  70 + scope.theForm = scope.$parent.theForm;
  71 +
  72 +
  73 + if (!scope.configuration.servers.length) {
  74 + scope.configuration.servers.push(new Server());
  75 + }
  76 +
  77 + scope.addServer = function(serversList) {
  78 + serversList.push(new Server());
  79 + scope.theForm.$setDirty();
  80 + };
  81 +
  82 + scope.addDevice = function(deviceList) {
  83 + deviceList.push(new Device());
  84 + scope.theForm.$setDirty();
  85 + };
  86 +
  87 + scope.addNewAttribute = function(device) {
  88 + device.attributes.push(new Tag(device.attributesPollPeriod));
  89 + scope.theForm.$setDirty();
  90 + };
  91 +
  92 + scope.addNewTimeseries = function(device) {
  93 + device.timeseries.push(new Tag(device.timeseriesPollPeriod));
  94 + scope.theForm.$setDirty();
  95 + };
  96 +
  97 + scope.removeItem = (item, itemList) => {
  98 + var index = itemList.indexOf(item);
  99 + if (index > -1) {
  100 + itemList.splice(index, 1);
  101 + }
  102 + scope.theForm.$setDirty();
  103 + };
  104 +
  105 + scope.onTransportChanged = function(server) {
  106 + var type = server.transport.type;
  107 +
  108 + server.transport = {};
  109 + server.transport.type = type;
  110 + server.transport.timeout = 3000;
  111 +
  112 + scope.theForm.$setDirty();
  113 + };
  114 +
  115 + $compile(element.contents())(scope);
  116 +
  117 +
  118 + scope.collapseValidation = function(index, id) {
  119 + var invalidState = angular.element('#'+id+':has(.ng-invalid)');
  120 + if(invalidState.length) {
  121 + invalidState.addClass('inner-invalid');
  122 + }
  123 + };
  124 +
  125 + scope.expandValidation = function (index, id) {
  126 + var invalidState = angular.element('#'+id);
  127 + invalidState.removeClass('inner-invalid');
  128 + };
  129 +
  130 + };
  131 +
  132 + return {
  133 + restrict: "A",
  134 + link: linker,
  135 + scope: {
  136 + configuration: "=",
  137 + isAdd: "="
  138 + }
  139 + }
  140 +}
\ No newline at end of file
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2018 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-card class="extension-form extension-modbus">
  19 + <md-card-title>
  20 + <md-card-title-text>
  21 + <span translate class="md-headline">extension.configuration</span>
  22 + </md-card-title-text>
  23 + </md-card-title>
  24 +
  25 + <md-card-content>
  26 + <v-accordion id="modbus-server-configs-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
  27 + <v-pane id="modbus-servers-pane" expanded="true">
  28 + <v-pane-header>
  29 + {{ 'extension.modbus-server' | translate }}
  30 + </v-pane-header>
  31 +
  32 + <v-pane-content>
  33 + <div ng-if="configuration.servers.length === 0">
  34 + <span translate layout-align="center center" class="tb-prompt">extension.modbus-add-server-prompt</span>
  35 + </div>
  36 +
  37 + <div ng-if="configuration.servers.length > 0">
  38 + <ol class="list-group">
  39 + <li class="list-group-item" ng-repeat="(serverIndex, server) in configuration.servers">
  40 + <md-button aria-label="{{ 'action.remove' | translate }}"
  41 + class="md-icon-button"
  42 + ng-click="removeItem(server, configuration.servers)"
  43 + ng-hide="configuration.servers.length < 2"
  44 + >
  45 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  46 + <md-tooltip md-direction="top">
  47 + {{ 'action.remove' | translate }}
  48 + </md-tooltip>
  49 + </md-button>
  50 +
  51 + <md-card>
  52 + <md-card-content>
  53 +
  54 + <div layout="row">
  55 +
  56 + <md-input-container flex="50" class="md-block tb-container-for-select">
  57 + <label translate>extension.modbus-transport</label>
  58 + <md-select required
  59 + name="transportType_{{serverIndex}}"
  60 + ng-model="server.transport.type"
  61 + ng-change="onTransportChanged(server)"
  62 + >
  63 + <md-option ng-value="transportType"
  64 + ng-repeat="(transportType, transportValue) in types.extensionModbusTransports"
  65 + ><span ng-bind="transportValue"></span></md-option>
  66 + </md-select>
  67 + <div ng-messages="theForm['transportType_' + serverIndex].$error">
  68 + <div translate
  69 + ng-message="required"
  70 + >extension.field-required</div>
  71 + </div>
  72 + </md-input-container>
  73 +
  74 + </div>
  75 +
  76 + <div layout="row" ng-if="server.transport.type == 'tcp' || server.transport.type == 'udp'">
  77 + <md-input-container flex="33" class="md-block">
  78 + <label translate>extension.host</label>
  79 + <input required name="transportHost_{{serverIndex}}" ng-model="server.transport.host">
  80 + <div ng-messages="theForm['transportHost_' + serverIndex].$error">
  81 + <div translate ng-message="required">extension.field-required</div>
  82 + </div>
  83 + </md-input-container>
  84 +
  85 + <md-input-container flex="33" class="md-block">
  86 + <label translate>extension.port</label>
  87 + <input type="number"
  88 + required
  89 + name="transportPort_{{serverIndex}}"
  90 + ng-model="server.transport.port"
  91 + min="1"
  92 + max="65535"
  93 + >
  94 + <div ng-messages="theForm['transportPort_' + serverIndex].$error">
  95 + <div translate
  96 + ng-message="required"
  97 + >extension.field-required</div>
  98 + <div translate
  99 + ng-message="min"
  100 + >extension.port-range</div>
  101 + <div translate
  102 + ng-message="max"
  103 + >extension.port-range</div>
  104 + </div>
  105 + </md-input-container>
  106 +
  107 + <md-input-container flex="33" class="md-block">
  108 + <label translate>extension.timeout</label>
  109 + <input type="number"
  110 + required name="transportTimeout_{{serverIndex}}"
  111 + ng-model="server.transport.timeout"
  112 + >
  113 + <div ng-messages="theForm['transportTimeout_' + serverIndex].$error">
  114 + <div translate
  115 + ng-message="required"
  116 + >extension.field-required</div>
  117 + </div>
  118 + </md-input-container>
  119 + </div>
  120 +
  121 + <div ng-if="server.transport.type == 'rtu'">
  122 +
  123 + <div layout="row">
  124 + <md-input-container flex="70" class="md-block">
  125 + <label translate>extension.modbus-port-name</label>
  126 + <input required name="transportPortName_{{serverIndex}}" ng-model="server.transport.portName">
  127 + <div ng-messages="theForm['transportPortName_' + serverIndex].$error">
  128 + <div translate ng-message="required">extension.field-required</div>
  129 + </div>
  130 + </md-input-container>
  131 +
  132 + <md-input-container flex="30" class="md-block">
  133 + <label translate>extension.timeout</label>
  134 + <input type="number"
  135 + required name="transportTimeout_{{serverIndex}}"
  136 + ng-model="server.transport.timeout"
  137 + >
  138 + <div ng-messages="theForm['transportTimeout_' + serverIndex].$error">
  139 + <div translate
  140 + ng-message="required"
  141 + >extension.field-required</div>
  142 + </div>
  143 + </md-input-container>
  144 + </div>
  145 +
  146 + <div layout="row">
  147 + <md-input-container flex="50" class="md-block tb-container-for-select">
  148 + <label translate>extension.modbus-encoding</label>
  149 + <md-select required
  150 + name="transportEncoding_{{serverIndex}}"
  151 + ng-model="server.transport.encoding"
  152 + >
  153 + <md-option ng-value="encodingType"
  154 + ng-repeat="(encodingType, encodingValue) in types.extensionModbusRtuEncodings"
  155 + ><span ng-bind="encodingValue"></span></md-option>
  156 + </md-select>
  157 + <div ng-messages="theForm['transportEncoding_' + serverIndex].$error">
  158 + <div translate
  159 + ng-message="required"
  160 + >extension.field-required</div>
  161 + </div>
  162 + </md-input-container>
  163 +
  164 + <md-input-container flex="50" class="md-block tb-container-for-select">
  165 + <label translate>extension.modbus-parity</label>
  166 + <md-select name="transportParity_{{serverIndex}}" ng-model="server.transport.parity">
  167 + <md-option ng-repeat="(parityKey, parityValue) in types.extensionModbusRtuParities"
  168 + ng-value="parityValue"
  169 + >
  170 + {{parityValue}}
  171 + </md-option>
  172 + </md-select>
  173 + <div ng-messages="theForm['transportParity_' + serverIndex].$error">
  174 + <div translate
  175 + ng-message="required"
  176 + >extension.field-required</div>
  177 + </div>
  178 + </md-input-container>
  179 +
  180 + </div>
  181 +
  182 + <div layout="row">
  183 + <md-input-container flex="33" class="md-block">
  184 + <label translate>extension.modbus-baudrate</label>
  185 + <input type="number"
  186 + required name="transportBaudRate_{{serverIndex}}"
  187 + ng-model="server.transport.baudRate"
  188 + >
  189 + <div ng-messages="theForm['transportBaudRate_' + serverIndex].$error">
  190 + <div translate
  191 + ng-message="required"
  192 + >extension.field-required</div>
  193 + </div>
  194 + </md-input-container>
  195 +
  196 + <md-input-container flex="33" class="md-block">
  197 + <label translate>extension.modbus-databits</label>
  198 + <input type="number"
  199 + required
  200 + name="transportDataBits_{{serverIndex}}"
  201 + ng-model="server.transport.dataBits"
  202 + min="7"
  203 + max="8"
  204 + >
  205 + <div ng-messages="theForm['transportDataBits_' + serverIndex].$error">
  206 + <div translate
  207 + ng-message="required"
  208 + >extension.field-required</div>
  209 + <div translate
  210 + ng-message="min"
  211 + >extension.modbus-databits-range</div>
  212 + <div translate
  213 + ng-message="max"
  214 + >extension.modbus-databits-range</div>
  215 + </div>
  216 + </md-input-container>
  217 +
  218 + <md-input-container flex="33" class="md-block">
  219 + <label translate>extension.modbus-stopbits</label>
  220 + <input type="number"
  221 + required
  222 + name="transportStopBits_{{serverIndex}}"
  223 + ng-model="server.transport.stopBits"
  224 + min="1"
  225 + max="2"
  226 + >
  227 + <div ng-messages="theForm['transportStopBits_' + serverIndex].$error">
  228 + <div translate
  229 + ng-message="required"
  230 + >extension.field-required</div>
  231 + <div translate
  232 + ng-message="min"
  233 + >extension.modbus-stopbits-range</div>
  234 + <div translate
  235 + ng-message="max"
  236 + >extension.modbus-stopbits-range</div>
  237 + </div>
  238 + </md-input-container>
  239 + </div>
  240 + </div>
  241 +
  242 + <v-accordion id="modbus-mapping-accordion"
  243 + class="vAccordion--default"
  244 + onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
  245 + <v-pane id="modbus-mapping-pane_{{serverIndex}}">
  246 + <v-pane-header>
  247 + {{ 'extension.mapping' | translate }}
  248 + </v-pane-header>
  249 + <v-pane-content>
  250 + <div ng-if="server.devices.length > 0">
  251 + <ol class="list-group">
  252 + <li class="list-group-item"
  253 + ng-repeat="(deviceIndex, device) in server.devices"
  254 + >
  255 + <md-button aria-label="{{ 'action.remove' | translate }}"
  256 + class="md-icon-button"
  257 + ng-click="removeItem(device, server.devices)"
  258 + >
  259 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  260 + <md-tooltip md-direction="top">
  261 + {{ 'action.remove' | translate }}
  262 + </md-tooltip>
  263 + </md-button>
  264 +
  265 + <md-card>
  266 + <md-card-content>
  267 + <div flex layout="row">
  268 + <md-input-container flex="80" class="md-block">
  269 + <label translate>extension.modbus-device-name</label>
  270 + <input required
  271 + name="deviceName_{{serverIndex}}{{deviceIndex}}"
  272 + ng-model="device.deviceName"
  273 + >
  274 + <div ng-messages="theForm['deviceName_' + serverIndex + deviceIndex].$error">
  275 + <div translate
  276 + ng-message="required"
  277 + >extension.field-required</div>
  278 + </div>
  279 + </md-input-container>
  280 +
  281 + <md-input-container flex="20" class="md-block">
  282 + <label translate>extension.modbus-unit-id</label>
  283 + <input type="number"
  284 + required
  285 + name="unitId_{{serverIndex}}{{deviceIndex}}"
  286 + ng-model="device.unitId"
  287 + min="1"
  288 + max="247"
  289 + >
  290 +
  291 + <div ng-messages="theForm['unitId_' + serverIndex + deviceIndex].$error">
  292 + <div translate
  293 + ng-message="required"
  294 + >extension.field-required</div>
  295 + <div translate
  296 + ng-message="min"
  297 + >extension.modbus-unit-id-range</div>
  298 + <div translate
  299 + ng-message="max"
  300 + >extension.modbus-unit-id-range</div>
  301 + </div>
  302 + </md-input-container>
  303 + </div>
  304 +
  305 + <div flex layout="row">
  306 + <md-input-container flex="50" class="md-block">
  307 + <label translate>extension.modbus-attributes-poll-period</label>
  308 + <input type="number"
  309 + required
  310 + name="attributesPollPeriod_{{serverIndex}}{{deviceIndex}}"
  311 + ng-model="device.attributesPollPeriod"
  312 + min="1"
  313 + >
  314 +
  315 + <div ng-messages="theForm['attributesPollPeriod_' + serverIndex + deviceIndex].$error">
  316 + <div translate
  317 + ng-message="required"
  318 + >extension.field-required</div>
  319 + <div translate
  320 + ng-message="min"
  321 + >extension.modbus-poll-period-range</div>
  322 + </div>
  323 + </md-input-container>
  324 +
  325 + <md-input-container flex="50" class="md-block">
  326 + <label translate>extension.modbus-timeseries-poll-period</label>
  327 + <input type="number"
  328 + required
  329 + name="timeseriesPollPeriod_{{serverIndex}}{{deviceIndex}}"
  330 + ng-model="device.timeseriesPollPeriod"
  331 + min="1"
  332 + >
  333 +
  334 + <div ng-messages="theForm['timeseriesPollPeriod_' + serverIndex + deviceIndex].$error">
  335 + <div translate
  336 + ng-message="required"
  337 + >extension.field-required</div>
  338 + <div translate
  339 + ng-message="min"
  340 + >extension.modbus-poll-period-range</div>
  341 + </div>
  342 + </md-input-container>
  343 +
  344 + </div>
  345 +
  346 +
  347 + <v-accordion id="modbus-attributes-accordion"
  348 + class="vAccordion--default"
  349 + onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
  350 + <v-pane id="modbus-attributes-pane_{{serverIndex}}{{deviceIndex}}">
  351 + <v-pane-header>
  352 + {{ 'extension.attributes' | translate }}
  353 + </v-pane-header>
  354 + <v-pane-content>
  355 + <div ng-show="device.attributes.length > 0">
  356 + <ol class="list-group">
  357 + <li class="list-group-item"
  358 + ng-repeat="(attributeIndex, attribute) in device.attributes"
  359 + >
  360 + <md-button aria-label="{{ 'action.remove' | translate }}"
  361 + class="md-icon-button"
  362 + ng-click="removeItem(attribute, device.attributes)">
  363 + <ng-md-icon icon="close"
  364 + aria-label="{{ 'action.remove' | translate }}"
  365 + ></ng-md-icon>
  366 + <md-tooltip md-direction="top">
  367 + {{ 'action.remove' | translate }}
  368 + </md-tooltip>
  369 + </md-button>
  370 + <md-card>
  371 + <md-card-content>
  372 +
  373 + <section flex layout="row">
  374 + <md-input-container flex="50" class="md-block">
  375 + <label translate>extension.modbus-tag</label>
  376 + <input required
  377 + name="modbusAttributeTag_{{serverIndex}}{{deviceIndex}}{{attributeIndex}}"
  378 + ng-model="attribute.tag"
  379 + >
  380 + <div ng-messages="theForm['modbusAttributeTag_' + serverIndex + deviceIndex + attributeIndex].$error">
  381 + <div translate
  382 + ng-message="required"
  383 + >extension.field-required</div>
  384 + </div>
  385 + </md-input-container>
  386 +
  387 + <md-input-container flex="30" class="md-block tb-container-for-select">
  388 + <label translate>extension.type</label>
  389 + <md-select required name="modbusAttributeType_{{serverIndex}}{{deviceIndex}}{{attributeIndex}}"
  390 + ng-model="attribute.type"
  391 + >
  392 + <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType"
  393 + ng-value="attrType"
  394 + >
  395 + {{attrTypeValue | translate}}
  396 + </md-option>
  397 + </md-select>
  398 + <div ng-messages="theForm['modbusAttributeType_' + serverIndex + deviceIndex + attributeIndex].$error">
  399 + <div translate
  400 + ng-message="required"
  401 + >extension.field-required</div>
  402 + </div>
  403 + </md-input-container>
  404 +
  405 + <md-input-container flex="20" class="md-block">
  406 + <label translate>extension.modbus-poll-period</label>
  407 + <input type="number"
  408 + name="pollPeriod_{{serverIndex}}{{deviceIndex}}"
  409 + ng-model="attribute.pollPeriod"
  410 + min="1"
  411 + >
  412 +
  413 + <div ng-messages="theForm['pollPeriod_' + serverIndex + deviceIndex].$error">
  414 + <div translate
  415 + ng-message="min"
  416 + >extension.modbus-poll-period-range</div>
  417 + </div>
  418 + </md-input-container>
  419 + </section>
  420 +
  421 + <section flex layout="row">
  422 + <md-input-container flex="50" class="md-block tb-container-for-select">
  423 + <label translate>extension.type</label>
  424 + <md-select required name="modbusAttributeFunctionCode_{{serverIndex}}{{deviceIndex}}{{attributeIndex}}"
  425 + ng-model="attribute.functionCode"
  426 + >
  427 + <md-option ng-repeat="(functionCode, functionName) in types.extensionModbusFunctionCodes"
  428 + ng-value="functionCode"
  429 + >
  430 + {{functionName}}
  431 + </md-option>
  432 + </md-select>
  433 + <div ng-messages="theForm['modbusAttributeFunctionCode_' + serverIndex + deviceIndex + attributeIndex].$error">
  434 + <div translate
  435 + ng-message="required"
  436 + >extension.field-required</div>
  437 + </div>
  438 + </md-input-container>
  439 +
  440 + <md-input-container flex="50" class="md-block">
  441 + <label translate>extension.modbus-register-address</label>
  442 + <input type="number"
  443 + required
  444 + name="address_{{serverIndex}}{{deviceIndex}}"
  445 + ng-model="attribute.address"
  446 + min="0"
  447 + max="65535"
  448 + >
  449 +
  450 + <div ng-messages="theForm['address_' + serverIndex + deviceIndex].$error">
  451 + <div translate
  452 + ng-message="required"
  453 + >extension.field-required</div>
  454 + <div translate
  455 + ng-message="min"
  456 + >extension.modbus-register-address-range</div>
  457 + <div translate
  458 + ng-message="max"
  459 + >extension.modbus-register-address-range</div>
  460 + </div>
  461 + </md-input-container>
  462 +
  463 + </section>
  464 +
  465 + <section flex layout="row">
  466 + <md-input-container flex="50" class="md-block" ng-if="attribute.type != 'boolean'">
  467 + <label translate>extension.modbus-register-count</label>
  468 + <input type="number"
  469 + name="registerCount_{{serverIndex}}{{deviceIndex}}"
  470 + ng-model="attribute.registerCount"
  471 + min="1"
  472 + >
  473 +
  474 + <div ng-messages="theForm['registerCount_' + serverIndex + deviceIndex].$error">
  475 + <div translate
  476 + ng-message="min"
  477 + >extension.modbus-register-count-range</div>
  478 + </div>
  479 + </md-input-container>
  480 +
  481 + <md-input-container flex="50" class="md-block" ng-if="attribute.type == 'boolean' && attribute.functionCode >= 3">
  482 + <label translate>extension.modbus-register-bit-index</label>
  483 + <input type="number"
  484 + required
  485 + name="bit_{{serverIndex}}{{deviceIndex}}"
  486 + ng-model="attribute.bit"
  487 + min="0"
  488 + max="15"
  489 + >
  490 +
  491 + <div ng-messages="theForm['bit_' + serverIndex + deviceIndex].$error">
  492 + <div translate
  493 + ng-message="required"
  494 + >extension.field-required</div>
  495 + <div translate
  496 + ng-message="min"
  497 + >extension.modbus-register-bit-index-range</div>
  498 + <div translate
  499 + ng-message="max"
  500 + >extension.modbus-register-bit-index-range</div>
  501 + </div>
  502 + </md-input-container>
  503 + </section>
  504 +
  505 + <section flex layout="row">
  506 + <md-input-container flex="100" class="md-block" ng-if="attribute.functionCode >= 3">
  507 + <label translate>extension.modbus-byte-order</label>
  508 + <input required
  509 + name="modbusByteOrder_{{serverIndex}}{{deviceIndex}}{{attributeIndex}}"
  510 + ng-model="attribute.byteOrder"
  511 + >
  512 + <div ng-messages="theForm['modbusByteOrder_' + serverIndex + deviceIndex + attributeIndex].$error">
  513 + <div translate
  514 + ng-message="required"
  515 + >extension.field-required</div>
  516 + </div>
  517 + </md-input-container>
  518 +
  519 + </section>
  520 +
  521 +
  522 + </md-card-content>
  523 + </md-card>
  524 + </li>
  525 + </ol>
  526 + </div>
  527 + <div flex layout="row" layout-align="start center">
  528 + <md-button class="md-primary md-raised"
  529 + ng-click="addNewAttribute(device)"
  530 + aria-label="{{ 'action.add' | translate }}"
  531 + >
  532 + <md-icon class="material-icons">add</md-icon>
  533 + <span translate>extension.add-attribute</span>
  534 + </md-button>
  535 + </div>
  536 + </v-pane-content>
  537 + </v-pane>
  538 + </v-accordion>
  539 +
  540 + <v-accordion id="modbus-timeseries-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
  541 + <v-pane id="modbus-timeseries-pane_{{serverIndex}}{{deviceIndex}}">
  542 + <v-pane-header>
  543 + {{ 'extension.timeseries' | translate }}
  544 + </v-pane-header>
  545 + <v-pane-content>
  546 + <div ng-show="device.timeseries.length > 0">
  547 + <ol class="list-group">
  548 + <li class="list-group-item"
  549 + ng-repeat="(timeseriesIndex, timeserie) in device.timeseries"
  550 + >
  551 + <md-button aria-label="{{ 'action.remove' | translate }}"
  552 + class="md-icon-button"
  553 + ng-click="removeItem(timeserie, device.timeseries)">
  554 + <ng-md-icon icon="close"
  555 + aria-label="{{ 'action.remove' | translate }}"
  556 + ></ng-md-icon>
  557 + <md-tooltip md-direction="top">
  558 + {{ 'action.remove' | translate }}
  559 + </md-tooltip>
  560 + </md-button>
  561 + <md-card>
  562 + <md-card-content>
  563 +
  564 + <section flex layout="row">
  565 + <md-input-container flex="50" class="md-block">
  566 + <label translate>extension.modbus-tag</label>
  567 + <input required
  568 + name="modbusTimeserieTag_{{serverIndex}}{{deviceIndex}}{{timeseriesIndex}}"
  569 + ng-model="timeserie.tag"
  570 + >
  571 + <div ng-messages="theForm['modbusTimeserieTag_' + serverIndex + deviceIndex + timeseriesIndex].$error">
  572 + <div translate
  573 + ng-message="required"
  574 + >extension.field-required</div>
  575 + </div>
  576 + </md-input-container>
  577 +
  578 + <md-input-container flex="30" class="md-block tb-container-for-select">
  579 + <label translate>extension.type</label>
  580 + <md-select required name="modbusTimeserieType_{{serverIndex}}{{deviceIndex}}{{timeseriesIndex}}"
  581 + ng-model="timeserie.type"
  582 + >
  583 + <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType"
  584 + ng-value="attrType"
  585 + >
  586 + {{attrTypeValue | translate}}
  587 + </md-option>
  588 + </md-select>
  589 + <div ng-messages="theForm['modbusTimeserieType_' + serverIndex + deviceIndex + timeseriesIndex].$error">
  590 + <div translate
  591 + ng-message="required"
  592 + >extension.field-required</div>
  593 + </div>
  594 + </md-input-container>
  595 +
  596 + <md-input-container flex="20" class="md-block">
  597 + <label translate>extension.modbus-poll-period</label>
  598 + <input type="number"
  599 + name="pollPeriod_{{serverIndex}}{{deviceIndex}}"
  600 + ng-model="timeserie.pollPeriod"
  601 + min="1"
  602 + >
  603 +
  604 + <div ng-messages="theForm['pollPeriod_' + serverIndex + deviceIndex].$error">
  605 + <div translate
  606 + ng-message="min"
  607 + >extension.modbus-poll-period-range</div>
  608 + </div>
  609 + </md-input-container>
  610 + </section>
  611 +
  612 + <section flex layout="row">
  613 + <md-input-container flex="50" class="md-block tb-container-for-select">
  614 + <label translate>extension.type</label>
  615 + <md-select required name="modbusTimeserieFunctionCode_{{serverIndex}}{{deviceIndex}}{{timeseriesIndex}}"
  616 + ng-model="timeserie.functionCode"
  617 + >
  618 + <md-option ng-repeat="(functionCode, functionName) in types.extensionModbusFunctionCodes"
  619 + ng-value="functionCode"
  620 + >
  621 + {{functionName}}
  622 + </md-option>
  623 + </md-select>
  624 + <div ng-messages="theForm['modbusTimeserieFunctionCode_' + serverIndex + deviceIndex + timeseriesIndex].$error">
  625 + <div translate
  626 + ng-message="required"
  627 + >extension.field-required</div>
  628 + </div>
  629 + </md-input-container>
  630 +
  631 + <md-input-container flex="50" class="md-block">
  632 + <label translate>extension.modbus-register-address</label>
  633 + <input type="number"
  634 + required
  635 + name="address_{{serverIndex}}{{deviceIndex}}"
  636 + ng-model="timeserie.address"
  637 + min="0"
  638 + max="65535"
  639 + >
  640 +
  641 + <div ng-messages="theForm['address_' + serverIndex + deviceIndex].$error">
  642 + <div translate
  643 + ng-message="required"
  644 + >extension.field-required</div>
  645 + <div translate
  646 + ng-message="min"
  647 + >extension.modbus-register-address-range</div>
  648 + <div translate
  649 + ng-message="max"
  650 + >extension.modbus-register-address-range</div>
  651 + </div>
  652 + </md-input-container>
  653 + </section>
  654 +
  655 + <section flex layout="row">
  656 + <md-input-container flex="50" class="md-block" ng-if="timeserie.type != 'boolean'">
  657 + <label translate>extension.modbus-register-count</label>
  658 + <input type="number"
  659 + name="registerCount_{{serverIndex}}{{deviceIndex}}"
  660 + ng-model="timeserie.registerCount"
  661 + min="1"
  662 + >
  663 +
  664 + <div ng-messages="theForm['registerCount_' + serverIndex + deviceIndex].$error">
  665 + <div translate
  666 + ng-message="min"
  667 + >extension.modbus-register-count-range</div>
  668 + </div>
  669 + </md-input-container>
  670 +
  671 + <md-input-container flex="50" class="md-block" ng-if="timeserie.type == 'boolean' && timeserie.functionCode >= 3">
  672 + <label translate>extension.modbus-register-bit-index</label>
  673 + <input type="number"
  674 + required
  675 + name="bit_{{serverIndex}}{{deviceIndex}}"
  676 + ng-model="timeserie.bit"
  677 + min="0"
  678 + max="15"
  679 + >
  680 +
  681 + <div ng-messages="theForm['bit_' + serverIndex + deviceIndex].$error">
  682 + <div translate
  683 + ng-message="required"
  684 + >extension.field-required</div>
  685 + <div translate
  686 + ng-message="min"
  687 + >extension.modbus-register-bit-index-range</div>
  688 + <div translate
  689 + ng-message="max"
  690 + >extension.modbus-register-bit-index-range</div>
  691 + </div>
  692 + </md-input-container>
  693 + </section>
  694 +
  695 + <section flex layout="row">
  696 + <md-input-container flex="100" class="md-block" ng-if="timeserie.functionCode >= 3">
  697 + <label translate>extension.modbus-byte-order</label>
  698 + <input required
  699 + name="modbusByteOrder_{{serverIndex}}{{deviceIndex}}{{timeseriesIndex}}"
  700 + ng-model="timeserie.byteOrder"
  701 + >
  702 + <div ng-messages="theForm['modbusByteOrder_' + serverIndex + deviceIndex + timeseriesIndex].$error">
  703 + <div translate
  704 + ng-message="required"
  705 + >extension.field-required</div>
  706 + </div>
  707 + </md-input-container>
  708 +
  709 + </section>
  710 + </md-card-content>
  711 + </md-card>
  712 + </li>
  713 + </ol>
  714 + </div>
  715 + <div flex layout="row" layout-align="start center">
  716 + <md-button class="md-primary md-raised"
  717 + ng-click="addNewTimeseries(device)"
  718 + aria-label="{{ 'action.add' | translate }}"
  719 + >
  720 + <md-icon class="material-icons">add</md-icon>
  721 + <span translate>extension.add-timeseries</span>
  722 + </md-button>
  723 + </div>
  724 + </v-pane-content>
  725 + </v-pane>
  726 + </v-accordion>
  727 +
  728 +
  729 + </md-card-content>
  730 + </md-card>
  731 + </li>
  732 + </ol>
  733 + </div>
  734 + <div flex
  735 + layout="row"
  736 + layout-align="start center"
  737 + >
  738 + <md-button class="md-primary md-raised"
  739 + ng-click="addDevice(server.devices)"
  740 + aria-label="{{ 'action.add' | translate }}"
  741 + >
  742 + <md-icon class="material-icons">add</md-icon>
  743 + <span translate>extension.add-device</span>
  744 + </md-button>
  745 + </div>
  746 + </v-pane-content>
  747 + </v-pane>
  748 + </v-accordion>
  749 +
  750 + </md-card-content>
  751 + </md-card>
  752 + </li>
  753 + </ol>
  754 +
  755 + <div flex
  756 + layout="row"
  757 + layout-align="start center"
  758 + >
  759 + <md-button class="md-primary md-raised"
  760 + ng-click="addServer(configuration.servers)"
  761 + aria-label="{{ 'action.add' | translate }}"
  762 + >
  763 + <md-icon class="material-icons">add</md-icon>
  764 + <span translate>extension.modbus-add-server</span>
  765 + </md-button>
  766 + </div>
  767 +
  768 + </div>
  769 + </v-pane-content>
  770 + </v-pane>
  771 + </v-accordion>
  772 + <!--{{config}}-->
  773 + </md-card-content>
  774 +</md-card>
\ No newline at end of file
... ...
... ... @@ -194,7 +194,7 @@
194 194 </md-input-container>
195 195 </div>
196 196
197   - <v-accordion id="opc-keystore-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
  197 + <v-accordion id="opc-keystore-accordion" class="vAccordion--default" ng-if="server.security != 'None'" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)">
198 198 <v-pane id="opc-keystore-pane__{{serverIndex}}" expanded="true">
199 199 <v-pane-header>
200 200 {{ 'extension.opc-keystore' | translate }}
... ...
... ... @@ -17,6 +17,8 @@ import ExtensionTableDirective from './extension-table.directive';
17 17 import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive';
18 18 import ExtensionFormMqttDirective from './extensions-forms/extension-form-mqtt.directive'
19 19 import ExtensionFormOpcDirective from './extensions-forms/extension-form-opc.directive';
  20 +import ExtensionFormModbusDirective from './extensions-forms/extension-form-modbus.directive';
  21 +
20 22 import {ParseToNull} from './extension-dialog.controller';
21 23
22 24 export default angular.module('thingsboard.extension', [])
... ... @@ -24,5 +26,6 @@ export default angular.module('thingsboard.extension', [])
24 26 .directive('tbExtensionFormHttp', ExtensionFormHttpDirective)
25 27 .directive('tbExtensionFormMqtt', ExtensionFormMqttDirective)
26 28 .directive('tbExtensionFormOpc', ExtensionFormOpcDirective)
  29 + .directive('tbExtensionFormModbus', ExtensionFormModbusDirective)
27 30 .directive('parseToNull', ParseToNull)
28 31 .name;
\ No newline at end of file
... ...
... ... @@ -886,8 +886,10 @@ export default angular.module('thingsboard.locale', [])
886 886 "response-timeout": "Response timeout in milliseconds",
887 887 "topic-expression": "Topic expression",
888 888 "client-scope": "Client scope",
  889 + "add-device": "Add device",
889 890 "opc-server": "Servers",
890 891 "opc-add-server": "Add server",
  892 + "opc-add-server-prompt": "Please add server",
891 893 "opc-application-name": "Application name",
892 894 "opc-application-uri": "Application uri",
893 895 "opc-scan-period-in-seconds": "Scan period in seconds",
... ... @@ -902,6 +904,34 @@ export default angular.module('thingsboard.locale', [])
902 904 "opc-keystore-key-password":"Key password",
903 905 "opc-device-node-pattern":"Device node pattern",
904 906 "opc-device-name-pattern":"Device name pattern",
  907 + "modbus-server": "Servers/slaves",
  908 + "modbus-add-server": "Add server/slave",
  909 + "modbus-add-server-prompt": "Please add server/slave",
  910 + "modbus-transport": "Transport",
  911 + "modbus-port-name": "Serial port name",
  912 + "modbus-encoding": "Encoding",
  913 + "modbus-parity": "Parity",
  914 + "modbus-baudrate": "Baud rate",
  915 + "modbus-databits": "Data bits",
  916 + "modbus-stopbits": "Stop bits",
  917 + "modbus-databits-range": "Data bits should be in a range from 7 to 8.",
  918 + "modbus-stopbits-range": "Stop bits should be in a range from 1 to 2.",
  919 + "modbus-unit-id": "Unit ID",
  920 + "modbus-unit-id-range": "Unit ID should be in a range from 1 to 247.",
  921 + "modbus-device-name":"Device name",
  922 + "modbus-poll-period": "Poll period (ms)",
  923 + "modbus-attributes-poll-period": "Attributes poll period (ms)",
  924 + "modbus-timeseries-poll-period": "Timeseries poll period (ms)",
  925 + "modbus-poll-period-range": "Poll period should be positive value.",
  926 + "modbus-tag": "Tag",
  927 + "modbus-function": "Function",
  928 + "modbus-register-address": "Register address",
  929 + "modbus-register-address-range": "Register address should be in a range from 0 to 65535.",
  930 + "modbus-register-bit-index": "Bit index",
  931 + "modbus-register-bit-index-range": "Bit index should be in a range from 0 to 15.",
  932 + "modbus-register-count": "Register count",
  933 + "modbus-register-count-range": "Register count should be a positive value.",
  934 + "modbus-byte-order": "Byte order",
905 935
906 936 "sync": {
907 937 "status": "Status",
... ...
... ... @@ -251,7 +251,7 @@ export default class TbFlot {
251 251 if (this.tickUnits) {
252 252 formatted += ' ' + this.tickUnits;
253 253 }
254   -
  254 +
255 255 return formatted;
256 256 };
257 257
... ... @@ -1097,7 +1097,7 @@ export default class TbFlot {
1097 1097 "axisTickDecimals": {
1098 1098 "title": "Axis tick number of digits after floating point",
1099 1099 "type": "number",
1100   - "default": 0
  1100 + "default": null
1101 1101 },
1102 1102 "axisTickSize": {
1103 1103 "title": "Axis step size between ticks",
... ...