Showing
17 changed files
with
1125 additions
and
55 deletions
... | ... | @@ -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}") | ... | ... |
docker/k8s/redis.yaml
0 → 100644
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", | ... | ... |