Showing
76 changed files
with
3453 additions
and
418 deletions
Too many changes to show.
To preserve performance only 76 of 94 files are displayed.
... | ... | @@ -321,23 +321,23 @@ public class ActorSystemContext { |
321 | 321 | return discoveryService.getCurrentServer().getServerAddress().toString(); |
322 | 322 | } |
323 | 323 | |
324 | - public void persistDebugInput(TenantId tenantId, EntityId entityId, TbMsg tbMsg) { | |
325 | - persistDebug(tenantId, entityId, "IN", tbMsg, null); | |
324 | + public void persistDebugInput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, String relationType) { | |
325 | + persistDebug(tenantId, entityId, "IN", tbMsg, relationType, null); | |
326 | 326 | } |
327 | 327 | |
328 | - public void persistDebugInput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, Throwable error) { | |
329 | - persistDebug(tenantId, entityId, "IN", tbMsg, error); | |
328 | + public void persistDebugInput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, String relationType, Throwable error) { | |
329 | + persistDebug(tenantId, entityId, "IN", tbMsg, relationType, error); | |
330 | 330 | } |
331 | 331 | |
332 | - public void persistDebugOutput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, Throwable error) { | |
333 | - persistDebug(tenantId, entityId, "OUT", tbMsg, error); | |
332 | + public void persistDebugOutput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, String relationType, Throwable error) { | |
333 | + persistDebug(tenantId, entityId, "OUT", tbMsg, relationType, error); | |
334 | 334 | } |
335 | 335 | |
336 | - public void persistDebugOutput(TenantId tenantId, EntityId entityId, TbMsg tbMsg) { | |
337 | - persistDebug(tenantId, entityId, "OUT", tbMsg, null); | |
336 | + public void persistDebugOutput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, String relationType) { | |
337 | + persistDebug(tenantId, entityId, "OUT", tbMsg, relationType, null); | |
338 | 338 | } |
339 | 339 | |
340 | - private void persistDebug(TenantId tenantId, EntityId entityId, String type, TbMsg tbMsg, Throwable error) { | |
340 | + private void persistDebug(TenantId tenantId, EntityId entityId, String type, TbMsg tbMsg, String relationType, Throwable error) { | |
341 | 341 | try { |
342 | 342 | Event event = new Event(); |
343 | 343 | event.setTenantId(tenantId); |
... | ... | @@ -354,6 +354,7 @@ public class ActorSystemContext { |
354 | 354 | .put("msgId", tbMsg.getId().toString()) |
355 | 355 | .put("msgType", tbMsg.getType()) |
356 | 356 | .put("dataType", tbMsg.getDataType().name()) |
357 | + .put("relationType", relationType) | |
357 | 358 | .put("data", tbMsg.getData()) |
358 | 359 | .put("metadata", metadata); |
359 | 360 | ... | ... |
... | ... | @@ -356,16 +356,15 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso |
356 | 356 | |
357 | 357 | for (Map.Entry<Long, List<KvEntry>> entry : tsData.entrySet()) { |
358 | 358 | JsonObject json = new JsonObject(); |
359 | - json.addProperty("ts", entry.getKey()); | |
360 | - JsonObject values = new JsonObject(); | |
361 | 359 | for (KvEntry kv : entry.getValue()) { |
362 | - kv.getBooleanValue().ifPresent(v -> values.addProperty(kv.getKey(), v)); | |
363 | - kv.getLongValue().ifPresent(v -> values.addProperty(kv.getKey(), v)); | |
364 | - kv.getDoubleValue().ifPresent(v -> values.addProperty(kv.getKey(), v)); | |
365 | - kv.getStrValue().ifPresent(v -> values.addProperty(kv.getKey(), v)); | |
360 | + kv.getBooleanValue().ifPresent(v -> json.addProperty(kv.getKey(), v)); | |
361 | + kv.getLongValue().ifPresent(v -> json.addProperty(kv.getKey(), v)); | |
362 | + kv.getDoubleValue().ifPresent(v -> json.addProperty(kv.getKey(), v)); | |
363 | + kv.getStrValue().ifPresent(v -> json.addProperty(kv.getKey(), v)); | |
366 | 364 | } |
367 | - json.add("values", values); | |
368 | - TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, defaultMetaData.copy(), TbMsgDataType.JSON, gson.toJson(json), null, null, 0L); | |
365 | + TbMsgMetaData metaData = defaultMetaData.copy(); | |
366 | + metaData.putValue("ts", entry.getKey()+""); | |
367 | + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, metaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L); | |
369 | 368 | pushToRuleEngineWithTimeout(context, tbMsg, msgData); |
370 | 369 | } |
371 | 370 | } | ... | ... |
... | ... | @@ -29,15 +29,12 @@ import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener; |
29 | 29 | @Slf4j |
30 | 30 | public class BasicRpcSessionListener implements GrpcSessionListener { |
31 | 31 | |
32 | - public static final String SESSION_RECEIVED_SESSION_ACTOR_MSG = "{} session [{}] received session actor msg {}"; | |
33 | - private final ActorSystemContext context; | |
34 | 32 | private final ActorService service; |
35 | 33 | private final ActorRef manager; |
36 | 34 | private final ActorRef self; |
37 | 35 | |
38 | - public BasicRpcSessionListener(ActorSystemContext context, ActorRef manager, ActorRef self) { | |
39 | - this.context = context; | |
40 | - this.service = context.getActorService(); | |
36 | + public BasicRpcSessionListener(ActorService service, ActorRef manager, ActorRef self) { | |
37 | + this.service = service; | |
41 | 38 | this.manager = manager; |
42 | 39 | this.self = self; |
43 | 40 | } |
... | ... | @@ -64,7 +61,6 @@ public class BasicRpcSessionListener implements GrpcSessionListener { |
64 | 61 | service.onRecievedMsg(clusterMessage); |
65 | 62 | } |
66 | 63 | |
67 | - | |
68 | 64 | @Override |
69 | 65 | public void onError(GrpcSession session, Throwable t) { |
70 | 66 | log.warn("{} session got error -> {}", getType(session), session.getRemoteServer(), t); | ... | ... |
... | ... | @@ -69,7 +69,7 @@ class DefaultTbContext implements TbContext { |
69 | 69 | @Override |
70 | 70 | public void tellNext(TbMsg msg, String relationType, Throwable th) { |
71 | 71 | if (nodeCtx.getSelf().isDebugMode()) { |
72 | - mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, th); | |
72 | + mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, relationType, th); | |
73 | 73 | } |
74 | 74 | nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationType, msg), nodeCtx.getSelfActor()); |
75 | 75 | } |
... | ... | @@ -102,7 +102,7 @@ class DefaultTbContext implements TbContext { |
102 | 102 | @Override |
103 | 103 | public void tellError(TbMsg msg, Throwable th) { |
104 | 104 | if (nodeCtx.getSelf().isDebugMode()) { |
105 | - mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, th); | |
105 | + mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, "", th); | |
106 | 106 | } |
107 | 107 | nodeCtx.getSelfActor().tell(new RuleNodeToSelfErrorMsg(msg, th), nodeCtx.getSelfActor()); |
108 | 108 | } | ... | ... |
... | ... | @@ -96,12 +96,12 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh |
96 | 96 | private void reprocess(List<RuleNode> ruleNodeList) { |
97 | 97 | for (RuleNode ruleNode : ruleNodeList) { |
98 | 98 | for (TbMsg tbMsg : queue.findUnprocessed(ruleNode.getId().getId(), systemContext.getQueuePartitionId())) { |
99 | - pushMsgToNode(nodeActors.get(ruleNode.getId()), tbMsg); | |
99 | + pushMsgToNode(nodeActors.get(ruleNode.getId()), tbMsg, ""); | |
100 | 100 | } |
101 | 101 | } |
102 | 102 | if (firstNode != null) { |
103 | 103 | for (TbMsg tbMsg : queue.findUnprocessed(entityId.getId(), systemContext.getQueuePartitionId())) { |
104 | - pushMsgToNode(firstNode, tbMsg); | |
104 | + pushMsgToNode(firstNode, tbMsg, ""); | |
105 | 105 | } |
106 | 106 | } |
107 | 107 | } |
... | ... | @@ -183,13 +183,13 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh |
183 | 183 | |
184 | 184 | void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg envelope) { |
185 | 185 | checkActive(); |
186 | - putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> pushMsgToNode(firstNode, msg)); | |
186 | + putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> pushMsgToNode(firstNode, msg, "")); | |
187 | 187 | } |
188 | 188 | |
189 | 189 | void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg envelope) { |
190 | 190 | checkActive(); |
191 | 191 | putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> { |
192 | - pushMsgToNode(firstNode, msg); | |
192 | + pushMsgToNode(firstNode, msg, ""); | |
193 | 193 | envelope.getCallbackRef().tell(new RuleEngineQueuePutAckMsg(msg.getId()), self); |
194 | 194 | }); |
195 | 195 | } |
... | ... | @@ -197,9 +197,9 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh |
197 | 197 | void onRuleChainToRuleChainMsg(RuleChainToRuleChainMsg envelope) { |
198 | 198 | checkActive(); |
199 | 199 | if (envelope.isEnqueue()) { |
200 | - putToQueue(enrichWithRuleChainId(envelope.getMsg()), msg -> pushMsgToNode(firstNode, msg)); | |
200 | + putToQueue(enrichWithRuleChainId(envelope.getMsg()), msg -> pushMsgToNode(firstNode, msg, envelope.getFromRelationType())); | |
201 | 201 | } else { |
202 | - pushMsgToNode(firstNode, envelope.getMsg()); | |
202 | + pushMsgToNode(firstNode, envelope.getMsg(), envelope.getFromRelationType()); | |
203 | 203 | } |
204 | 204 | } |
205 | 205 | |
... | ... | @@ -218,17 +218,17 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh |
218 | 218 | queue.ack(msg, ackId.getId(), msg.getClusterPartition()); |
219 | 219 | } else if (relationsCount == 1) { |
220 | 220 | for (RuleNodeRelation relation : relations) { |
221 | - pushToTarget(msg, relation.getOut()); | |
221 | + pushToTarget(msg, relation.getOut(), relation.getType()); | |
222 | 222 | } |
223 | 223 | } else { |
224 | 224 | for (RuleNodeRelation relation : relations) { |
225 | 225 | EntityId target = relation.getOut(); |
226 | 226 | switch (target.getEntityType()) { |
227 | 227 | case RULE_NODE: |
228 | - enqueueAndForwardMsgCopyToNode(msg, target); | |
228 | + enqueueAndForwardMsgCopyToNode(msg, target, relation.getType()); | |
229 | 229 | break; |
230 | 230 | case RULE_CHAIN: |
231 | - enqueueAndForwardMsgCopyToChain(msg, target); | |
231 | + enqueueAndForwardMsgCopyToChain(msg, target, relation.getType()); | |
232 | 232 | break; |
233 | 233 | } |
234 | 234 | } |
... | ... | @@ -237,33 +237,33 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh |
237 | 237 | } |
238 | 238 | } |
239 | 239 | |
240 | - private void enqueueAndForwardMsgCopyToChain(TbMsg msg, EntityId target) { | |
240 | + private void enqueueAndForwardMsgCopyToChain(TbMsg msg, EntityId target, String fromRelationType) { | |
241 | 241 | RuleChainId targetRCId = new RuleChainId(target.getId()); |
242 | 242 | TbMsg copyMsg = msg.copy(UUIDs.timeBased(), targetRCId, null, DEFAULT_CLUSTER_PARTITION); |
243 | - parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, copyMsg, true), self); | |
243 | + parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, copyMsg, fromRelationType, true), self); | |
244 | 244 | } |
245 | 245 | |
246 | - private void enqueueAndForwardMsgCopyToNode(TbMsg msg, EntityId target) { | |
246 | + private void enqueueAndForwardMsgCopyToNode(TbMsg msg, EntityId target, String fromRelationType) { | |
247 | 247 | RuleNodeId targetId = new RuleNodeId(target.getId()); |
248 | 248 | RuleNodeCtx targetNodeCtx = nodeActors.get(targetId); |
249 | 249 | TbMsg copy = msg.copy(UUIDs.timeBased(), entityId, targetId, DEFAULT_CLUSTER_PARTITION); |
250 | - putToQueue(copy, queuedMsg -> pushMsgToNode(targetNodeCtx, queuedMsg)); | |
250 | + putToQueue(copy, queuedMsg -> pushMsgToNode(targetNodeCtx, queuedMsg, fromRelationType)); | |
251 | 251 | } |
252 | 252 | |
253 | - private void pushToTarget(TbMsg msg, EntityId target) { | |
253 | + private void pushToTarget(TbMsg msg, EntityId target, String fromRelationType) { | |
254 | 254 | switch (target.getEntityType()) { |
255 | 255 | case RULE_NODE: |
256 | - pushMsgToNode(nodeActors.get(new RuleNodeId(target.getId())), msg); | |
256 | + pushMsgToNode(nodeActors.get(new RuleNodeId(target.getId())), msg, fromRelationType); | |
257 | 257 | break; |
258 | 258 | case RULE_CHAIN: |
259 | - parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, msg, false), self); | |
259 | + parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, msg, fromRelationType, false), self); | |
260 | 260 | break; |
261 | 261 | } |
262 | 262 | } |
263 | 263 | |
264 | - private void pushMsgToNode(RuleNodeCtx nodeCtx, TbMsg msg) { | |
264 | + private void pushMsgToNode(RuleNodeCtx nodeCtx, TbMsg msg, String fromRelationType) { | |
265 | 265 | if (nodeCtx != null) { |
266 | - nodeCtx.getSelfActor().tell(new RuleChainToRuleNodeMsg(new DefaultTbContext(systemContext, nodeCtx), msg), self); | |
266 | + nodeCtx.getSelfActor().tell(new RuleChainToRuleNodeMsg(new DefaultTbContext(systemContext, nodeCtx), msg, fromRelationType), self); | |
267 | 267 | } |
268 | 268 | } |
269 | 269 | ... | ... |
... | ... | @@ -31,6 +31,7 @@ public final class RuleChainToRuleChainMsg implements TbActorMsg { |
31 | 31 | private final RuleChainId target; |
32 | 32 | private final RuleChainId source; |
33 | 33 | private final TbMsg msg; |
34 | + private final String fromRelationType; | |
34 | 35 | private final boolean enqueue; |
35 | 36 | |
36 | 37 | @Override | ... | ... |
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
... | ... | @@ -93,7 +93,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod |
93 | 93 | public void onRuleToSelfMsg(RuleNodeToSelfMsg msg) throws Exception { |
94 | 94 | checkActive(); |
95 | 95 | if (ruleNode.isDebugMode()) { |
96 | - systemContext.persistDebugInput(tenantId, entityId, msg.getMsg()); | |
96 | + systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), "Self"); | |
97 | 97 | } |
98 | 98 | tbNode.onMsg(defaultCtx, msg.getMsg()); |
99 | 99 | } |
... | ... | @@ -101,7 +101,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod |
101 | 101 | void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception { |
102 | 102 | checkActive(); |
103 | 103 | if (ruleNode.isDebugMode()) { |
104 | - systemContext.persistDebugInput(tenantId, entityId, msg.getMsg()); | |
104 | + systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), msg.getFromRelationType()); | |
105 | 105 | } |
106 | 106 | tbNode.onMsg(msg.getCtx(), msg.getMsg()); |
107 | 107 | } | ... | ... |
1 | 1 | /** |
2 | 2 | * Copyright © 2016-2018 The Thingsboard Authors |
3 | - * | |
3 | + * <p> | |
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | 5 | * you may not use this file except in compliance with the License. |
6 | 6 | * You may obtain a copy of the License at |
7 | - * | |
8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | - * | |
7 | + * <p> | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * <p> | |
10 | 10 | * Unless required by applicable law or agreed to in writing, software |
11 | 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
... | ... | @@ -28,7 +28,6 @@ import org.thingsboard.server.actors.app.AppActor; |
28 | 28 | import org.thingsboard.server.actors.rpc.RpcBroadcastMsg; |
29 | 29 | import org.thingsboard.server.actors.rpc.RpcManagerActor; |
30 | 30 | import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; |
31 | -import org.thingsboard.server.actors.rpc.RpcSessionTellMsg; | |
32 | 31 | import org.thingsboard.server.actors.session.SessionManagerActor; |
33 | 32 | import org.thingsboard.server.actors.stats.StatsActor; |
34 | 33 | import org.thingsboard.server.common.data.id.*; |
... | ... | @@ -39,14 +38,10 @@ import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
39 | 38 | import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; |
40 | 39 | import org.thingsboard.server.common.msg.cluster.ServerAddress; |
41 | 40 | import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg; |
42 | -import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; | |
43 | 41 | import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; |
44 | 42 | import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg; |
45 | -import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; | |
46 | 43 | import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; |
47 | 44 | import org.thingsboard.server.extensions.api.device.DeviceCredentialsUpdateNotificationMsg; |
48 | -import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; | |
49 | -import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg; | |
50 | 45 | import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; |
51 | 46 | import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; |
52 | 47 | import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
... | ... | @@ -59,10 +54,10 @@ import scala.concurrent.duration.Duration; |
59 | 54 | |
60 | 55 | import javax.annotation.PostConstruct; |
61 | 56 | import javax.annotation.PreDestroy; |
62 | -import java.util.Optional; | |
63 | 57 | |
58 | +import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CLUSTER_ACTOR_MESSAGE; | |
59 | +import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CLUSTER_ACTOR_MESSAGE_VALUE; | |
64 | 60 | import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CLUSTER_NETWORK_SERVER_DATA_MESSAGE; |
65 | -import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.RPC_BROADCAST_MSG; | |
66 | 61 | |
67 | 62 | @Service |
68 | 63 | @Slf4j |
... | ... | @@ -157,7 +152,6 @@ public class DefaultActorService implements ActorService { |
157 | 152 | } |
158 | 153 | |
159 | 154 | |
160 | - | |
161 | 155 | @Override |
162 | 156 | public void onServerAdded(ServerInstance server) { |
163 | 157 | log.trace("Processing onServerAdded msg: {}", server); |
... | ... | @@ -204,8 +198,8 @@ public class DefaultActorService implements ActorService { |
204 | 198 | rpcService.broadcast(new RpcBroadcastMsg(ClusterAPIProtos.ClusterMessage |
205 | 199 | .newBuilder() |
206 | 200 | .setPayload(ByteString |
207 | - .copyFrom(actorContext.getEncodingService().encode(msg))) | |
208 | - .setMessageType(CLUSTER_NETWORK_SERVER_DATA_MESSAGE) | |
201 | + .copyFrom(actorContext.getEncodingService().encode(msg))) | |
202 | + .setMessageType(CLUSTER_ACTOR_MESSAGE) | |
209 | 203 | .build())); |
210 | 204 | appActor.tell(msg, ActorRef.noSender()); |
211 | 205 | } |
... | ... | @@ -218,8 +212,9 @@ public class DefaultActorService implements ActorService { |
218 | 212 | |
219 | 213 | @Override |
220 | 214 | public void onRecievedMsg(ClusterAPIProtos.ClusterMessage msg) { |
221 | - switch(msg.getMessageType()) { | |
222 | - case CLUSTER_NETWORK_SERVER_DATA_MESSAGE: | |
215 | + ServerAddress serverAddress = new ServerAddress(msg.getServerAddress().getHost(), msg.getServerAddress().getPort()); | |
216 | + switch (msg.getMessageType()) { | |
217 | + case CLUSTER_ACTOR_MESSAGE: | |
223 | 218 | java.util.Optional<TbActorMsg> decodedMsg = actorContext.getEncodingService() |
224 | 219 | .decode(msg.getPayload().toByteArray()); |
225 | 220 | if (decodedMsg.isPresent()) { |
... | ... | @@ -229,7 +224,25 @@ public class DefaultActorService implements ActorService { |
229 | 224 | } |
230 | 225 | break; |
231 | 226 | case TO_ALL_NODES_MSG: |
232 | - //ToDo | |
227 | + //TODO | |
228 | + break; | |
229 | + case CLUSTER_TELEMETRY_SUBSCRIPTION_CREATE_MESSAGE: | |
230 | + actorContext.getTsSubService().onNewRemoteSubscription(serverAddress, msg.getPayload().toByteArray()); | |
231 | + break; | |
232 | + case CLUSTER_TELEMETRY_SUBSCRIPTION_UPDATE_MESSAGE: | |
233 | + actorContext.getTsSubService().onRemoteSubscriptionUpdate(serverAddress, msg.getPayload().toByteArray()); | |
234 | + break; | |
235 | + case CLUSTER_TELEMETRY_SUBSCRIPTION_CLOSE_MESSAGE: | |
236 | + actorContext.getTsSubService().onRemoteSubscriptionClose(serverAddress, msg.getPayload().toByteArray()); | |
237 | + break; | |
238 | + case CLUSTER_TELEMETRY_SESSION_CLOSE_MESSAGE: | |
239 | + actorContext.getTsSubService().onRemoteSessionClose(serverAddress, msg.getPayload().toByteArray()); | |
240 | + break; | |
241 | + case CLUSTER_TELEMETRY_ATTR_UPDATE_MESSAGE: | |
242 | + actorContext.getTsSubService().onRemoteAttributesUpdate(serverAddress, msg.getPayload().toByteArray()); | |
243 | + break; | |
244 | + case CLUSTER_TELEMETRY_TS_UPDATE_MESSAGE: | |
245 | + actorContext.getTsSubService().onRemoteTsUpdate(serverAddress, msg.getPayload().toByteArray()); | |
233 | 246 | break; |
234 | 247 | } |
235 | 248 | } | ... | ... |
... | ... | @@ -93,7 +93,7 @@ public class AlarmController extends BaseController { |
93 | 93 | try { |
94 | 94 | AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); |
95 | 95 | checkAlarmId(alarmId); |
96 | - alarmService.clearAlarm(alarmId, System.currentTimeMillis()).get(); | |
96 | + alarmService.clearAlarm(alarmId, null, System.currentTimeMillis()).get(); | |
97 | 97 | } catch (Exception e) { |
98 | 98 | throw handleException(e); |
99 | 99 | } | ... | ... |
... | ... | @@ -607,7 +607,7 @@ public abstract class BaseController { |
607 | 607 | } |
608 | 608 | |
609 | 609 | public static Exception toException(Throwable error) { |
610 | - return Exception.class.isInstance(error) ? (Exception) error : new Exception(error); | |
610 | + return error != null ? (Exception.class.isInstance(error) ? (Exception) error : new Exception(error)) : null; | |
611 | 611 | } |
612 | 612 | |
613 | 613 | } | ... | ... |
... | ... | @@ -21,14 +21,18 @@ import com.fasterxml.jackson.databind.JsonNode; |
21 | 21 | import com.fasterxml.jackson.databind.ObjectMapper; |
22 | 22 | import com.fasterxml.jackson.databind.node.ObjectNode; |
23 | 23 | import lombok.extern.slf4j.Slf4j; |
24 | +import org.springframework.beans.factory.annotation.Autowired; | |
24 | 25 | import org.springframework.http.HttpStatus; |
25 | 26 | import org.springframework.security.access.prepost.PreAuthorize; |
26 | 27 | import org.springframework.util.StringUtils; |
27 | 28 | import org.springframework.web.bind.annotation.*; |
28 | 29 | import org.thingsboard.rule.engine.api.ScriptEngine; |
30 | +import org.thingsboard.server.common.data.DataConstants; | |
29 | 31 | import org.thingsboard.server.common.data.EntityType; |
32 | +import org.thingsboard.server.common.data.Event; | |
30 | 33 | import org.thingsboard.server.common.data.audit.ActionType; |
31 | 34 | import org.thingsboard.server.common.data.id.RuleChainId; |
35 | +import org.thingsboard.server.common.data.id.RuleNodeId; | |
32 | 36 | import org.thingsboard.server.common.data.id.TenantId; |
33 | 37 | import org.thingsboard.server.common.data.page.TextPageData; |
34 | 38 | import org.thingsboard.server.common.data.page.TextPageLink; |
... | ... | @@ -38,6 +42,7 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; |
38 | 42 | import org.thingsboard.server.common.data.security.Authority; |
39 | 43 | import org.thingsboard.server.common.msg.TbMsg; |
40 | 44 | import org.thingsboard.server.common.msg.TbMsgMetaData; |
45 | +import org.thingsboard.server.dao.event.EventService; | |
41 | 46 | import org.thingsboard.server.dao.model.ModelConstants; |
42 | 47 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
43 | 48 | import org.thingsboard.server.service.script.NashornJsEngine; |
... | ... | @@ -52,9 +57,13 @@ import java.util.Set; |
52 | 57 | public class RuleChainController extends BaseController { |
53 | 58 | |
54 | 59 | public static final String RULE_CHAIN_ID = "ruleChainId"; |
60 | + public static final String RULE_NODE_ID = "ruleNodeId"; | |
55 | 61 | |
56 | 62 | private static final ObjectMapper objectMapper = new ObjectMapper(); |
57 | 63 | |
64 | + @Autowired | |
65 | + private EventService eventService; | |
66 | + | |
58 | 67 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") |
59 | 68 | @RequestMapping(value = "/ruleChain/{ruleChainId}", method = RequestMethod.GET) |
60 | 69 | @ResponseBody |
... | ... | @@ -217,6 +226,31 @@ public class RuleChainController extends BaseController { |
217 | 226 | } |
218 | 227 | } |
219 | 228 | |
229 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") | |
230 | + @RequestMapping(value = "/ruleNode/{ruleNodeId}/debugIn", method = RequestMethod.GET) | |
231 | + @ResponseBody | |
232 | + public JsonNode getLatestRuleNodeDebugInput(@PathVariable(RULE_NODE_ID) String strRuleNodeId) throws ThingsboardException { | |
233 | + checkParameter(RULE_NODE_ID, strRuleNodeId); | |
234 | + try { | |
235 | + RuleNodeId ruleNodeId = new RuleNodeId(toUUID(strRuleNodeId)); | |
236 | + TenantId tenantId = getCurrentUser().getTenantId(); | |
237 | + List<Event> events = eventService.findLatestEvents(tenantId, ruleNodeId, DataConstants.DEBUG_RULE_NODE, 2); | |
238 | + JsonNode result = null; | |
239 | + if (events != null) { | |
240 | + for (Event event : events) { | |
241 | + JsonNode body = event.getBody(); | |
242 | + if (body.has("type") && body.get("type").asText().equals("IN")) { | |
243 | + result = body; | |
244 | + break; | |
245 | + } | |
246 | + } | |
247 | + } | |
248 | + return result; | |
249 | + } catch (Exception e) { | |
250 | + throw handleException(e); | |
251 | + } | |
252 | + } | |
253 | + | |
220 | 254 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
221 | 255 | @RequestMapping(value = "/ruleChain/testScript", method = RequestMethod.POST) |
222 | 256 | @ResponseBody | ... | ... |
1 | 1 | /** |
2 | 2 | * Copyright © 2016-2018 The Thingsboard Authors |
3 | - * | |
3 | + * <p> | |
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | 5 | * you may not use this file except in compliance with the License. |
6 | 6 | * You may obtain a copy of the License at |
7 | - * | |
8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | - * | |
7 | + * <p> | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * <p> | |
10 | 10 | * Unless required by applicable law or agreed to in writing, software |
11 | 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
... | ... | @@ -26,13 +26,18 @@ import org.springframework.util.SerializationUtils; |
26 | 26 | import org.thingsboard.server.actors.rpc.RpcBroadcastMsg; |
27 | 27 | import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; |
28 | 28 | import org.thingsboard.server.common.data.id.EntityId; |
29 | +import org.thingsboard.server.common.msg.TbActorMsg; | |
30 | +import org.thingsboard.server.common.msg.cluster.ServerAddress; | |
29 | 31 | import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; |
30 | 32 | |
33 | +import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; | |
34 | +import org.thingsboard.server.extensions.core.plugin.telemetry.sub.Subscription; | |
31 | 35 | import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
32 | 36 | |
33 | 37 | import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc; |
34 | 38 | import org.thingsboard.server.service.cluster.discovery.ServerInstance; |
35 | 39 | import org.thingsboard.server.service.cluster.discovery.ServerInstanceService; |
40 | +import org.thingsboard.server.service.encoding.DataDecodingEncodingService; | |
36 | 41 | |
37 | 42 | import javax.annotation.PreDestroy; |
38 | 43 | import java.io.IOException; |
... | ... | @@ -52,6 +57,9 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI |
52 | 57 | @Autowired |
53 | 58 | private ServerInstanceService instanceService; |
54 | 59 | |
60 | + @Autowired | |
61 | + private DataDecodingEncodingService encodingService; | |
62 | + | |
55 | 63 | private RpcMsgListener listener; |
56 | 64 | |
57 | 65 | private Server server; |
... | ... | @@ -77,8 +85,8 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI |
77 | 85 | } |
78 | 86 | |
79 | 87 | @Override |
80 | - public void onSessionCreated(UUID msgUid, StreamObserver<ClusterAPIProtos.ClusterMessage> inputStream) { | |
81 | - BlockingQueue<StreamObserver<ClusterAPIProtos.ClusterMessage>> queue = pendingSessionMap.remove(msgUid); | |
88 | + public void onSessionCreated(UUID msgUid, StreamObserver<ClusterAPIProtos.ClusterMessage> inputStream) { | |
89 | + BlockingQueue<StreamObserver<ClusterAPIProtos.ClusterMessage>> queue = pendingSessionMap.remove(msgUid); | |
82 | 90 | if (queue != null) { |
83 | 91 | try { |
84 | 92 | queue.put(inputStream); |
... | ... | @@ -120,7 +128,7 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI |
120 | 128 | listener.onBroadcastMsg(msg); |
121 | 129 | } |
122 | 130 | |
123 | - private StreamObserver<ClusterAPIProtos.ClusterMessage> createSession(RpcSessionCreateRequestMsg msg) { | |
131 | + private StreamObserver<ClusterAPIProtos.ClusterMessage> createSession(RpcSessionCreateRequestMsg msg) { | |
124 | 132 | BlockingQueue<StreamObserver<ClusterAPIProtos.ClusterMessage>> queue = new ArrayBlockingQueue<>(1); |
125 | 133 | pendingSessionMap.put(msg.getMsgUid(), queue); |
126 | 134 | listener.onRpcSessionCreateRequestMsg(msg); |
... | ... | @@ -139,5 +147,22 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI |
139 | 147 | listener.onSendMsg(message); |
140 | 148 | } |
141 | 149 | |
150 | + @Override | |
151 | + public void tell(ServerAddress serverAddress, TbActorMsg actorMsg) { | |
152 | + listener.onSendMsg(encodingService.convertToProtoDataMessage(serverAddress, actorMsg)); | |
153 | + } | |
142 | 154 | |
155 | + @Override | |
156 | + public void tell(ServerAddress serverAddress, ClusterAPIProtos.MessageType msgType, byte[] data) { | |
157 | + ClusterAPIProtos.ClusterMessage msg = ClusterAPIProtos.ClusterMessage | |
158 | + .newBuilder() | |
159 | + .setServerAddress(ClusterAPIProtos.ServerAddress | |
160 | + .newBuilder() | |
161 | + .setHost(serverAddress.getHost()) | |
162 | + .setPort(serverAddress.getPort()) | |
163 | + .build()) | |
164 | + .setMessageType(msgType) | |
165 | + .setPayload(ByteString.copyFrom(data)).build(); | |
166 | + listener.onSendMsg(msg); | |
167 | + } | |
143 | 168 | } | ... | ... |
... | ... | @@ -25,6 +25,7 @@ import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; |
25 | 25 | import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; |
26 | 26 | import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg; |
27 | 27 | import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; |
28 | +import org.thingsboard.server.extensions.core.plugin.telemetry.sub.Subscription; | |
28 | 29 | import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
29 | 30 | import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; |
30 | 31 | |
... | ... | @@ -45,4 +46,5 @@ public interface ClusterRpcService { |
45 | 46 | |
46 | 47 | void tell(ServerAddress serverAddress, TbActorMsg actorMsg); |
47 | 48 | |
49 | + void tell(ServerAddress serverAddress, ClusterAPIProtos.MessageType msgType, byte[] data); | |
48 | 50 | } | ... | ... |
1 | 1 | /** |
2 | 2 | * Copyright © 2016-2018 The Thingsboard Authors |
3 | - * | |
3 | + * <p> | |
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | 5 | * you may not use this file except in compliance with the License. |
6 | 6 | * You may obtain a copy of the License at |
7 | - * | |
8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | - * | |
7 | + * <p> | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * <p> | |
10 | 10 | * Unless required by applicable law or agreed to in writing, software |
11 | 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
... | ... | @@ -18,30 +18,40 @@ package org.thingsboard.server.service.telemetry; |
18 | 18 | import com.google.common.util.concurrent.FutureCallback; |
19 | 19 | import com.google.common.util.concurrent.Futures; |
20 | 20 | import com.google.common.util.concurrent.ListenableFuture; |
21 | +import com.google.protobuf.InvalidProtocolBufferException; | |
21 | 22 | import lombok.extern.slf4j.Slf4j; |
22 | 23 | import org.springframework.beans.factory.annotation.Autowired; |
23 | 24 | import org.springframework.context.annotation.Lazy; |
24 | 25 | import org.springframework.stereotype.Service; |
25 | 26 | import org.springframework.util.StringUtils; |
27 | +import org.thingsboard.rule.engine.DonAsynchron; | |
26 | 28 | import org.thingsboard.server.common.data.DataConstants; |
27 | 29 | import org.thingsboard.server.common.data.EntityType; |
28 | 30 | import org.thingsboard.server.common.data.id.DeviceId; |
29 | 31 | import org.thingsboard.server.common.data.id.EntityId; |
32 | +import org.thingsboard.server.common.data.id.EntityIdFactory; | |
30 | 33 | import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
31 | 34 | import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; |
35 | +import org.thingsboard.server.common.data.kv.BaseTsKvQuery; | |
32 | 36 | import org.thingsboard.server.common.data.kv.BasicTsKvEntry; |
33 | 37 | import org.thingsboard.server.common.data.kv.BooleanDataEntry; |
38 | +import org.thingsboard.server.common.data.kv.DataType; | |
34 | 39 | import org.thingsboard.server.common.data.kv.DoubleDataEntry; |
40 | +import org.thingsboard.server.common.data.kv.KvEntry; | |
35 | 41 | import org.thingsboard.server.common.data.kv.LongDataEntry; |
36 | 42 | import org.thingsboard.server.common.data.kv.StringDataEntry; |
37 | 43 | import org.thingsboard.server.common.data.kv.TsKvEntry; |
44 | +import org.thingsboard.server.common.data.kv.TsKvQuery; | |
38 | 45 | import org.thingsboard.server.common.msg.cluster.ServerAddress; |
39 | 46 | import org.thingsboard.server.dao.attributes.AttributesService; |
40 | 47 | import org.thingsboard.server.dao.timeseries.TimeseriesService; |
48 | +import org.thingsboard.server.extensions.api.plugins.PluginContext; | |
41 | 49 | import org.thingsboard.server.extensions.core.plugin.telemetry.handlers.TelemetryFeature; |
42 | 50 | import org.thingsboard.server.extensions.core.plugin.telemetry.sub.Subscription; |
51 | +import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionErrorCode; | |
43 | 52 | import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionState; |
44 | 53 | import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionUpdate; |
54 | +import org.thingsboard.server.gen.cluster.ClusterAPIProtos; | |
45 | 55 | import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; |
46 | 56 | import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; |
47 | 57 | import org.thingsboard.server.service.state.DefaultDeviceStateService; |
... | ... | @@ -54,15 +64,18 @@ import java.util.ArrayList; |
54 | 64 | import java.util.Collections; |
55 | 65 | import java.util.HashMap; |
56 | 66 | import java.util.HashSet; |
67 | +import java.util.Iterator; | |
57 | 68 | import java.util.List; |
58 | 69 | import java.util.Map; |
59 | 70 | import java.util.Optional; |
60 | 71 | import java.util.Set; |
72 | +import java.util.TreeMap; | |
61 | 73 | import java.util.concurrent.ExecutorService; |
62 | 74 | import java.util.concurrent.Executors; |
63 | 75 | import java.util.function.Consumer; |
64 | 76 | import java.util.function.Function; |
65 | 77 | import java.util.function.Predicate; |
78 | +import java.util.stream.Collectors; | |
66 | 79 | |
67 | 80 | /** |
68 | 81 | * Created by ashvayka on 27.03.18. |
... | ... | @@ -120,8 +133,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio |
120 | 133 | ServerAddress address = server.get(); |
121 | 134 | log.trace("[{}] Forwarding subscription [{}] for device [{}] to [{}]", sessionId, sub.getSubscriptionId(), entityId, address); |
122 | 135 | subscription = new Subscription(sub, true, address); |
123 | -// rpcService.tell(); | |
124 | -// rpcHandler.onNewSubscription(ctx, address, sessionId, subscription); | |
136 | + tellNewSubscription(address, sessionId, subscription); | |
125 | 137 | } else { |
126 | 138 | log.trace("[{}] Registering local subscription [{}] for device [{}]", sessionId, sub.getSubscriptionId(), entityId); |
127 | 139 | subscription = new Subscription(sub, true); |
... | ... | @@ -193,6 +205,174 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio |
193 | 205 | , System.currentTimeMillis())), callback); |
194 | 206 | } |
195 | 207 | |
208 | + @Override | |
209 | + public void onNewRemoteSubscription(ServerAddress serverAddress, byte[] data) { | |
210 | + ClusterAPIProtos.SubscriptionProto proto; | |
211 | + try { | |
212 | + proto = ClusterAPIProtos.SubscriptionProto.parseFrom(data); | |
213 | + } catch (InvalidProtocolBufferException e) { | |
214 | + throw new RuntimeException(e); | |
215 | + } | |
216 | + Map<String, Long> statesMap = proto.getKeyStatesList().stream().collect( | |
217 | + Collectors.toMap(ClusterAPIProtos.SubscriptionKetStateProto::getKey, ClusterAPIProtos.SubscriptionKetStateProto::getTs)); | |
218 | + Subscription subscription = new Subscription( | |
219 | + new SubscriptionState(proto.getSessionId(), proto.getSubscriptionId(), | |
220 | + EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId()), | |
221 | + TelemetryFeature.valueOf(proto.getType()), proto.getAllKeys(), statesMap, proto.getScope()), | |
222 | + false, new ServerAddress(serverAddress.getHost(), serverAddress.getPort())); | |
223 | + | |
224 | + addRemoteWsSubscription(serverAddress, proto.getSessionId(), subscription); | |
225 | + } | |
226 | + | |
227 | + @Override | |
228 | + public void onRemoteSubscriptionUpdate(ServerAddress serverAddress, byte[] data) { | |
229 | + ClusterAPIProtos.SubscriptionUpdateProto proto; | |
230 | + try { | |
231 | + proto = ClusterAPIProtos.SubscriptionUpdateProto.parseFrom(data); | |
232 | + } catch (InvalidProtocolBufferException e) { | |
233 | + throw new RuntimeException(e); | |
234 | + } | |
235 | + SubscriptionUpdate update = convert(proto); | |
236 | + String sessionId = proto.getSessionId(); | |
237 | + log.trace("[{}] Processing remote subscription onUpdate [{}]", sessionId, update); | |
238 | + Optional<Subscription> subOpt = getSubscription(sessionId, update.getSubscriptionId()); | |
239 | + if (subOpt.isPresent()) { | |
240 | + updateSubscriptionState(sessionId, subOpt.get(), update); | |
241 | + wsService.sendWsMsg(sessionId, update); | |
242 | + } | |
243 | + } | |
244 | + | |
245 | + @Override | |
246 | + public void onRemoteSubscriptionClose(ServerAddress serverAddress, byte[] data) { | |
247 | + ClusterAPIProtos.SubscriptionCloseProto proto; | |
248 | + try { | |
249 | + proto = ClusterAPIProtos.SubscriptionCloseProto.parseFrom(data); | |
250 | + } catch (InvalidProtocolBufferException e) { | |
251 | + throw new RuntimeException(e); | |
252 | + } | |
253 | + removeSubscription(proto.getSessionId(), proto.getSubscriptionId()); | |
254 | + } | |
255 | + | |
256 | + @Override | |
257 | + public void onRemoteSessionClose(ServerAddress serverAddress, byte[] data) { | |
258 | + ClusterAPIProtos.SessionCloseProto proto; | |
259 | + try { | |
260 | + proto = ClusterAPIProtos.SessionCloseProto.parseFrom(data); | |
261 | + } catch (InvalidProtocolBufferException e) { | |
262 | + throw new RuntimeException(e); | |
263 | + } | |
264 | + cleanupRemoteWsSessionSubscriptions(proto.getSessionId()); | |
265 | + } | |
266 | + | |
267 | + @Override | |
268 | + public void onRemoteAttributesUpdate(ServerAddress serverAddress, byte[] data) { | |
269 | + ClusterAPIProtos.AttributeUpdateProto proto; | |
270 | + try { | |
271 | + proto = ClusterAPIProtos.AttributeUpdateProto.parseFrom(data); | |
272 | + } catch (InvalidProtocolBufferException e) { | |
273 | + throw new RuntimeException(e); | |
274 | + } | |
275 | + onAttributesUpdate(EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId()), proto.getScope(), | |
276 | + proto.getDataList().stream().map(this::toAttribute).collect(Collectors.toList())); | |
277 | + } | |
278 | + | |
279 | + @Override | |
280 | + public void onRemoteTsUpdate(ServerAddress serverAddress, byte[] data) { | |
281 | + ClusterAPIProtos.TimeseriesUpdateProto proto; | |
282 | + try { | |
283 | + proto = ClusterAPIProtos.TimeseriesUpdateProto.parseFrom(data); | |
284 | + } catch (InvalidProtocolBufferException e) { | |
285 | + throw new RuntimeException(e); | |
286 | + } | |
287 | + onTimeseriesUpdate(EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId()), | |
288 | + proto.getDataList().stream().map(this::toTimeseries).collect(Collectors.toList())); | |
289 | + } | |
290 | + | |
291 | + @Override | |
292 | + public void onClusterUpdate() { | |
293 | + log.trace("Processing cluster onUpdate msg!"); | |
294 | + Iterator<Map.Entry<EntityId, Set<Subscription>>> deviceIterator = subscriptionsByEntityId.entrySet().iterator(); | |
295 | + while (deviceIterator.hasNext()) { | |
296 | + Map.Entry<EntityId, Set<Subscription>> e = deviceIterator.next(); | |
297 | + Set<Subscription> subscriptions = e.getValue(); | |
298 | + Optional<ServerAddress> newAddressOptional = routingService.resolveById(e.getKey()); | |
299 | + if (newAddressOptional.isPresent()) { | |
300 | + newAddressOptional.ifPresent(serverAddress -> checkSubsciptionsNewAddress(serverAddress, subscriptions)); | |
301 | + } else { | |
302 | + checkSubsciptionsPrevAddress(subscriptions); | |
303 | + } | |
304 | + if (subscriptions.size() == 0) { | |
305 | + log.trace("[{}] No more subscriptions for this device on current server.", e.getKey()); | |
306 | + deviceIterator.remove(); | |
307 | + } | |
308 | + } | |
309 | + } | |
310 | + | |
311 | + private void checkSubsciptionsNewAddress(ServerAddress newAddress, Set<Subscription> subscriptions) { | |
312 | + Iterator<Subscription> subscriptionIterator = subscriptions.iterator(); | |
313 | + while (subscriptionIterator.hasNext()) { | |
314 | + Subscription s = subscriptionIterator.next(); | |
315 | + if (s.isLocal()) { | |
316 | + if (!newAddress.equals(s.getServer())) { | |
317 | + log.trace("[{}] Local subscription is now handled on new server [{}]", s.getWsSessionId(), newAddress); | |
318 | + s.setServer(newAddress); | |
319 | + tellNewSubscription(newAddress, s.getWsSessionId(), s); | |
320 | + } | |
321 | + } else { | |
322 | + log.trace("[{}] Remote subscription is now handled on new server address: [{}]", s.getWsSessionId(), newAddress); | |
323 | + subscriptionIterator.remove(); | |
324 | + //TODO: onUpdate state of subscription by WsSessionId and other maps. | |
325 | + } | |
326 | + } | |
327 | + } | |
328 | + | |
329 | + private void checkSubsciptionsPrevAddress(Set<Subscription> subscriptions) { | |
330 | + for (Subscription s : subscriptions) { | |
331 | + if (s.isLocal() && s.getServer() != null) { | |
332 | + log.trace("[{}] Local subscription is no longer handled on remote server address [{}]", s.getWsSessionId(), s.getServer()); | |
333 | + s.setServer(null); | |
334 | + } else { | |
335 | + log.trace("[{}] Remote subscription is on up to date server address.", s.getWsSessionId()); | |
336 | + } | |
337 | + } | |
338 | + } | |
339 | + | |
340 | + private void addRemoteWsSubscription(ServerAddress address, String sessionId, Subscription subscription) { | |
341 | + EntityId entityId = subscription.getEntityId(); | |
342 | + log.trace("[{}] Registering remote subscription [{}] for device [{}] to [{}]", sessionId, subscription.getSubscriptionId(), entityId, address); | |
343 | + registerSubscription(sessionId, entityId, subscription); | |
344 | + if (subscription.getType() == TelemetryFeature.ATTRIBUTES) { | |
345 | + final Map<String, Long> keyStates = subscription.getKeyStates(); | |
346 | + DonAsynchron.withCallback(attrService.find(entityId, DataConstants.CLIENT_SCOPE, keyStates.keySet()), values -> { | |
347 | + List<TsKvEntry> missedUpdates = new ArrayList<>(); | |
348 | + values.forEach(latestEntry -> { | |
349 | + if (latestEntry.getLastUpdateTs() > keyStates.get(latestEntry.getKey())) { | |
350 | + missedUpdates.add(new BasicTsKvEntry(latestEntry.getLastUpdateTs(), latestEntry)); | |
351 | + } | |
352 | + }); | |
353 | + if (!missedUpdates.isEmpty()) { | |
354 | + tellRemoteSubUpdate(address, sessionId, new SubscriptionUpdate(subscription.getSubscriptionId(), missedUpdates)); | |
355 | + } | |
356 | + }, | |
357 | + e -> log.error("Failed to fetch missed updates.", e), tsCallBackExecutor); | |
358 | + } else if (subscription.getType() == TelemetryFeature.TIMESERIES) { | |
359 | + long curTs = System.currentTimeMillis(); | |
360 | + List<TsKvQuery> queries = new ArrayList<>(); | |
361 | + subscription.getKeyStates().entrySet().forEach(e -> { | |
362 | + queries.add(new BaseTsKvQuery(e.getKey(), e.getValue() + 1L, curTs)); | |
363 | + }); | |
364 | + | |
365 | + DonAsynchron.withCallback(tsService.findAll(entityId, queries), | |
366 | + missedUpdates -> { | |
367 | + if (!missedUpdates.isEmpty()) { | |
368 | + tellRemoteSubUpdate(address, sessionId, new SubscriptionUpdate(subscription.getSubscriptionId(), missedUpdates)); | |
369 | + } | |
370 | + }, | |
371 | + e -> log.error("Failed to fetch missed updates.", e), | |
372 | + tsCallBackExecutor); | |
373 | + } | |
374 | + } | |
375 | + | |
196 | 376 | private void onAttributesUpdate(EntityId entityId, String scope, List<AttributeKvEntry> attributes) { |
197 | 377 | Optional<ServerAddress> serverAddress = routingService.resolveById(entityId); |
198 | 378 | if (!serverAddress.isPresent()) { |
... | ... | @@ -205,7 +385,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio |
205 | 385 | } |
206 | 386 | } |
207 | 387 | } else { |
208 | -// rpcHandler.onAttributesUpdate(ctx, serverAddress.get(), entityId, entries); | |
388 | + tellRemoteAttributesUpdate(serverAddress.get(), entityId, scope, attributes); | |
209 | 389 | } |
210 | 390 | } |
211 | 391 | |
... | ... | @@ -214,7 +394,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio |
214 | 394 | if (!serverAddress.isPresent()) { |
215 | 395 | onLocalTimeseriesUpdate(entityId, ts); |
216 | 396 | } else { |
217 | -// rpcHandler.onTimeseriesUpdate(ctx, serverAddress.get(), entityId, entries); | |
397 | + tellRemoteTimeseriesUpdate(serverAddress.get(), entityId, ts); | |
218 | 398 | } |
219 | 399 | } |
220 | 400 | |
... | ... | @@ -260,8 +440,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio |
260 | 440 | updateSubscriptionState(sessionId, s, update); |
261 | 441 | wsService.sendWsMsg(sessionId, update); |
262 | 442 | } else { |
263 | - //TODO: ashvayka | |
264 | -// rpcHandler.onSubscriptionUpdate(ctx, s.getServer(), sessionId, update); | |
443 | + tellRemoteSubUpdate(s.getServer(), sessionId, update); | |
265 | 444 | } |
266 | 445 | } |
267 | 446 | }); |
... | ... | @@ -282,11 +461,11 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio |
282 | 461 | sessionSubscriptions.put(subscription.getSubscriptionId(), subscription); |
283 | 462 | } |
284 | 463 | |
285 | - public void cleanupLocalWsSessionSubscriptions(String sessionId) { | |
464 | + private void cleanupLocalWsSessionSubscriptions(String sessionId) { | |
286 | 465 | cleanupWsSessionSubscriptions(sessionId, true); |
287 | 466 | } |
288 | 467 | |
289 | - public void cleanupRemoteWsSessionSubscriptions(String sessionId) { | |
468 | + private void cleanupRemoteWsSessionSubscriptions(String sessionId) { | |
290 | 469 | cleanupWsSessionSubscriptions(sessionId, false); |
291 | 470 | } |
292 | 471 | |
... | ... | @@ -324,14 +503,14 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio |
324 | 503 | } |
325 | 504 | for (ServerAddress address : affectedServers) { |
326 | 505 | log.debug("[{}] Going to onSubscriptionUpdate [{}] server about session close event", sessionId, address); |
327 | -// rpcHandler.onSessionClose(ctx, address, sessionId); | |
506 | + tellRemoteSessionClose(address, sessionId); | |
328 | 507 | } |
329 | 508 | } |
330 | 509 | |
331 | 510 | private void processSubscriptionRemoval(String sessionId, Map<Integer, Subscription> sessionSubscriptions, Subscription subscription) { |
332 | 511 | EntityId entityId = subscription.getEntityId(); |
333 | 512 | if (subscription.isLocal() && subscription.getServer() != null) { |
334 | -// rpcHandler.onSubscriptionClose(ctx, subscription.getServer(), sessionId, subscription.getSubscriptionId()); | |
513 | + tellRemoteSubClose(subscription.getServer(), sessionId, subscription.getSubscriptionId()); | |
335 | 514 | } |
336 | 515 | if (sessionSubscriptions.isEmpty()) { |
337 | 516 | log.debug("[{}] Removed last subscription for particular session.", sessionId); |
... | ... | @@ -383,4 +562,151 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio |
383 | 562 | } |
384 | 563 | }, wsCallBackExecutor); |
385 | 564 | } |
565 | + | |
566 | + private void tellNewSubscription(ServerAddress address, String sessionId, Subscription sub) { | |
567 | + ClusterAPIProtos.SubscriptionProto.Builder builder = ClusterAPIProtos.SubscriptionProto.newBuilder(); | |
568 | + builder.setSessionId(sessionId); | |
569 | + builder.setSubscriptionId(sub.getSubscriptionId()); | |
570 | + builder.setEntityType(sub.getEntityId().getEntityType().name()); | |
571 | + builder.setEntityId(sub.getEntityId().getId().toString()); | |
572 | + builder.setType(sub.getType().name()); | |
573 | + builder.setAllKeys(sub.isAllKeys()); | |
574 | + builder.setScope(sub.getScope()); | |
575 | + sub.getKeyStates().entrySet().forEach(e -> builder.addKeyStates( | |
576 | + ClusterAPIProtos.SubscriptionKetStateProto.newBuilder().setKey(e.getKey()).setTs(e.getValue()).build())); | |
577 | + rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_SUBSCRIPTION_CREATE_MESSAGE, builder.build().toByteArray()); | |
578 | + } | |
579 | + | |
580 | + private void tellRemoteSubUpdate(ServerAddress address, String sessionId, SubscriptionUpdate update) { | |
581 | + ClusterAPIProtos.SubscriptionUpdateProto.Builder builder = ClusterAPIProtos.SubscriptionUpdateProto.newBuilder(); | |
582 | + builder.setSessionId(sessionId); | |
583 | + builder.setSubscriptionId(update.getSubscriptionId()); | |
584 | + builder.setErrorCode(update.getErrorCode()); | |
585 | + if (update.getErrorMsg() != null) { | |
586 | + builder.setErrorMsg(update.getErrorMsg()); | |
587 | + } | |
588 | + update.getData().entrySet().forEach( | |
589 | + e -> { | |
590 | + ClusterAPIProtos.SubscriptionUpdateValueListProto.Builder dataBuilder = ClusterAPIProtos.SubscriptionUpdateValueListProto.newBuilder(); | |
591 | + | |
592 | + dataBuilder.setKey(e.getKey()); | |
593 | + e.getValue().forEach(v -> { | |
594 | + Object[] array = (Object[]) v; | |
595 | + dataBuilder.addTs((long) array[0]); | |
596 | + dataBuilder.addValue((String) array[1]); | |
597 | + }); | |
598 | + | |
599 | + builder.addData(dataBuilder.build()); | |
600 | + } | |
601 | + ); | |
602 | + rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_SUBSCRIPTION_UPDATE_MESSAGE, builder.build().toByteArray()); | |
603 | + } | |
604 | + | |
605 | + private void tellRemoteAttributesUpdate(ServerAddress address, EntityId entityId, String scope, List<AttributeKvEntry> attributes) { | |
606 | + ClusterAPIProtos.AttributeUpdateProto.Builder builder = ClusterAPIProtos.AttributeUpdateProto.newBuilder(); | |
607 | + builder.setEntityId(entityId.getId().toString()); | |
608 | + builder.setEntityType(entityId.getEntityType().name()); | |
609 | + builder.setScope(scope); | |
610 | + attributes.forEach(v -> builder.addData(toKeyValueProto(v.getLastUpdateTs(), v).build())); | |
611 | + rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_ATTR_UPDATE_MESSAGE, builder.build().toByteArray()); | |
612 | + } | |
613 | + | |
614 | + private void tellRemoteTimeseriesUpdate(ServerAddress address, EntityId entityId, List<TsKvEntry> ts) { | |
615 | + ClusterAPIProtos.TimeseriesUpdateProto.Builder builder = ClusterAPIProtos.TimeseriesUpdateProto.newBuilder(); | |
616 | + builder.setEntityId(entityId.getId().toString()); | |
617 | + builder.setEntityType(entityId.getEntityType().name()); | |
618 | + ts.forEach(v -> builder.addData(toKeyValueProto(v.getTs(), v).build())); | |
619 | + rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_TS_UPDATE_MESSAGE, builder.build().toByteArray()); | |
620 | + } | |
621 | + | |
622 | + private void tellRemoteSessionClose(ServerAddress address, String sessionId) { | |
623 | + ClusterAPIProtos.SessionCloseProto proto = ClusterAPIProtos.SessionCloseProto.newBuilder().setSessionId(sessionId).build(); | |
624 | + rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_SESSION_CLOSE_MESSAGE, proto.toByteArray()); | |
625 | + } | |
626 | + | |
627 | + private void tellRemoteSubClose(ServerAddress address, String sessionId, int subscriptionId) { | |
628 | + ClusterAPIProtos.SubscriptionCloseProto proto = ClusterAPIProtos.SubscriptionCloseProto.newBuilder().setSessionId(sessionId).setSubscriptionId(subscriptionId).build(); | |
629 | + rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_SUBSCRIPTION_CLOSE_MESSAGE, proto.toByteArray()); | |
630 | + } | |
631 | + | |
632 | + private ClusterAPIProtos.KeyValueProto.Builder toKeyValueProto(long ts, KvEntry attr) { | |
633 | + ClusterAPIProtos.KeyValueProto.Builder dataBuilder = ClusterAPIProtos.KeyValueProto.newBuilder(); | |
634 | + dataBuilder.setKey(attr.getKey()); | |
635 | + dataBuilder.setTs(ts); | |
636 | + dataBuilder.setValueType(attr.getDataType().ordinal()); | |
637 | + switch (attr.getDataType()) { | |
638 | + case BOOLEAN: | |
639 | + Optional<Boolean> booleanValue = attr.getBooleanValue(); | |
640 | + booleanValue.ifPresent(dataBuilder::setBoolValue); | |
641 | + break; | |
642 | + case LONG: | |
643 | + Optional<Long> longValue = attr.getLongValue(); | |
644 | + longValue.ifPresent(dataBuilder::setLongValue); | |
645 | + break; | |
646 | + case DOUBLE: | |
647 | + Optional<Double> doubleValue = attr.getDoubleValue(); | |
648 | + doubleValue.ifPresent(dataBuilder::setDoubleValue); | |
649 | + break; | |
650 | + case STRING: | |
651 | + Optional<String> stringValue = attr.getStrValue(); | |
652 | + stringValue.ifPresent(dataBuilder::setStrValue); | |
653 | + break; | |
654 | + } | |
655 | + return dataBuilder; | |
656 | + } | |
657 | + | |
658 | + private AttributeKvEntry toAttribute(ClusterAPIProtos.KeyValueProto proto) { | |
659 | + return new BaseAttributeKvEntry(getKvEntry(proto), proto.getTs()); | |
660 | + } | |
661 | + | |
662 | + private TsKvEntry toTimeseries(ClusterAPIProtos.KeyValueProto proto) { | |
663 | + return new BasicTsKvEntry(proto.getTs(), getKvEntry(proto)); | |
664 | + } | |
665 | + | |
666 | + private KvEntry getKvEntry(ClusterAPIProtos.KeyValueProto proto) { | |
667 | + KvEntry entry = null; | |
668 | + DataType type = DataType.values()[proto.getValueType()]; | |
669 | + switch (type) { | |
670 | + case BOOLEAN: | |
671 | + entry = new BooleanDataEntry(proto.getKey(), proto.getBoolValue()); | |
672 | + break; | |
673 | + case LONG: | |
674 | + entry = new LongDataEntry(proto.getKey(), proto.getLongValue()); | |
675 | + break; | |
676 | + case DOUBLE: | |
677 | + entry = new DoubleDataEntry(proto.getKey(), proto.getDoubleValue()); | |
678 | + break; | |
679 | + case STRING: | |
680 | + entry = new StringDataEntry(proto.getKey(), proto.getStrValue()); | |
681 | + break; | |
682 | + } | |
683 | + return entry; | |
684 | + } | |
685 | + | |
686 | + private SubscriptionUpdate convert(ClusterAPIProtos.SubscriptionUpdateProto proto) { | |
687 | + if (proto.getErrorCode() > 0) { | |
688 | + return new SubscriptionUpdate(proto.getSubscriptionId(), SubscriptionErrorCode.forCode(proto.getErrorCode()), proto.getErrorMsg()); | |
689 | + } else { | |
690 | + Map<String, List<Object>> data = new TreeMap<>(); | |
691 | + proto.getDataList().forEach(v -> { | |
692 | + List<Object> values = data.computeIfAbsent(v.getKey(), k -> new ArrayList<>()); | |
693 | + for (int i = 0; i < v.getTsCount(); i++) { | |
694 | + Object[] value = new Object[2]; | |
695 | + value[0] = v.getTs(i); | |
696 | + value[1] = v.getValue(i); | |
697 | + values.add(value); | |
698 | + } | |
699 | + }); | |
700 | + return new SubscriptionUpdate(proto.getSubscriptionId(), data); | |
701 | + } | |
702 | + } | |
703 | + | |
704 | + private Optional<Subscription> getSubscription(String sessionId, int subscriptionId) { | |
705 | + Subscription state = null; | |
706 | + Map<Integer, Subscription> subMap = subscriptionsByWsSessionId.get(sessionId); | |
707 | + if (subMap != null) { | |
708 | + state = subMap.get(subscriptionId); | |
709 | + } | |
710 | + return Optional.ofNullable(state); | |
711 | + } | |
386 | 712 | } | ... | ... |
application/src/main/java/org/thingsboard/server/service/telemetry/TelemetrySubscriptionService.java
... | ... | @@ -17,7 +17,10 @@ package org.thingsboard.server.service.telemetry; |
17 | 17 | |
18 | 18 | import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; |
19 | 19 | import org.thingsboard.server.common.data.id.EntityId; |
20 | +import org.thingsboard.server.common.msg.cluster.ServerAddress; | |
21 | +import org.thingsboard.server.extensions.api.plugins.PluginContext; | |
20 | 22 | import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionState; |
23 | +import org.thingsboard.server.gen.cluster.ClusterAPIProtos; | |
21 | 24 | |
22 | 25 | /** |
23 | 26 | * Created by ashvayka on 27.03.18. |
... | ... | @@ -30,4 +33,17 @@ public interface TelemetrySubscriptionService extends RuleEngineTelemetryService |
30 | 33 | |
31 | 34 | void removeSubscription(String sessionId, int cmdId); |
32 | 35 | |
36 | + void onNewRemoteSubscription(ServerAddress serverAddress, byte[] data); | |
37 | + | |
38 | + void onRemoteSubscriptionUpdate(ServerAddress serverAddress, byte[] bytes); | |
39 | + | |
40 | + void onRemoteSubscriptionClose(ServerAddress serverAddress, byte[] bytes); | |
41 | + | |
42 | + void onRemoteSessionClose(ServerAddress serverAddress, byte[] bytes); | |
43 | + | |
44 | + void onRemoteAttributesUpdate(ServerAddress serverAddress, byte[] bytes); | |
45 | + | |
46 | + void onRemoteTsUpdate(ServerAddress serverAddress, byte[] bytes); | |
47 | + | |
48 | + void onClusterUpdate(); | |
33 | 49 | } | ... | ... |
... | ... | @@ -25,7 +25,7 @@ service ClusterRpcService { |
25 | 25 | message ClusterMessage { |
26 | 26 | MessageType messageType = 1; |
27 | 27 | MessageMataInfo messageMetaInfo = 2; |
28 | - ServerAddress serverAdresss = 3; | |
28 | + ServerAddress serverAddress = 3; | |
29 | 29 | bytes payload = 4; |
30 | 30 | } |
31 | 31 | |
... | ... | @@ -49,6 +49,75 @@ enum MessageType { |
49 | 49 | CONNECT_RPC_MESSAGE =4; |
50 | 50 | |
51 | 51 | CLUSTER_ACTOR_MESSAGE = 5; |
52 | - CLUSTER_TELEMETRY_MESSAGE = 6; | |
53 | - CLUSTER_DEVICE_RPC_MESSAGE = 7; | |
52 | + // Messages related to TelemetrySubscriptionService | |
53 | + CLUSTER_TELEMETRY_SUBSCRIPTION_CREATE_MESSAGE = 6; | |
54 | + CLUSTER_TELEMETRY_SUBSCRIPTION_UPDATE_MESSAGE = 7; | |
55 | + CLUSTER_TELEMETRY_SUBSCRIPTION_CLOSE_MESSAGE = 8; | |
56 | + CLUSTER_TELEMETRY_SESSION_CLOSE_MESSAGE = 9; | |
57 | + CLUSTER_TELEMETRY_ATTR_UPDATE_MESSAGE = 10; | |
58 | + CLUSTER_TELEMETRY_TS_UPDATE_MESSAGE = 11; | |
54 | 59 | } |
60 | + | |
61 | +// Messages related to CLUSTER_TELEMETRY_MESSAGE | |
62 | +message SubscriptionProto { | |
63 | + string sessionId = 1; | |
64 | + int32 subscriptionId = 2; | |
65 | + string entityType = 3; | |
66 | + string entityId = 4; | |
67 | + string type = 5; | |
68 | + bool allKeys = 6; | |
69 | + repeated SubscriptionKetStateProto keyStates = 7; | |
70 | + string scope = 8; | |
71 | +} | |
72 | + | |
73 | +message SubscriptionUpdateProto { | |
74 | + string sessionId = 1; | |
75 | + int32 subscriptionId = 2; | |
76 | + int32 errorCode = 3; | |
77 | + string errorMsg = 4; | |
78 | + repeated SubscriptionUpdateValueListProto data = 5; | |
79 | +} | |
80 | + | |
81 | +message AttributeUpdateProto { | |
82 | + string entityType = 1; | |
83 | + string entityId = 2; | |
84 | + string scope = 3; | |
85 | + repeated KeyValueProto data = 4; | |
86 | +} | |
87 | + | |
88 | +message TimeseriesUpdateProto { | |
89 | + string entityType = 1; | |
90 | + string entityId = 2; | |
91 | + repeated KeyValueProto data = 4; | |
92 | +} | |
93 | + | |
94 | +message SessionCloseProto { | |
95 | + string sessionId = 1; | |
96 | +} | |
97 | + | |
98 | +message SubscriptionCloseProto { | |
99 | + string sessionId = 1; | |
100 | + int32 subscriptionId = 2; | |
101 | +} | |
102 | + | |
103 | +message SubscriptionKetStateProto { | |
104 | + string key = 1; | |
105 | + int64 ts = 2; | |
106 | +} | |
107 | + | |
108 | +message SubscriptionUpdateValueListProto { | |
109 | + string key = 1; | |
110 | + repeated int64 ts = 2; | |
111 | + repeated string value = 3; | |
112 | +} | |
113 | + | |
114 | +message KeyValueProto { | |
115 | + string key = 1; | |
116 | + int64 ts = 2; | |
117 | + int32 valueType = 3; | |
118 | + string strValue = 4; | |
119 | + int64 longValue = 5; | |
120 | + double doubleValue = 6; | |
121 | + bool boolValue = 7; | |
122 | +} | |
123 | + | ... | ... |
... | ... | @@ -36,9 +36,16 @@ public class JsonConverter { |
36 | 36 | return convertToTelemetry(jsonObject, BasicRequest.DEFAULT_REQUEST_ID); |
37 | 37 | } |
38 | 38 | |
39 | + public static TelemetryUploadRequest convertToTelemetry(JsonElement jsonObject, long ts) throws JsonSyntaxException { | |
40 | + return convertToTelemetry(jsonObject, ts, BasicRequest.DEFAULT_REQUEST_ID); | |
41 | + } | |
42 | + | |
39 | 43 | public static TelemetryUploadRequest convertToTelemetry(JsonElement jsonObject, int requestId) throws JsonSyntaxException { |
44 | + return convertToTelemetry(jsonObject, System.currentTimeMillis(), requestId); | |
45 | + } | |
46 | + | |
47 | + private static TelemetryUploadRequest convertToTelemetry(JsonElement jsonObject, long systemTs, int requestId) throws JsonSyntaxException { | |
40 | 48 | BasicTelemetryUploadRequest request = new BasicTelemetryUploadRequest(requestId); |
41 | - long systemTs = System.currentTimeMillis(); | |
42 | 49 | if (jsonObject.isJsonObject()) { |
43 | 50 | parseObject(request, systemTs, jsonObject); |
44 | 51 | } else if (jsonObject.isJsonArray()) { | ... | ... |
... | ... | @@ -15,6 +15,7 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.dao.alarm; |
17 | 17 | |
18 | +import com.fasterxml.jackson.databind.JsonNode; | |
18 | 19 | import com.google.common.util.concurrent.ListenableFuture; |
19 | 20 | import org.thingsboard.server.common.data.alarm.*; |
20 | 21 | import org.thingsboard.server.common.data.id.EntityId; |
... | ... | @@ -30,7 +31,7 @@ public interface AlarmService { |
30 | 31 | |
31 | 32 | ListenableFuture<Boolean> ackAlarm(AlarmId alarmId, long ackTs); |
32 | 33 | |
33 | - ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long ackTs); | |
34 | + ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, JsonNode details, long ackTs); | |
34 | 35 | |
35 | 36 | ListenableFuture<Alarm> findAlarmByIdAsync(AlarmId alarmId); |
36 | 37 | ... | ... |
... | ... | @@ -16,6 +16,7 @@ |
16 | 16 | package org.thingsboard.server.dao.alarm; |
17 | 17 | |
18 | 18 | |
19 | +import com.fasterxml.jackson.databind.JsonNode; | |
19 | 20 | import com.google.common.base.Function; |
20 | 21 | import com.google.common.util.concurrent.AsyncFunction; |
21 | 22 | import com.google.common.util.concurrent.Futures; |
... | ... | @@ -187,7 +188,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ |
187 | 188 | } |
188 | 189 | |
189 | 190 | @Override |
190 | - public ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long clearTime) { | |
191 | + public ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, JsonNode details, long clearTime) { | |
191 | 192 | return getAndUpdate(alarmId, new Function<Alarm, Boolean>() { |
192 | 193 | @Nullable |
193 | 194 | @Override |
... | ... | @@ -199,6 +200,9 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ |
199 | 200 | AlarmStatus newStatus = oldStatus.isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK; |
200 | 201 | alarm.setStatus(newStatus); |
201 | 202 | alarm.setClearTs(clearTime); |
203 | + if (details != null) { | |
204 | + alarm.setDetails(details); | |
205 | + } | |
202 | 206 | alarmDao.save(alarm); |
203 | 207 | updateRelations(alarm, oldStatus, newStatus); |
204 | 208 | return true; | ... | ... |
... | ... | @@ -82,6 +82,11 @@ public class BaseEventService implements EventService { |
82 | 82 | return new TimePageData<>(events, pageLink); |
83 | 83 | } |
84 | 84 | |
85 | + @Override | |
86 | + public List<Event> findLatestEvents(TenantId tenantId, EntityId entityId, String eventType, int limit) { | |
87 | + return eventDao.findLatestEvents(tenantId.getId(), entityId, eventType, limit); | |
88 | + } | |
89 | + | |
85 | 90 | private DataValidator<Event> eventValidator = |
86 | 91 | new DataValidator<Event>() { |
87 | 92 | @Override | ... | ... |
... | ... | @@ -134,6 +134,21 @@ public class CassandraBaseEventDao extends CassandraAbstractSearchTimeDao<EventE |
134 | 134 | return DaoUtil.convertDataList(entities); |
135 | 135 | } |
136 | 136 | |
137 | + @Override | |
138 | + public List<Event> findLatestEvents(UUID tenantId, EntityId entityId, String eventType, int limit) { | |
139 | + log.trace("Try to find latest events by tenant [{}], entity [{}], type [{}] and limit [{}]", tenantId, entityId, eventType, limit); | |
140 | + Select select = select().from(EVENT_BY_TYPE_AND_ID_VIEW_NAME); | |
141 | + Select.Where query = select.where(); | |
142 | + query.and(eq(ModelConstants.EVENT_TENANT_ID_PROPERTY, tenantId)); | |
143 | + query.and(eq(ModelConstants.EVENT_ENTITY_TYPE_PROPERTY, entityId.getEntityType())); | |
144 | + query.and(eq(ModelConstants.EVENT_ENTITY_ID_PROPERTY, entityId.getId())); | |
145 | + query.and(eq(ModelConstants.EVENT_TYPE_PROPERTY, eventType)); | |
146 | + query.limit(limit); | |
147 | + query.orderBy(QueryBuilder.desc(ModelConstants.EVENT_TYPE_PROPERTY), QueryBuilder.desc(ModelConstants.ID_PROPERTY)); | |
148 | + List<EventEntity> entities = findListByStatement(query); | |
149 | + return DaoUtil.convertDataList(entities); | |
150 | + } | |
151 | + | |
137 | 152 | private Optional<Event> save(EventEntity entity, boolean ifNotExists) { |
138 | 153 | if (entity.getId() == null) { |
139 | 154 | entity.setId(UUIDs.timeBased()); | ... | ... |
... | ... | @@ -76,4 +76,16 @@ public interface EventDao extends Dao<Event> { |
76 | 76 | * @return the event list |
77 | 77 | */ |
78 | 78 | List<Event> findEvents(UUID tenantId, EntityId entityId, String eventType, TimePageLink pageLink); |
79 | + | |
80 | + /** | |
81 | + * Find latest events by tenantId, entityId and eventType. | |
82 | + * | |
83 | + * @param tenantId the tenantId | |
84 | + * @param entityId the entityId | |
85 | + * @param eventType the eventType | |
86 | + * @param limit the limit | |
87 | + * @return the event list | |
88 | + */ | |
89 | + List<Event> findLatestEvents(UUID tenantId, EntityId entityId, String eventType, int limit); | |
90 | + | |
79 | 91 | } | ... | ... |
... | ... | @@ -21,7 +21,9 @@ import org.thingsboard.server.common.data.id.TenantId; |
21 | 21 | import org.thingsboard.server.common.data.page.TimePageData; |
22 | 22 | import org.thingsboard.server.common.data.page.TimePageLink; |
23 | 23 | |
24 | +import java.util.List; | |
24 | 25 | import java.util.Optional; |
26 | +import java.util.UUID; | |
25 | 27 | |
26 | 28 | public interface EventService { |
27 | 29 | |
... | ... | @@ -34,4 +36,7 @@ public interface EventService { |
34 | 36 | TimePageData<Event> findEvents(TenantId tenantId, EntityId entityId, TimePageLink pageLink); |
35 | 37 | |
36 | 38 | TimePageData<Event> findEvents(TenantId tenantId, EntityId entityId, String eventType, TimePageLink pageLink); |
39 | + | |
40 | + List<Event> findLatestEvents(TenantId tenantId, EntityId entityId, String eventType, int limit); | |
41 | + | |
37 | 42 | } | ... | ... |
... | ... | @@ -15,12 +15,18 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.dao.sql.event; |
17 | 17 | |
18 | +import org.springframework.data.domain.Pageable; | |
18 | 19 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; |
20 | +import org.springframework.data.jpa.repository.Query; | |
19 | 21 | import org.springframework.data.repository.CrudRepository; |
22 | +import org.springframework.data.repository.query.Param; | |
20 | 23 | import org.thingsboard.server.common.data.EntityType; |
24 | +import org.thingsboard.server.dao.model.sql.AlarmEntity; | |
21 | 25 | import org.thingsboard.server.dao.model.sql.EventEntity; |
22 | 26 | import org.thingsboard.server.dao.util.SqlDao; |
23 | 27 | |
28 | +import java.util.List; | |
29 | + | |
24 | 30 | /** |
25 | 31 | * Created by Valerii Sosliuk on 5/3/2017. |
26 | 32 | */ |
... | ... | @@ -36,4 +42,14 @@ public interface EventRepository extends CrudRepository<EventEntity, String>, Jp |
36 | 42 | EventEntity findByTenantIdAndEntityTypeAndEntityId(String tenantId, |
37 | 43 | EntityType entityType, |
38 | 44 | String entityId); |
45 | + | |
46 | + @Query("SELECT e FROM EventEntity e WHERE e.tenantId = :tenantId AND e.entityType = :entityType " + | |
47 | + "AND e.entityId = :entityId AND e.eventType = :eventType ORDER BY e.eventType DESC, e.id DESC") | |
48 | + List<EventEntity> findLatestByTenantIdAndEntityTypeAndEntityIdAndEventType( | |
49 | + @Param("tenantId") String tenantId, | |
50 | + @Param("entityType") EntityType entityType, | |
51 | + @Param("entityId") String entityId, | |
52 | + @Param("eventType") String eventType, | |
53 | + Pageable pageable); | |
54 | + | |
39 | 55 | } | ... | ... |
... | ... | @@ -109,6 +109,17 @@ public class JpaBaseEventDao extends JpaAbstractSearchTimeDao<EventEntity, Event |
109 | 109 | return DaoUtil.convertDataList(eventRepository.findAll(where(timeSearchSpec).and(fieldsSpec), pageable).getContent()); |
110 | 110 | } |
111 | 111 | |
112 | + @Override | |
113 | + public List<Event> findLatestEvents(UUID tenantId, EntityId entityId, String eventType, int limit) { | |
114 | + List<EventEntity> latest = eventRepository.findLatestByTenantIdAndEntityTypeAndEntityIdAndEventType( | |
115 | + UUIDConverter.fromTimeUUID(tenantId), | |
116 | + entityId.getEntityType(), | |
117 | + UUIDConverter.fromTimeUUID(entityId.getId()), | |
118 | + eventType, | |
119 | + new PageRequest(0, limit)); | |
120 | + return DaoUtil.convertDataList(latest); | |
121 | + } | |
122 | + | |
112 | 123 | public Optional<Event> save(EventEntity entity, boolean ifNotExists) { |
113 | 124 | log.debug("Save event [{}] ", entity); |
114 | 125 | if (entity.getTenantId() == null) { | ... | ... |
... | ... | @@ -168,7 +168,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { |
168 | 168 | Assert.assertNotNull(alarms.getData()); |
169 | 169 | Assert.assertEquals(0, alarms.getData().size()); |
170 | 170 | |
171 | - alarmService.clearAlarm(created.getId(), System.currentTimeMillis()).get(); | |
171 | + alarmService.clearAlarm(created.getId(), null, System.currentTimeMillis()).get(); | |
172 | 172 | created = alarmService.findAlarmByIdAsync(created.getId()).get(); |
173 | 173 | |
174 | 174 | alarms = alarmService.findAlarms(AlarmQuery.builder() | ... | ... |
... | ... | @@ -31,12 +31,14 @@ public class SubscriptionUpdate { |
31 | 31 | super(); |
32 | 32 | this.subscriptionId = subscriptionId; |
33 | 33 | this.data = new TreeMap<>(); |
34 | - for (TsKvEntry tsEntry : data) { | |
35 | - List<Object> values = this.data.computeIfAbsent(tsEntry.getKey(), k -> new ArrayList<>()); | |
36 | - Object[] value = new Object[2]; | |
37 | - value[0] = tsEntry.getTs(); | |
38 | - value[1] = tsEntry.getValueAsString(); | |
39 | - values.add(value); | |
34 | + if (data != null) { | |
35 | + for (TsKvEntry tsEntry : data) { | |
36 | + List<Object> values = this.data.computeIfAbsent(tsEntry.getKey(), k -> new ArrayList<>()); | |
37 | + Object[] value = new Object[2]; | |
38 | + value[0] = tsEntry.getTs(); | |
39 | + value[1] = tsEntry.getValueAsString(); | |
40 | + values.add(value); | |
41 | + } | |
40 | 42 | } |
41 | 43 | } |
42 | 44 | ... | ... |
netty-mqtt/.gitignore
0 → 100644
netty-mqtt/pom.xml
0 → 100644
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 | +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
19 | + <modelVersion>4.0.0</modelVersion> | |
20 | + <parent> | |
21 | + <groupId>org.thingsboard</groupId> | |
22 | + <version>1.4.1-SNAPSHOT</version> | |
23 | + <artifactId>thingsboard</artifactId> | |
24 | + </parent> | |
25 | + <groupId>org.thingsboard</groupId> | |
26 | + <artifactId>netty-mqtt</artifactId> | |
27 | + <version>1.4.1-SNAPSHOT</version> | |
28 | + <packaging>jar</packaging> | |
29 | + | |
30 | + <name>Netty MQTT Client</name> | |
31 | + <url>https://thingsboard.io</url> | |
32 | + | |
33 | + <properties> | |
34 | + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |
35 | + <main.dir>${basedir}/..</main.dir> | |
36 | + </properties> | |
37 | + | |
38 | + <dependencies> | |
39 | + <dependency> | |
40 | + <groupId>io.netty</groupId> | |
41 | + <artifactId>netty-codec-mqtt</artifactId> | |
42 | + </dependency> | |
43 | + <dependency> | |
44 | + <groupId>io.netty</groupId> | |
45 | + <artifactId>netty-handler</artifactId> | |
46 | + </dependency> | |
47 | + <dependency> | |
48 | + <groupId>com.google.code.findbugs</groupId> | |
49 | + <artifactId>jsr305</artifactId> | |
50 | + <version>3.0.1</version> | |
51 | + <optional>true</optional> | |
52 | + </dependency> | |
53 | + <dependency> | |
54 | + <groupId>com.google.guava</groupId> | |
55 | + <artifactId>guava</artifactId> | |
56 | + </dependency> | |
57 | + </dependencies> | |
58 | + | |
59 | + <distributionManagement> | |
60 | + <repository> | |
61 | + <id>jk-5-maven</id> | |
62 | + <name>jk-5's maven server</name> | |
63 | + <url>sftp://10.2.1.2/opt/maven</url> | |
64 | + </repository> | |
65 | + </distributionManagement> | |
66 | + | |
67 | + <build> | |
68 | + <extensions> | |
69 | + <extension> | |
70 | + <groupId>org.apache.maven.wagon</groupId> | |
71 | + <artifactId>wagon-ssh</artifactId> | |
72 | + <version>2.6</version> | |
73 | + </extension> | |
74 | + </extensions> | |
75 | + <plugins> | |
76 | + <plugin> | |
77 | + <groupId>org.apache.maven.plugins</groupId> | |
78 | + <artifactId>maven-compiler-plugin</artifactId> | |
79 | + <version>3.1</version> | |
80 | + <configuration> | |
81 | + <source>1.8</source> | |
82 | + <target>1.8</target> | |
83 | + </configuration> | |
84 | + </plugin> | |
85 | + <plugin> | |
86 | + <groupId>org.apache.maven.plugins</groupId> | |
87 | + <artifactId>maven-jar-plugin</artifactId> | |
88 | + <version>2.4</version> | |
89 | + <configuration> | |
90 | + <archive> | |
91 | + <manifest> | |
92 | + <addDefaultImplementationEntries>true</addDefaultImplementationEntries> | |
93 | + </manifest> | |
94 | + </archive> | |
95 | + </configuration> | |
96 | + </plugin> | |
97 | + </plugins> | |
98 | + </build> | |
99 | +</project> | |
\ No newline at end of file | ... | ... |
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 | +package org.thingsboard.mqtt; | |
17 | + | |
18 | +/** | |
19 | + * Created by Valerii Sosliuk on 12/26/2017. | |
20 | + */ | |
21 | +public class ChannelClosedException extends RuntimeException { | |
22 | + | |
23 | + private static final long serialVersionUID = 6266638352424706909L; | |
24 | + | |
25 | + public ChannelClosedException() { | |
26 | + } | |
27 | + | |
28 | + public ChannelClosedException(String message) { | |
29 | + super(message); | |
30 | + } | |
31 | + | |
32 | + public ChannelClosedException(String message, Throwable cause) { | |
33 | + super(message, cause); | |
34 | + } | |
35 | + | |
36 | + public ChannelClosedException(Throwable cause) { | |
37 | + super(cause); | |
38 | + } | |
39 | + | |
40 | + public ChannelClosedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { | |
41 | + super(message, cause, enableSuppression, writableStackTrace); | |
42 | + } | |
43 | +} | ... | ... |
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 | +package org.thingsboard.mqtt; | |
17 | + | |
18 | +import com.google.common.collect.ImmutableSet; | |
19 | +import io.netty.channel.Channel; | |
20 | +import io.netty.channel.ChannelHandlerContext; | |
21 | +import io.netty.channel.SimpleChannelInboundHandler; | |
22 | +import io.netty.handler.codec.mqtt.*; | |
23 | +import io.netty.util.CharsetUtil; | |
24 | +import io.netty.util.concurrent.Promise; | |
25 | + | |
26 | +final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage> { | |
27 | + | |
28 | + private final MqttClientImpl client; | |
29 | + private final Promise<MqttConnectResult> connectFuture; | |
30 | + | |
31 | + MqttChannelHandler(MqttClientImpl client, Promise<MqttConnectResult> connectFuture) { | |
32 | + this.client = client; | |
33 | + this.connectFuture = connectFuture; | |
34 | + } | |
35 | + | |
36 | + @Override | |
37 | + protected void channelRead0(ChannelHandlerContext ctx, MqttMessage msg) throws Exception { | |
38 | + switch (msg.fixedHeader().messageType()) { | |
39 | + case CONNACK: | |
40 | + handleConack(ctx.channel(), (MqttConnAckMessage) msg); | |
41 | + break; | |
42 | + case SUBACK: | |
43 | + handleSubAck((MqttSubAckMessage) msg); | |
44 | + break; | |
45 | + case PUBLISH: | |
46 | + handlePublish(ctx.channel(), (MqttPublishMessage) msg); | |
47 | + break; | |
48 | + case UNSUBACK: | |
49 | + handleUnsuback((MqttUnsubAckMessage) msg); | |
50 | + break; | |
51 | + case PUBACK: | |
52 | + handlePuback((MqttPubAckMessage) msg); | |
53 | + break; | |
54 | + case PUBREC: | |
55 | + handlePubrec(ctx.channel(), msg); | |
56 | + break; | |
57 | + case PUBREL: | |
58 | + handlePubrel(ctx.channel(), msg); | |
59 | + break; | |
60 | + case PUBCOMP: | |
61 | + handlePubcomp(msg); | |
62 | + break; | |
63 | + } | |
64 | + } | |
65 | + | |
66 | + @Override | |
67 | + public void channelActive(ChannelHandlerContext ctx) throws Exception { | |
68 | + super.channelActive(ctx); | |
69 | + | |
70 | + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.CONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0); | |
71 | + MqttConnectVariableHeader variableHeader = new MqttConnectVariableHeader( | |
72 | + this.client.getClientConfig().getProtocolVersion().protocolName(), // Protocol Name | |
73 | + this.client.getClientConfig().getProtocolVersion().protocolLevel(), // Protocol Level | |
74 | + this.client.getClientConfig().getUsername() != null, // Has Username | |
75 | + this.client.getClientConfig().getPassword() != null, // Has Password | |
76 | + this.client.getClientConfig().getLastWill() != null // Will Retain | |
77 | + && this.client.getClientConfig().getLastWill().isRetain(), | |
78 | + this.client.getClientConfig().getLastWill() != null // Will QOS | |
79 | + ? this.client.getClientConfig().getLastWill().getQos().value() | |
80 | + : 0, | |
81 | + this.client.getClientConfig().getLastWill() != null, // Has Will | |
82 | + this.client.getClientConfig().isCleanSession(), // Clean Session | |
83 | + this.client.getClientConfig().getTimeoutSeconds() // Timeout | |
84 | + ); | |
85 | + MqttConnectPayload payload = new MqttConnectPayload( | |
86 | + this.client.getClientConfig().getClientId(), | |
87 | + this.client.getClientConfig().getLastWill() != null ? this.client.getClientConfig().getLastWill().getTopic() : null, | |
88 | + this.client.getClientConfig().getLastWill() != null ? this.client.getClientConfig().getLastWill().getMessage().getBytes(CharsetUtil.UTF_8) : null, | |
89 | + this.client.getClientConfig().getUsername(), | |
90 | + this.client.getClientConfig().getPassword() != null ? this.client.getClientConfig().getPassword().getBytes(CharsetUtil.UTF_8) : null | |
91 | + ); | |
92 | + ctx.channel().writeAndFlush(new MqttConnectMessage(fixedHeader, variableHeader, payload)); | |
93 | + } | |
94 | + | |
95 | + @Override | |
96 | + public void channelInactive(ChannelHandlerContext ctx) throws Exception { | |
97 | + super.channelInactive(ctx); | |
98 | + } | |
99 | + | |
100 | + private void invokeHandlersForIncomingPublish(MqttPublishMessage message) { | |
101 | + for (MqttSubscribtion subscribtion : ImmutableSet.copyOf(this.client.getSubscriptions().values())) { | |
102 | + if (subscribtion.matches(message.variableHeader().topicName())) { | |
103 | + if (subscribtion.isOnce() && subscribtion.isCalled()) { | |
104 | + continue; | |
105 | + } | |
106 | + message.payload().markReaderIndex(); | |
107 | + subscribtion.setCalled(true); | |
108 | + subscribtion.getHandler().onMessage(message.variableHeader().topicName(), message.payload()); | |
109 | + if (subscribtion.isOnce()) { | |
110 | + this.client.off(subscribtion.getTopic(), subscribtion.getHandler()); | |
111 | + } | |
112 | + message.payload().resetReaderIndex(); | |
113 | + } | |
114 | + } | |
115 | + /*Set<MqttSubscribtion> subscribtions = ImmutableSet.copyOf(this.client.getSubscriptions().get(message.variableHeader().topicName())); | |
116 | + for (MqttSubscribtion subscribtion : subscribtions) { | |
117 | + if(subscribtion.isOnce() && subscribtion.isCalled()){ | |
118 | + continue; | |
119 | + } | |
120 | + message.payload().markReaderIndex(); | |
121 | + subscribtion.setCalled(true); | |
122 | + subscribtion.getHandler().onMessage(message.variableHeader().topicName(), message.payload()); | |
123 | + if(subscribtion.isOnce()){ | |
124 | + this.client.off(subscribtion.getTopic(), subscribtion.getHandler()); | |
125 | + } | |
126 | + message.payload().resetReaderIndex(); | |
127 | + }*/ | |
128 | + message.payload().release(); | |
129 | + } | |
130 | + | |
131 | + private void handleConack(Channel channel, MqttConnAckMessage message) { | |
132 | + switch (message.variableHeader().connectReturnCode()) { | |
133 | + case CONNECTION_ACCEPTED: | |
134 | + this.connectFuture.setSuccess(new MqttConnectResult(true, MqttConnectReturnCode.CONNECTION_ACCEPTED, channel.closeFuture())); | |
135 | + | |
136 | + this.client.getPendingSubscribtions().entrySet().stream().filter((e) -> !e.getValue().isSent()).forEach((e) -> { | |
137 | + channel.write(e.getValue().getSubscribeMessage()); | |
138 | + e.getValue().setSent(true); | |
139 | + }); | |
140 | + | |
141 | + this.client.getPendingPublishes().forEach((id, publish) -> { | |
142 | + if (publish.isSent()) return; | |
143 | + channel.write(publish.getMessage()); | |
144 | + publish.setSent(true); | |
145 | + if (publish.getQos() == MqttQoS.AT_MOST_ONCE) { | |
146 | + publish.getFuture().setSuccess(null); //We don't get an ACK for QOS 0 | |
147 | + this.client.getPendingPublishes().remove(publish.getMessageId()); | |
148 | + } | |
149 | + }); | |
150 | + channel.flush(); | |
151 | + break; | |
152 | + | |
153 | + case CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD: | |
154 | + case CONNECTION_REFUSED_IDENTIFIER_REJECTED: | |
155 | + case CONNECTION_REFUSED_NOT_AUTHORIZED: | |
156 | + case CONNECTION_REFUSED_SERVER_UNAVAILABLE: | |
157 | + case CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION: | |
158 | + this.connectFuture.setSuccess(new MqttConnectResult(false, message.variableHeader().connectReturnCode(), channel.closeFuture())); | |
159 | + channel.close(); | |
160 | + // Don't start reconnect logic here | |
161 | + break; | |
162 | + } | |
163 | + } | |
164 | + | |
165 | + private void handleSubAck(MqttSubAckMessage message) { | |
166 | + MqttPendingSubscribtion pendingSubscription = this.client.getPendingSubscribtions().remove(message.variableHeader().messageId()); | |
167 | + if (pendingSubscription == null) { | |
168 | + return; | |
169 | + } | |
170 | + pendingSubscription.onSubackReceived(); | |
171 | + for (MqttPendingSubscribtion.MqttPendingHandler handler : pendingSubscription.getHandlers()) { | |
172 | + MqttSubscribtion subscribtion = new MqttSubscribtion(pendingSubscription.getTopic(), handler.getHandler(), handler.isOnce()); | |
173 | + this.client.getSubscriptions().put(pendingSubscription.getTopic(), subscribtion); | |
174 | + this.client.getHandlerToSubscribtion().put(handler.getHandler(), subscribtion); | |
175 | + } | |
176 | + this.client.getPendingSubscribeTopics().remove(pendingSubscription.getTopic()); | |
177 | + | |
178 | + this.client.getServerSubscribtions().add(pendingSubscription.getTopic()); | |
179 | + | |
180 | + if (!pendingSubscription.getFuture().isDone()) { | |
181 | + pendingSubscription.getFuture().setSuccess(null); | |
182 | + } | |
183 | + } | |
184 | + | |
185 | + private void handlePublish(Channel channel, MqttPublishMessage message) { | |
186 | + switch (message.fixedHeader().qosLevel()) { | |
187 | + case AT_MOST_ONCE: | |
188 | + invokeHandlersForIncomingPublish(message); | |
189 | + break; | |
190 | + | |
191 | + case AT_LEAST_ONCE: | |
192 | + invokeHandlersForIncomingPublish(message); | |
193 | + if (message.variableHeader().messageId() != -1) { | |
194 | + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0); | |
195 | + MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(message.variableHeader().messageId()); | |
196 | + channel.writeAndFlush(new MqttPubAckMessage(fixedHeader, variableHeader)); | |
197 | + } | |
198 | + break; | |
199 | + | |
200 | + case EXACTLY_ONCE: | |
201 | + if (message.variableHeader().messageId() != -1) { | |
202 | + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREC, false, MqttQoS.AT_MOST_ONCE, false, 0); | |
203 | + MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(message.variableHeader().messageId()); | |
204 | + MqttMessage pubrecMessage = new MqttMessage(fixedHeader, variableHeader); | |
205 | + | |
206 | + MqttIncomingQos2Publish incomingQos2Publish = new MqttIncomingQos2Publish(message, pubrecMessage); | |
207 | + this.client.getQos2PendingIncomingPublishes().put(message.variableHeader().messageId(), incomingQos2Publish); | |
208 | + message.payload().retain(); | |
209 | + incomingQos2Publish.startPubrecRetransmitTimer(this.client.getEventLoop().next(), this.client::sendAndFlushPacket); | |
210 | + | |
211 | + channel.writeAndFlush(pubrecMessage); | |
212 | + } | |
213 | + break; | |
214 | + } | |
215 | + } | |
216 | + | |
217 | + private void handleUnsuback(MqttUnsubAckMessage message) { | |
218 | + MqttPendingUnsubscribtion unsubscribtion = this.client.getPendingServerUnsubscribes().get(message.variableHeader().messageId()); | |
219 | + if (unsubscribtion == null) { | |
220 | + return; | |
221 | + } | |
222 | + unsubscribtion.onUnsubackReceived(); | |
223 | + this.client.getServerSubscribtions().remove(unsubscribtion.getTopic()); | |
224 | + unsubscribtion.getFuture().setSuccess(null); | |
225 | + this.client.getPendingServerUnsubscribes().remove(message.variableHeader().messageId()); | |
226 | + } | |
227 | + | |
228 | + private void handlePuback(MqttPubAckMessage message) { | |
229 | + MqttPendingPublish pendingPublish = this.client.getPendingPublishes().get(message.variableHeader().messageId()); | |
230 | + pendingPublish.getFuture().setSuccess(null); | |
231 | + pendingPublish.onPubackReceived(); | |
232 | + this.client.getPendingPublishes().remove(message.variableHeader().messageId()); | |
233 | + pendingPublish.getPayload().release(); | |
234 | + } | |
235 | + | |
236 | + private void handlePubrec(Channel channel, MqttMessage message) { | |
237 | + MqttPendingPublish pendingPublish = this.client.getPendingPublishes().get(((MqttMessageIdVariableHeader) message.variableHeader()).messageId()); | |
238 | + pendingPublish.onPubackReceived(); | |
239 | + | |
240 | + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREL, false, MqttQoS.AT_LEAST_ONCE, false, 0); | |
241 | + MqttMessageIdVariableHeader variableHeader = (MqttMessageIdVariableHeader) message.variableHeader(); | |
242 | + MqttMessage pubrelMessage = new MqttMessage(fixedHeader, variableHeader); | |
243 | + channel.writeAndFlush(pubrelMessage); | |
244 | + | |
245 | + pendingPublish.setPubrelMessage(pubrelMessage); | |
246 | + pendingPublish.startPubrelRetransmissionTimer(this.client.getEventLoop().next(), this.client::sendAndFlushPacket); | |
247 | + } | |
248 | + | |
249 | + private void handlePubrel(Channel channel, MqttMessage message) { | |
250 | + if (this.client.getQos2PendingIncomingPublishes().containsKey(((MqttMessageIdVariableHeader) message.variableHeader()).messageId())) { | |
251 | + MqttIncomingQos2Publish incomingQos2Publish = this.client.getQos2PendingIncomingPublishes().get(((MqttMessageIdVariableHeader) message.variableHeader()).messageId()); | |
252 | + this.invokeHandlersForIncomingPublish(incomingQos2Publish.getIncomingPublish()); | |
253 | + incomingQos2Publish.onPubrelReceived(); | |
254 | + this.client.getQos2PendingIncomingPublishes().remove(incomingQos2Publish.getIncomingPublish().variableHeader().messageId()); | |
255 | + } | |
256 | + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBCOMP, false, MqttQoS.AT_MOST_ONCE, false, 0); | |
257 | + MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(((MqttMessageIdVariableHeader) message.variableHeader()).messageId()); | |
258 | + channel.writeAndFlush(new MqttMessage(fixedHeader, variableHeader)); | |
259 | + } | |
260 | + | |
261 | + private void handlePubcomp(MqttMessage message) { | |
262 | + MqttMessageIdVariableHeader variableHeader = (MqttMessageIdVariableHeader) message.variableHeader(); | |
263 | + MqttPendingPublish pendingPublish = this.client.getPendingPublishes().get(variableHeader.messageId()); | |
264 | + pendingPublish.getFuture().setSuccess(null); | |
265 | + this.client.getPendingPublishes().remove(variableHeader.messageId()); | |
266 | + pendingPublish.getPayload().release(); | |
267 | + pendingPublish.onPubcompReceived(); | |
268 | + } | |
269 | +} | ... | ... |
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 | +package org.thingsboard.mqtt; | |
17 | + | |
18 | +import io.netty.buffer.ByteBuf; | |
19 | +import io.netty.channel.Channel; | |
20 | +import io.netty.channel.EventLoopGroup; | |
21 | +import io.netty.channel.nio.NioEventLoopGroup; | |
22 | +import io.netty.handler.codec.mqtt.MqttQoS; | |
23 | +import io.netty.util.concurrent.Future; | |
24 | + | |
25 | +public interface MqttClient { | |
26 | + | |
27 | + /** | |
28 | + * Connect to the specified hostname/ip. By default uses port 1883. | |
29 | + * If you want to change the port number, see {@link #connect(String, int)} | |
30 | + * | |
31 | + * @param host The ip address or host to connect to | |
32 | + * @return A future which will be completed when the connection is opened and we received an CONNACK | |
33 | + */ | |
34 | + Future<MqttConnectResult> connect(String host); | |
35 | + | |
36 | + /** | |
37 | + * Connect to the specified hostname/ip using the specified port | |
38 | + * | |
39 | + * @param host The ip address or host to connect to | |
40 | + * @param port The tcp port to connect to | |
41 | + * @return A future which will be completed when the connection is opened and we received an CONNACK | |
42 | + */ | |
43 | + Future<MqttConnectResult> connect(String host, int port); | |
44 | + | |
45 | + /** | |
46 | + * | |
47 | + * @return boolean value indicating if channel is active | |
48 | + */ | |
49 | + boolean isConnected(); | |
50 | + | |
51 | + /** | |
52 | + * Attempt reconnect to the host that was attempted with {@link #connect(String, int)} method before | |
53 | + * | |
54 | + * @return A future which will be completed when the connection is opened and we received an CONNACK | |
55 | + * @throws IllegalStateException if no previous {@link #connect(String, int)} calls were attempted | |
56 | + */ | |
57 | + Future<MqttConnectResult> reconnect(); | |
58 | + | |
59 | + /** | |
60 | + * Retrieve the netty {@link EventLoopGroup} we are using | |
61 | + * @return The netty {@link EventLoopGroup} we use for the connection | |
62 | + */ | |
63 | + EventLoopGroup getEventLoop(); | |
64 | + | |
65 | + /** | |
66 | + * By default we use the netty {@link NioEventLoopGroup}. | |
67 | + * If you change the EventLoopGroup to another type, make sure to change the {@link Channel} class using {@link MqttClientConfig#setChannelClass(Class)} | |
68 | + * If you want to force the MqttClient to use another {@link EventLoopGroup}, call this function before calling {@link #connect(String, int)} | |
69 | + * | |
70 | + * @param eventLoop The new eventloop to use | |
71 | + */ | |
72 | + void setEventLoop(EventLoopGroup eventLoop); | |
73 | + | |
74 | + /** | |
75 | + * Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler | |
76 | + * | |
77 | + * @param topic The topic filter to subscribe to | |
78 | + * @param handler The handler to invoke when we receive a message | |
79 | + * @return A future which will be completed when the server acknowledges our subscribe request | |
80 | + */ | |
81 | + Future<Void> on(String topic, MqttHandler handler); | |
82 | + | |
83 | + /** | |
84 | + * Subscribe on the given topic, with the given qos. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler | |
85 | + * | |
86 | + * @param topic The topic filter to subscribe to | |
87 | + * @param handler The handler to invoke when we receive a message | |
88 | + * @param qos The qos to request to the server | |
89 | + * @return A future which will be completed when the server acknowledges our subscribe request | |
90 | + */ | |
91 | + Future<Void> on(String topic, MqttHandler handler, MqttQoS qos); | |
92 | + | |
93 | + /** | |
94 | + * Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler | |
95 | + * This subscribtion is only once. If the MqttClient has received 1 message, the subscribtion will be removed | |
96 | + * | |
97 | + * @param topic The topic filter to subscribe to | |
98 | + * @param handler The handler to invoke when we receive a message | |
99 | + * @return A future which will be completed when the server acknowledges our subscribe request | |
100 | + */ | |
101 | + Future<Void> once(String topic, MqttHandler handler); | |
102 | + | |
103 | + /** | |
104 | + * Subscribe on the given topic, with the given qos. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler | |
105 | + * This subscribtion is only once. If the MqttClient has received 1 message, the subscribtion will be removed | |
106 | + * | |
107 | + * @param topic The topic filter to subscribe to | |
108 | + * @param handler The handler to invoke when we receive a message | |
109 | + * @param qos The qos to request to the server | |
110 | + * @return A future which will be completed when the server acknowledges our subscribe request | |
111 | + */ | |
112 | + Future<Void> once(String topic, MqttHandler handler, MqttQoS qos); | |
113 | + | |
114 | + /** | |
115 | + * Remove the subscribtion for the given topic and handler | |
116 | + * If you want to unsubscribe from all handlers known for this topic, use {@link #off(String)} | |
117 | + * | |
118 | + * @param topic The topic to unsubscribe for | |
119 | + * @param handler The handler to unsubscribe | |
120 | + * @return A future which will be completed when the server acknowledges our unsubscribe request | |
121 | + */ | |
122 | + Future<Void> off(String topic, MqttHandler handler); | |
123 | + | |
124 | + /** | |
125 | + * Remove all subscribtions for the given topic. | |
126 | + * If you want to specify which handler to unsubscribe, use {@link #off(String, MqttHandler)} | |
127 | + * | |
128 | + * @param topic The topic to unsubscribe for | |
129 | + * @return A future which will be completed when the server acknowledges our unsubscribe request | |
130 | + */ | |
131 | + Future<Void> off(String topic); | |
132 | + | |
133 | + /** | |
134 | + * Publish a message to the given payload | |
135 | + * @param topic The topic to publish to | |
136 | + * @param payload The payload to send | |
137 | + * @return A future which will be completed when the message is sent out of the MqttClient | |
138 | + */ | |
139 | + Future<Void> publish(String topic, ByteBuf payload); | |
140 | + | |
141 | + /** | |
142 | + * Publish a message to the given payload, using the given qos | |
143 | + * @param topic The topic to publish to | |
144 | + * @param payload The payload to send | |
145 | + * @param qos The qos to use while publishing | |
146 | + * @return A future which will be completed when the message is delivered to the server | |
147 | + */ | |
148 | + Future<Void> publish(String topic, ByteBuf payload, MqttQoS qos); | |
149 | + | |
150 | + /** | |
151 | + * Publish a message to the given payload, using optional retain | |
152 | + * @param topic The topic to publish to | |
153 | + * @param payload The payload to send | |
154 | + * @param retain true if you want to retain the message on the server, false otherwise | |
155 | + * @return A future which will be completed when the message is sent out of the MqttClient | |
156 | + */ | |
157 | + Future<Void> publish(String topic, ByteBuf payload, boolean retain); | |
158 | + | |
159 | + /** | |
160 | + * Publish a message to the given payload, using the given qos and optional retain | |
161 | + * @param topic The topic to publish to | |
162 | + * @param payload The payload to send | |
163 | + * @param qos The qos to use while publishing | |
164 | + * @param retain true if you want to retain the message on the server, false otherwise | |
165 | + * @return A future which will be completed when the message is delivered to the server | |
166 | + */ | |
167 | + Future<Void> publish(String topic, ByteBuf payload, MqttQoS qos, boolean retain); | |
168 | + | |
169 | + /** | |
170 | + * Retrieve the MqttClient configuration | |
171 | + * @return The {@link MqttClientConfig} instance we use | |
172 | + */ | |
173 | + MqttClientConfig getClientConfig(); | |
174 | + | |
175 | + /** | |
176 | + * Construct the MqttClientImpl with default config | |
177 | + */ | |
178 | + static MqttClient create(){ | |
179 | + return new MqttClientImpl(); | |
180 | + } | |
181 | + | |
182 | + /** | |
183 | + * Construct the MqttClientImpl with additional config. | |
184 | + * This config can also be changed using the {@link #getClientConfig()} function | |
185 | + * | |
186 | + * @param config The config object to use while looking for settings | |
187 | + */ | |
188 | + static MqttClient create(MqttClientConfig config){ | |
189 | + return new MqttClientImpl(config); | |
190 | + } | |
191 | + | |
192 | + | |
193 | + /** | |
194 | + * Send disconnect and close channel | |
195 | + * | |
196 | + */ | |
197 | + void disconnect(); | |
198 | + | |
199 | + /** | |
200 | + * Sets the {@see #MqttClientCallback} object for this MqttClient | |
201 | + * @param callback The callback to be set | |
202 | + */ | |
203 | + void setCallback(MqttClientCallback callback); | |
204 | + | |
205 | +} | ... | ... |
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 | +package org.thingsboard.mqtt; | |
17 | + | |
18 | +/** | |
19 | + * Created by Valerii Sosliuk on 12/30/2017. | |
20 | + */ | |
21 | +public interface MqttClientCallback { | |
22 | + | |
23 | + /** | |
24 | + * This method is called when the connection to the server is lost. | |
25 | + * | |
26 | + * @param cause the reason behind the loss of connection. | |
27 | + */ | |
28 | + public void connectionLost(Throwable cause); | |
29 | +} | ... | ... |
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 | +package org.thingsboard.mqtt; | |
17 | + | |
18 | +import io.netty.channel.Channel; | |
19 | +import io.netty.channel.socket.nio.NioSocketChannel; | |
20 | +import io.netty.handler.codec.mqtt.MqttVersion; | |
21 | +import io.netty.handler.ssl.SslContext; | |
22 | + | |
23 | +import javax.annotation.Nonnull; | |
24 | +import javax.annotation.Nullable; | |
25 | +import java.util.Random; | |
26 | + | |
27 | +@SuppressWarnings({"WeakerAccess", "unused"}) | |
28 | +public final class MqttClientConfig { | |
29 | + | |
30 | + private final SslContext sslContext; | |
31 | + private final String randomClientId; | |
32 | + | |
33 | + private String clientId; | |
34 | + private int timeoutSeconds = 60; | |
35 | + private MqttVersion protocolVersion = MqttVersion.MQTT_3_1; | |
36 | + @Nullable private String username = null; | |
37 | + @Nullable private String password = null; | |
38 | + private boolean cleanSession = true; | |
39 | + @Nullable private MqttLastWill lastWill; | |
40 | + private Class<? extends Channel> channelClass = NioSocketChannel.class; | |
41 | + | |
42 | + private boolean reconnect = true; | |
43 | + | |
44 | + public MqttClientConfig() { | |
45 | + this(null); | |
46 | + } | |
47 | + | |
48 | + public MqttClientConfig(SslContext sslContext) { | |
49 | + this.sslContext = sslContext; | |
50 | + Random random = new Random(); | |
51 | + String id = "netty-mqtt/"; | |
52 | + String[] options = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split(""); | |
53 | + for(int i = 0; i < 8; i++){ | |
54 | + id += options[random.nextInt(options.length)]; | |
55 | + } | |
56 | + this.clientId = id; | |
57 | + this.randomClientId = id; | |
58 | + } | |
59 | + | |
60 | + @Nonnull | |
61 | + public String getClientId() { | |
62 | + return clientId; | |
63 | + } | |
64 | + | |
65 | + public void setClientId(@Nullable String clientId) { | |
66 | + if(clientId == null){ | |
67 | + this.clientId = randomClientId; | |
68 | + }else{ | |
69 | + this.clientId = clientId; | |
70 | + } | |
71 | + } | |
72 | + | |
73 | + public int getTimeoutSeconds() { | |
74 | + return timeoutSeconds; | |
75 | + } | |
76 | + | |
77 | + public void setTimeoutSeconds(int timeoutSeconds) { | |
78 | + if(timeoutSeconds != -1 && timeoutSeconds <= 0){ | |
79 | + throw new IllegalArgumentException("timeoutSeconds must be > 0 or -1"); | |
80 | + } | |
81 | + this.timeoutSeconds = timeoutSeconds; | |
82 | + } | |
83 | + | |
84 | + public MqttVersion getProtocolVersion() { | |
85 | + return protocolVersion; | |
86 | + } | |
87 | + | |
88 | + public void setProtocolVersion(MqttVersion protocolVersion) { | |
89 | + if(protocolVersion == null){ | |
90 | + throw new NullPointerException("protocolVersion"); | |
91 | + } | |
92 | + this.protocolVersion = protocolVersion; | |
93 | + } | |
94 | + | |
95 | + @Nullable | |
96 | + public String getUsername() { | |
97 | + return username; | |
98 | + } | |
99 | + | |
100 | + public void setUsername(@Nullable String username) { | |
101 | + this.username = username; | |
102 | + } | |
103 | + | |
104 | + @Nullable | |
105 | + public String getPassword() { | |
106 | + return password; | |
107 | + } | |
108 | + | |
109 | + public void setPassword(@Nullable String password) { | |
110 | + this.password = password; | |
111 | + } | |
112 | + | |
113 | + public boolean isCleanSession() { | |
114 | + return cleanSession; | |
115 | + } | |
116 | + | |
117 | + public void setCleanSession(boolean cleanSession) { | |
118 | + this.cleanSession = cleanSession; | |
119 | + } | |
120 | + | |
121 | + @Nullable | |
122 | + public MqttLastWill getLastWill() { | |
123 | + return lastWill; | |
124 | + } | |
125 | + | |
126 | + public void setLastWill(@Nullable MqttLastWill lastWill) { | |
127 | + this.lastWill = lastWill; | |
128 | + } | |
129 | + | |
130 | + public Class<? extends Channel> getChannelClass() { | |
131 | + return channelClass; | |
132 | + } | |
133 | + | |
134 | + public void setChannelClass(Class<? extends Channel> channelClass) { | |
135 | + this.channelClass = channelClass; | |
136 | + } | |
137 | + | |
138 | + public SslContext getSslContext() { | |
139 | + return sslContext; | |
140 | + } | |
141 | + | |
142 | + public boolean isReconnect() { | |
143 | + return reconnect; | |
144 | + } | |
145 | + | |
146 | + public void setReconnect(boolean reconnect) { | |
147 | + this.reconnect = reconnect; | |
148 | + } | |
149 | +} | ... | ... |
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 | +package org.thingsboard.mqtt; | |
17 | + | |
18 | +import com.google.common.collect.HashMultimap; | |
19 | +import com.google.common.collect.ImmutableSet; | |
20 | +import io.netty.bootstrap.Bootstrap; | |
21 | +import io.netty.buffer.ByteBuf; | |
22 | +import io.netty.channel.*; | |
23 | +import io.netty.channel.nio.NioEventLoopGroup; | |
24 | +import io.netty.channel.socket.SocketChannel; | |
25 | +import io.netty.handler.codec.mqtt.*; | |
26 | +import io.netty.handler.ssl.SslContext; | |
27 | +import io.netty.handler.timeout.IdleStateHandler; | |
28 | +import io.netty.util.collection.IntObjectHashMap; | |
29 | +import io.netty.util.concurrent.DefaultPromise; | |
30 | +import io.netty.util.concurrent.Future; | |
31 | +import io.netty.util.concurrent.Promise; | |
32 | + | |
33 | +import java.util.*; | |
34 | +import java.util.concurrent.TimeUnit; | |
35 | +import java.util.concurrent.atomic.AtomicInteger; | |
36 | + | |
37 | +/** | |
38 | + * Represents an MqttClientImpl connected to a single MQTT server. Will try to keep the connection going at all times | |
39 | + */ | |
40 | +@SuppressWarnings({"WeakerAccess", "unused"}) | |
41 | +final class MqttClientImpl implements MqttClient { | |
42 | + | |
43 | + private final Set<String> serverSubscribtions = new HashSet<>(); | |
44 | + private final IntObjectHashMap<MqttPendingUnsubscribtion> pendingServerUnsubscribes = new IntObjectHashMap<>(); | |
45 | + private final IntObjectHashMap<MqttIncomingQos2Publish> qos2PendingIncomingPublishes = new IntObjectHashMap<>(); | |
46 | + private final IntObjectHashMap<MqttPendingPublish> pendingPublishes = new IntObjectHashMap<>(); | |
47 | + private final HashMultimap<String, MqttSubscribtion> subscriptions = HashMultimap.create(); | |
48 | + private final IntObjectHashMap<MqttPendingSubscribtion> pendingSubscribtions = new IntObjectHashMap<>(); | |
49 | + private final Set<String> pendingSubscribeTopics = new HashSet<>(); | |
50 | + private final HashMultimap<MqttHandler, MqttSubscribtion> handlerToSubscribtion = HashMultimap.create(); | |
51 | + private final AtomicInteger nextMessageId = new AtomicInteger(1); | |
52 | + | |
53 | + private final MqttClientConfig clientConfig; | |
54 | + | |
55 | + private EventLoopGroup eventLoop; | |
56 | + | |
57 | + private Channel channel; | |
58 | + | |
59 | + private boolean disconnected = false; | |
60 | + private String host; | |
61 | + private int port; | |
62 | + private MqttClientCallback callback; | |
63 | + | |
64 | + | |
65 | + /** | |
66 | + * Construct the MqttClientImpl with default config | |
67 | + */ | |
68 | + public MqttClientImpl() { | |
69 | + this.clientConfig = new MqttClientConfig(); | |
70 | + } | |
71 | + | |
72 | + /** | |
73 | + * Construct the MqttClientImpl with additional config. | |
74 | + * This config can also be changed using the {@link #getClientConfig()} function | |
75 | + * | |
76 | + * @param clientConfig The config object to use while looking for settings | |
77 | + */ | |
78 | + public MqttClientImpl(MqttClientConfig clientConfig) { | |
79 | + this.clientConfig = clientConfig; | |
80 | + } | |
81 | + | |
82 | + /** | |
83 | + * Connect to the specified hostname/ip. By default uses port 1883. | |
84 | + * If you want to change the port number, see {@link #connect(String, int)} | |
85 | + * | |
86 | + * @param host The ip address or host to connect to | |
87 | + * @return A future which will be completed when the connection is opened and we received an CONNACK | |
88 | + */ | |
89 | + @Override | |
90 | + public Future<MqttConnectResult> connect(String host) { | |
91 | + return connect(host, 1883); | |
92 | + } | |
93 | + | |
94 | + /** | |
95 | + * Connect to the specified hostname/ip using the specified port | |
96 | + * | |
97 | + * @param host The ip address or host to connect to | |
98 | + * @param port The tcp port to connect to | |
99 | + * @return A future which will be completed when the connection is opened and we received an CONNACK | |
100 | + */ | |
101 | + @Override | |
102 | + public Future<MqttConnectResult> connect(String host, int port) { | |
103 | + if (this.eventLoop == null) { | |
104 | + this.eventLoop = new NioEventLoopGroup(); | |
105 | + } | |
106 | + this.host = host; | |
107 | + this.port = port; | |
108 | + | |
109 | + Promise<MqttConnectResult> connectFuture = new DefaultPromise<>(this.eventLoop.next()); | |
110 | + Bootstrap bootstrap = new Bootstrap(); | |
111 | + bootstrap.group(this.eventLoop); | |
112 | + bootstrap.channel(clientConfig.getChannelClass()); | |
113 | + bootstrap.remoteAddress(host, port); | |
114 | + bootstrap.handler(new MqttChannelInitializer(connectFuture, host, port, clientConfig.getSslContext())); | |
115 | + ChannelFuture future = bootstrap.connect(); | |
116 | + future.addListener((ChannelFutureListener) f -> { | |
117 | + if (f.isSuccess()) { | |
118 | + MqttClientImpl.this.channel = f.channel(); | |
119 | + } else if (clientConfig.isReconnect() && !disconnected) { | |
120 | + eventLoop.schedule((Runnable) () -> connect(host, port), 1L, TimeUnit.SECONDS); | |
121 | + } | |
122 | + }); | |
123 | + return connectFuture; | |
124 | + } | |
125 | + | |
126 | + @Override | |
127 | + public boolean isConnected() { | |
128 | + if (!disconnected) { | |
129 | + return channel == null ? false : channel.isActive(); | |
130 | + }; | |
131 | + return false; | |
132 | + } | |
133 | + | |
134 | + @Override | |
135 | + public Future<MqttConnectResult> reconnect() { | |
136 | + if (host == null) { | |
137 | + throw new IllegalStateException("Cannot reconnect. Call connect() first"); | |
138 | + } | |
139 | + return connect(host, port); | |
140 | + } | |
141 | + | |
142 | + /** | |
143 | + * Retrieve the netty {@link EventLoopGroup} we are using | |
144 | + * | |
145 | + * @return The netty {@link EventLoopGroup} we use for the connection | |
146 | + */ | |
147 | + @Override | |
148 | + public EventLoopGroup getEventLoop() { | |
149 | + return eventLoop; | |
150 | + } | |
151 | + | |
152 | + /** | |
153 | + * By default we use the netty {@link NioEventLoopGroup}. | |
154 | + * If you change the EventLoopGroup to another type, make sure to change the {@link Channel} class using {@link MqttClientConfig#setChannelClass(Class)} | |
155 | + * If you want to force the MqttClient to use another {@link EventLoopGroup}, call this function before calling {@link #connect(String, int)} | |
156 | + * | |
157 | + * @param eventLoop The new eventloop to use | |
158 | + */ | |
159 | + @Override | |
160 | + public void setEventLoop(EventLoopGroup eventLoop) { | |
161 | + this.eventLoop = eventLoop; | |
162 | + } | |
163 | + | |
164 | + /** | |
165 | + * Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler | |
166 | + * | |
167 | + * @param topic The topic filter to subscribe to | |
168 | + * @param handler The handler to invoke when we receive a message | |
169 | + * @return A future which will be completed when the server acknowledges our subscribe request | |
170 | + */ | |
171 | + @Override | |
172 | + public Future<Void> on(String topic, MqttHandler handler) { | |
173 | + return on(topic, handler, MqttQoS.AT_MOST_ONCE); | |
174 | + } | |
175 | + | |
176 | + /** | |
177 | + * Subscribe on the given topic, with the given qos. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler | |
178 | + * | |
179 | + * @param topic The topic filter to subscribe to | |
180 | + * @param handler The handler to invoke when we receive a message | |
181 | + * @param qos The qos to request to the server | |
182 | + * @return A future which will be completed when the server acknowledges our subscribe request | |
183 | + */ | |
184 | + @Override | |
185 | + public Future<Void> on(String topic, MqttHandler handler, MqttQoS qos) { | |
186 | + return createSubscribtion(topic, handler, false, qos); | |
187 | + } | |
188 | + | |
189 | + /** | |
190 | + * Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler | |
191 | + * This subscribtion is only once. If the MqttClient has received 1 message, the subscribtion will be removed | |
192 | + * | |
193 | + * @param topic The topic filter to subscribe to | |
194 | + * @param handler The handler to invoke when we receive a message | |
195 | + * @return A future which will be completed when the server acknowledges our subscribe request | |
196 | + */ | |
197 | + @Override | |
198 | + public Future<Void> once(String topic, MqttHandler handler) { | |
199 | + return once(topic, handler, MqttQoS.AT_MOST_ONCE); | |
200 | + } | |
201 | + | |
202 | + /** | |
203 | + * Subscribe on the given topic, with the given qos. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler | |
204 | + * This subscribtion is only once. If the MqttClient has received 1 message, the subscribtion will be removed | |
205 | + * | |
206 | + * @param topic The topic filter to subscribe to | |
207 | + * @param handler The handler to invoke when we receive a message | |
208 | + * @param qos The qos to request to the server | |
209 | + * @return A future which will be completed when the server acknowledges our subscribe request | |
210 | + */ | |
211 | + @Override | |
212 | + public Future<Void> once(String topic, MqttHandler handler, MqttQoS qos) { | |
213 | + return createSubscribtion(topic, handler, true, qos); | |
214 | + } | |
215 | + | |
216 | + /** | |
217 | + * Remove the subscribtion for the given topic and handler | |
218 | + * If you want to unsubscribe from all handlers known for this topic, use {@link #off(String)} | |
219 | + * | |
220 | + * @param topic The topic to unsubscribe for | |
221 | + * @param handler The handler to unsubscribe | |
222 | + * @return A future which will be completed when the server acknowledges our unsubscribe request | |
223 | + */ | |
224 | + @Override | |
225 | + public Future<Void> off(String topic, MqttHandler handler) { | |
226 | + Promise<Void> future = new DefaultPromise<>(this.eventLoop.next()); | |
227 | + for (MqttSubscribtion subscribtion : this.handlerToSubscribtion.get(handler)) { | |
228 | + this.subscriptions.remove(topic, subscribtion); | |
229 | + } | |
230 | + this.handlerToSubscribtion.removeAll(handler); | |
231 | + this.checkSubscribtions(topic, future); | |
232 | + return future; | |
233 | + } | |
234 | + | |
235 | + /** | |
236 | + * Remove all subscribtions for the given topic. | |
237 | + * If you want to specify which handler to unsubscribe, use {@link #off(String, MqttHandler)} | |
238 | + * | |
239 | + * @param topic The topic to unsubscribe for | |
240 | + * @return A future which will be completed when the server acknowledges our unsubscribe request | |
241 | + */ | |
242 | + @Override | |
243 | + public Future<Void> off(String topic) { | |
244 | + Promise<Void> future = new DefaultPromise<>(this.eventLoop.next()); | |
245 | + ImmutableSet<MqttSubscribtion> subscribtions = ImmutableSet.copyOf(this.subscriptions.get(topic)); | |
246 | + for (MqttSubscribtion subscribtion : subscribtions) { | |
247 | + for (MqttSubscribtion handSub : this.handlerToSubscribtion.get(subscribtion.getHandler())) { | |
248 | + this.subscriptions.remove(topic, handSub); | |
249 | + } | |
250 | + this.handlerToSubscribtion.remove(subscribtion.getHandler(), subscribtion); | |
251 | + } | |
252 | + this.checkSubscribtions(topic, future); | |
253 | + return future; | |
254 | + } | |
255 | + | |
256 | + /** | |
257 | + * Publish a message to the given payload | |
258 | + * | |
259 | + * @param topic The topic to publish to | |
260 | + * @param payload The payload to send | |
261 | + * @return A future which will be completed when the message is sent out of the MqttClient | |
262 | + */ | |
263 | + @Override | |
264 | + public Future<Void> publish(String topic, ByteBuf payload) { | |
265 | + return publish(topic, payload, MqttQoS.AT_MOST_ONCE, false); | |
266 | + } | |
267 | + | |
268 | + /** | |
269 | + * Publish a message to the given payload, using the given qos | |
270 | + * | |
271 | + * @param topic The topic to publish to | |
272 | + * @param payload The payload to send | |
273 | + * @param qos The qos to use while publishing | |
274 | + * @return A future which will be completed when the message is delivered to the server | |
275 | + */ | |
276 | + @Override | |
277 | + public Future<Void> publish(String topic, ByteBuf payload, MqttQoS qos) { | |
278 | + return publish(topic, payload, qos, false); | |
279 | + } | |
280 | + | |
281 | + /** | |
282 | + * Publish a message to the given payload, using optional retain | |
283 | + * | |
284 | + * @param topic The topic to publish to | |
285 | + * @param payload The payload to send | |
286 | + * @param retain true if you want to retain the message on the server, false otherwise | |
287 | + * @return A future which will be completed when the message is sent out of the MqttClient | |
288 | + */ | |
289 | + @Override | |
290 | + public Future<Void> publish(String topic, ByteBuf payload, boolean retain) { | |
291 | + return publish(topic, payload, MqttQoS.AT_MOST_ONCE, retain); | |
292 | + } | |
293 | + | |
294 | + /** | |
295 | + * Publish a message to the given payload, using the given qos and optional retain | |
296 | + * | |
297 | + * @param topic The topic to publish to | |
298 | + * @param payload The payload to send | |
299 | + * @param qos The qos to use while publishing | |
300 | + * @param retain true if you want to retain the message on the server, false otherwise | |
301 | + * @return A future which will be completed when the message is delivered to the server | |
302 | + */ | |
303 | + @Override | |
304 | + public Future<Void> publish(String topic, ByteBuf payload, MqttQoS qos, boolean retain) { | |
305 | + Promise<Void> future = new DefaultPromise<>(this.eventLoop.next()); | |
306 | + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, retain, 0); | |
307 | + MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(topic, getNewMessageId().messageId()); | |
308 | + MqttPublishMessage message = new MqttPublishMessage(fixedHeader, variableHeader, payload); | |
309 | + MqttPendingPublish pendingPublish = new MqttPendingPublish(variableHeader.messageId(), future, payload.retain(), message, qos); | |
310 | + ChannelFuture channelFuture = this.sendAndFlushPacket(message); | |
311 | + | |
312 | + if (channelFuture != null) { | |
313 | + pendingPublish.setSent(channelFuture != null); | |
314 | + if (channelFuture.cause() != null) { | |
315 | + future.setFailure(channelFuture.cause()); | |
316 | + return future; | |
317 | + } | |
318 | + } | |
319 | + if (pendingPublish.isSent() && pendingPublish.getQos() == MqttQoS.AT_MOST_ONCE) { | |
320 | + pendingPublish.getFuture().setSuccess(null); //We don't get an ACK for QOS 0 | |
321 | + } else if (pendingPublish.isSent()) { | |
322 | + this.pendingPublishes.put(pendingPublish.getMessageId(), pendingPublish); | |
323 | + pendingPublish.startPublishRetransmissionTimer(this.eventLoop.next(), this::sendAndFlushPacket); | |
324 | + } | |
325 | + return future; | |
326 | + } | |
327 | + | |
328 | + /** | |
329 | + * Retrieve the MqttClient configuration | |
330 | + * | |
331 | + * @return The {@link MqttClientConfig} instance we use | |
332 | + */ | |
333 | + @Override | |
334 | + public MqttClientConfig getClientConfig() { | |
335 | + return clientConfig; | |
336 | + } | |
337 | + | |
338 | + @Override | |
339 | + public void disconnect() { | |
340 | + disconnected = true; | |
341 | + if (this.channel != null) { | |
342 | + MqttMessage message = new MqttMessage(new MqttFixedHeader(MqttMessageType.DISCONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0)); | |
343 | + this.sendAndFlushPacket(message).addListener(future1 -> channel.close()); | |
344 | + } | |
345 | + } | |
346 | + | |
347 | + @Override | |
348 | + public void setCallback(MqttClientCallback callback) { | |
349 | + this.callback = callback; | |
350 | + } | |
351 | + | |
352 | + | |
353 | + ///////////////////////////////////////////// PRIVATE API ///////////////////////////////////////////// | |
354 | + | |
355 | + ChannelFuture sendAndFlushPacket(Object message) { | |
356 | + if (this.channel == null) { | |
357 | + return null; | |
358 | + } | |
359 | + if (this.channel.isActive()) { | |
360 | + return this.channel.writeAndFlush(message); | |
361 | + } | |
362 | + ChannelClosedException e = new ChannelClosedException("Channel is closed"); | |
363 | + if (callback != null) { | |
364 | + callback.connectionLost(e); | |
365 | + } | |
366 | + return this.channel.newFailedFuture(e); | |
367 | + } | |
368 | + | |
369 | + private MqttMessageIdVariableHeader getNewMessageId() { | |
370 | + this.nextMessageId.compareAndSet(0xffff, 1); | |
371 | + return MqttMessageIdVariableHeader.from(this.nextMessageId.getAndIncrement()); | |
372 | + } | |
373 | + | |
374 | + private Future<Void> createSubscribtion(String topic, MqttHandler handler, boolean once, MqttQoS qos) { | |
375 | + if (this.pendingSubscribeTopics.contains(topic)) { | |
376 | + Optional<Map.Entry<Integer, MqttPendingSubscribtion>> subscribtionEntry = this.pendingSubscribtions.entrySet().stream().filter((e) -> e.getValue().getTopic().equals(topic)).findAny(); | |
377 | + if (subscribtionEntry.isPresent()) { | |
378 | + subscribtionEntry.get().getValue().addHandler(handler, once); | |
379 | + return subscribtionEntry.get().getValue().getFuture(); | |
380 | + } | |
381 | + } | |
382 | + if (this.serverSubscribtions.contains(topic)) { | |
383 | + MqttSubscribtion subscribtion = new MqttSubscribtion(topic, handler, once); | |
384 | + this.subscriptions.put(topic, subscribtion); | |
385 | + this.handlerToSubscribtion.put(handler, subscribtion); | |
386 | + return this.channel.newSucceededFuture(); | |
387 | + } | |
388 | + | |
389 | + Promise<Void> future = new DefaultPromise<>(this.eventLoop.next()); | |
390 | + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.SUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0); | |
391 | + MqttTopicSubscription subscription = new MqttTopicSubscription(topic, qos); | |
392 | + MqttMessageIdVariableHeader variableHeader = getNewMessageId(); | |
393 | + MqttSubscribePayload payload = new MqttSubscribePayload(Collections.singletonList(subscription)); | |
394 | + MqttSubscribeMessage message = new MqttSubscribeMessage(fixedHeader, variableHeader, payload); | |
395 | + | |
396 | + final MqttPendingSubscribtion pendingSubscribtion = new MqttPendingSubscribtion(future, topic, message); | |
397 | + pendingSubscribtion.addHandler(handler, once); | |
398 | + this.pendingSubscribtions.put(variableHeader.messageId(), pendingSubscribtion); | |
399 | + this.pendingSubscribeTopics.add(topic); | |
400 | + pendingSubscribtion.setSent(this.sendAndFlushPacket(message) != null); //If not sent, we will send it when the connection is opened | |
401 | + | |
402 | + pendingSubscribtion.startRetransmitTimer(this.eventLoop.next(), this::sendAndFlushPacket); | |
403 | + | |
404 | + return future; | |
405 | + } | |
406 | + | |
407 | + private void checkSubscribtions(String topic, Promise<Void> promise) { | |
408 | + if (!(this.subscriptions.containsKey(topic) && this.subscriptions.get(topic).size() != 0) && this.serverSubscribtions.contains(topic)) { | |
409 | + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0); | |
410 | + MqttMessageIdVariableHeader variableHeader = getNewMessageId(); | |
411 | + MqttUnsubscribePayload payload = new MqttUnsubscribePayload(Collections.singletonList(topic)); | |
412 | + MqttUnsubscribeMessage message = new MqttUnsubscribeMessage(fixedHeader, variableHeader, payload); | |
413 | + | |
414 | + MqttPendingUnsubscribtion pendingUnsubscribtion = new MqttPendingUnsubscribtion(promise, topic, message); | |
415 | + this.pendingServerUnsubscribes.put(variableHeader.messageId(), pendingUnsubscribtion); | |
416 | + pendingUnsubscribtion.startRetransmissionTimer(this.eventLoop.next(), this::sendAndFlushPacket); | |
417 | + | |
418 | + this.sendAndFlushPacket(message); | |
419 | + } else { | |
420 | + promise.setSuccess(null); | |
421 | + } | |
422 | + } | |
423 | + | |
424 | + IntObjectHashMap<MqttPendingSubscribtion> getPendingSubscribtions() { | |
425 | + return pendingSubscribtions; | |
426 | + } | |
427 | + | |
428 | + HashMultimap<String, MqttSubscribtion> getSubscriptions() { | |
429 | + return subscriptions; | |
430 | + } | |
431 | + | |
432 | + Set<String> getPendingSubscribeTopics() { | |
433 | + return pendingSubscribeTopics; | |
434 | + } | |
435 | + | |
436 | + HashMultimap<MqttHandler, MqttSubscribtion> getHandlerToSubscribtion() { | |
437 | + return handlerToSubscribtion; | |
438 | + } | |
439 | + | |
440 | + Set<String> getServerSubscribtions() { | |
441 | + return serverSubscribtions; | |
442 | + } | |
443 | + | |
444 | + IntObjectHashMap<MqttPendingUnsubscribtion> getPendingServerUnsubscribes() { | |
445 | + return pendingServerUnsubscribes; | |
446 | + } | |
447 | + | |
448 | + IntObjectHashMap<MqttPendingPublish> getPendingPublishes() { | |
449 | + return pendingPublishes; | |
450 | + } | |
451 | + | |
452 | + IntObjectHashMap<MqttIncomingQos2Publish> getQos2PendingIncomingPublishes() { | |
453 | + return qos2PendingIncomingPublishes; | |
454 | + } | |
455 | + | |
456 | + private class MqttChannelInitializer extends ChannelInitializer<SocketChannel> { | |
457 | + | |
458 | + private final Promise<MqttConnectResult> connectFuture; | |
459 | + private final String host; | |
460 | + private final int port; | |
461 | + private final SslContext sslContext; | |
462 | + | |
463 | + | |
464 | + public MqttChannelInitializer(Promise<MqttConnectResult> connectFuture, String host, int port, SslContext sslContext) { | |
465 | + this.connectFuture = connectFuture; | |
466 | + this.host = host; | |
467 | + this.port = port; | |
468 | + this.sslContext = sslContext; | |
469 | + } | |
470 | + | |
471 | + @Override | |
472 | + protected void initChannel(SocketChannel ch) throws Exception { | |
473 | + if (sslContext != null) { | |
474 | + ch.pipeline().addLast(sslContext.newHandler(ch.alloc(), host, port)); | |
475 | + } | |
476 | + | |
477 | + ch.pipeline().addLast("mqttDecoder", new MqttDecoder()); | |
478 | + ch.pipeline().addLast("mqttEncoder", MqttEncoder.INSTANCE); | |
479 | + ch.pipeline().addLast("idleStateHandler", new IdleStateHandler(MqttClientImpl.this.clientConfig.getTimeoutSeconds(), MqttClientImpl.this.clientConfig.getTimeoutSeconds(), 0)); | |
480 | + ch.pipeline().addLast("mqttPingHandler", new MqttPingHandler(MqttClientImpl.this.clientConfig.getTimeoutSeconds())); | |
481 | + ch.pipeline().addLast("mqttHandler", new MqttChannelHandler(MqttClientImpl.this, connectFuture)); | |
482 | + } | |
483 | + } | |
484 | +} | ... | ... |
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 | +package org.thingsboard.mqtt; | |
17 | + | |
18 | +import io.netty.channel.ChannelFuture; | |
19 | +import io.netty.handler.codec.mqtt.MqttConnectReturnCode; | |
20 | + | |
21 | +@SuppressWarnings({"WeakerAccess", "unused"}) | |
22 | +public final class MqttConnectResult { | |
23 | + | |
24 | + private final boolean success; | |
25 | + private final MqttConnectReturnCode returnCode; | |
26 | + private final ChannelFuture closeFuture; | |
27 | + | |
28 | + MqttConnectResult(boolean success, MqttConnectReturnCode returnCode, ChannelFuture closeFuture) { | |
29 | + this.success = success; | |
30 | + this.returnCode = returnCode; | |
31 | + this.closeFuture = closeFuture; | |
32 | + } | |
33 | + | |
34 | + public boolean isSuccess() { | |
35 | + return success; | |
36 | + } | |
37 | + | |
38 | + public MqttConnectReturnCode getReturnCode() { | |
39 | + return returnCode; | |
40 | + } | |
41 | + | |
42 | + public ChannelFuture getCloseFuture() { | |
43 | + return closeFuture; | |
44 | + } | |
45 | +} | ... | ... |
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 | +package org.thingsboard.mqtt; | |
17 | + | |
18 | +import io.netty.buffer.ByteBuf; | |
19 | + | |
20 | +public interface MqttHandler { | |
21 | + | |
22 | + void onMessage(String topic, ByteBuf payload); | |
23 | +} | ... | ... |
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 | +package org.thingsboard.mqtt; | |
17 | + | |
18 | +import io.netty.channel.EventLoop; | |
19 | +import io.netty.handler.codec.mqtt.*; | |
20 | + | |
21 | +import java.util.function.Consumer; | |
22 | + | |
23 | +final class MqttIncomingQos2Publish { | |
24 | + | |
25 | + private final MqttPublishMessage incomingPublish; | |
26 | + | |
27 | + private final RetransmissionHandler<MqttMessage> retransmissionHandler = new RetransmissionHandler<>(); | |
28 | + | |
29 | + MqttIncomingQos2Publish(MqttPublishMessage incomingPublish, MqttMessage originalMessage) { | |
30 | + this.incomingPublish = incomingPublish; | |
31 | + | |
32 | + this.retransmissionHandler.setOriginalMessage(originalMessage); | |
33 | + } | |
34 | + | |
35 | + MqttPublishMessage getIncomingPublish() { | |
36 | + return incomingPublish; | |
37 | + } | |
38 | + | |
39 | + void startPubrecRetransmitTimer(EventLoop eventLoop, Consumer<Object> sendPacket) { | |
40 | + this.retransmissionHandler.setHandle((fixedHeader, originalMessage) -> | |
41 | + sendPacket.accept(new MqttMessage(fixedHeader, originalMessage.variableHeader()))); | |
42 | + this.retransmissionHandler.start(eventLoop); | |
43 | + } | |
44 | + | |
45 | + void onPubrelReceived() { | |
46 | + this.retransmissionHandler.stop(); | |
47 | + } | |
48 | +} | ... | ... |
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 | +package org.thingsboard.mqtt; | |
17 | + | |
18 | +import io.netty.handler.codec.mqtt.MqttQoS; | |
19 | + | |
20 | +@SuppressWarnings({"WeakerAccess", "unused", "SimplifiableIfStatement", "StringBufferReplaceableByString"}) | |
21 | +public final class MqttLastWill { | |
22 | + | |
23 | + private final String topic; | |
24 | + private final String message; | |
25 | + private final boolean retain; | |
26 | + private final MqttQoS qos; | |
27 | + | |
28 | + public MqttLastWill(String topic, String message, boolean retain, MqttQoS qos) { | |
29 | + if(topic == null){ | |
30 | + throw new NullPointerException("topic"); | |
31 | + } | |
32 | + if(message == null){ | |
33 | + throw new NullPointerException("message"); | |
34 | + } | |
35 | + if(qos == null){ | |
36 | + throw new NullPointerException("qos"); | |
37 | + } | |
38 | + this.topic = topic; | |
39 | + this.message = message; | |
40 | + this.retain = retain; | |
41 | + this.qos = qos; | |
42 | + } | |
43 | + | |
44 | + public String getTopic() { | |
45 | + return topic; | |
46 | + } | |
47 | + | |
48 | + public String getMessage() { | |
49 | + return message; | |
50 | + } | |
51 | + | |
52 | + public boolean isRetain() { | |
53 | + return retain; | |
54 | + } | |
55 | + | |
56 | + public MqttQoS getQos() { | |
57 | + return qos; | |
58 | + } | |
59 | + | |
60 | + public static MqttLastWill.Builder builder(){ | |
61 | + return new MqttLastWill.Builder(); | |
62 | + } | |
63 | + | |
64 | + public static final class Builder { | |
65 | + | |
66 | + private String topic; | |
67 | + private String message; | |
68 | + private boolean retain; | |
69 | + private MqttQoS qos; | |
70 | + | |
71 | + public String getTopic() { | |
72 | + return topic; | |
73 | + } | |
74 | + | |
75 | + public Builder setTopic(String topic) { | |
76 | + if(topic == null){ | |
77 | + throw new NullPointerException("topic"); | |
78 | + } | |
79 | + this.topic = topic; | |
80 | + return this; | |
81 | + } | |
82 | + | |
83 | + public String getMessage() { | |
84 | + return message; | |
85 | + } | |
86 | + | |
87 | + public Builder setMessage(String message) { | |
88 | + if(message == null){ | |
89 | + throw new NullPointerException("message"); | |
90 | + } | |
91 | + this.message = message; | |
92 | + return this; | |
93 | + } | |
94 | + | |
95 | + public boolean isRetain() { | |
96 | + return retain; | |
97 | + } | |
98 | + | |
99 | + public Builder setRetain(boolean retain) { | |
100 | + this.retain = retain; | |
101 | + return this; | |
102 | + } | |
103 | + | |
104 | + public MqttQoS getQos() { | |
105 | + return qos; | |
106 | + } | |
107 | + | |
108 | + public Builder setQos(MqttQoS qos) { | |
109 | + if(qos == null){ | |
110 | + throw new NullPointerException("qos"); | |
111 | + } | |
112 | + this.qos = qos; | |
113 | + return this; | |
114 | + } | |
115 | + | |
116 | + public MqttLastWill build(){ | |
117 | + return new MqttLastWill(topic, message, retain, qos); | |
118 | + } | |
119 | + } | |
120 | + | |
121 | + @Override | |
122 | + public boolean equals(Object o) { | |
123 | + if (this == o) return true; | |
124 | + if (o == null || getClass() != o.getClass()) return false; | |
125 | + | |
126 | + MqttLastWill that = (MqttLastWill) o; | |
127 | + | |
128 | + if (retain != that.retain) return false; | |
129 | + if (!topic.equals(that.topic)) return false; | |
130 | + if (!message.equals(that.message)) return false; | |
131 | + return qos == that.qos; | |
132 | + | |
133 | + } | |
134 | + | |
135 | + @Override | |
136 | + public int hashCode() { | |
137 | + int result = topic.hashCode(); | |
138 | + result = 31 * result + message.hashCode(); | |
139 | + result = 31 * result + (retain ? 1 : 0); | |
140 | + result = 31 * result + qos.hashCode(); | |
141 | + return result; | |
142 | + } | |
143 | + | |
144 | + @Override | |
145 | + public String toString() { | |
146 | + final StringBuilder sb = new StringBuilder("MqttLastWill{"); | |
147 | + sb.append("topic='").append(topic).append('\''); | |
148 | + sb.append(", message='").append(message).append('\''); | |
149 | + sb.append(", retain=").append(retain); | |
150 | + sb.append(", qos=").append(qos.name()); | |
151 | + sb.append('}'); | |
152 | + return sb.toString(); | |
153 | + } | |
154 | +} | ... | ... |
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 | +package org.thingsboard.mqtt; | |
17 | + | |
18 | +import io.netty.buffer.ByteBuf; | |
19 | +import io.netty.channel.EventLoop; | |
20 | +import io.netty.handler.codec.mqtt.MqttMessage; | |
21 | +import io.netty.handler.codec.mqtt.MqttPublishMessage; | |
22 | +import io.netty.handler.codec.mqtt.MqttQoS; | |
23 | +import io.netty.util.concurrent.Promise; | |
24 | + | |
25 | +import java.util.function.Consumer; | |
26 | + | |
27 | +final class MqttPendingPublish { | |
28 | + | |
29 | + private final int messageId; | |
30 | + private final Promise<Void> future; | |
31 | + private final ByteBuf payload; | |
32 | + private final MqttPublishMessage message; | |
33 | + private final MqttQoS qos; | |
34 | + | |
35 | + private final RetransmissionHandler<MqttPublishMessage> publishRetransmissionHandler = new RetransmissionHandler<>(); | |
36 | + private final RetransmissionHandler<MqttMessage> pubrelRetransmissionHandler = new RetransmissionHandler<>(); | |
37 | + | |
38 | + private boolean sent = false; | |
39 | + | |
40 | + MqttPendingPublish(int messageId, Promise<Void> future, ByteBuf payload, MqttPublishMessage message, MqttQoS qos) { | |
41 | + this.messageId = messageId; | |
42 | + this.future = future; | |
43 | + this.payload = payload; | |
44 | + this.message = message; | |
45 | + this.qos = qos; | |
46 | + | |
47 | + this.publishRetransmissionHandler.setOriginalMessage(message); | |
48 | + } | |
49 | + | |
50 | + int getMessageId() { | |
51 | + return messageId; | |
52 | + } | |
53 | + | |
54 | + Promise<Void> getFuture() { | |
55 | + return future; | |
56 | + } | |
57 | + | |
58 | + ByteBuf getPayload() { | |
59 | + return payload; | |
60 | + } | |
61 | + | |
62 | + boolean isSent() { | |
63 | + return sent; | |
64 | + } | |
65 | + | |
66 | + void setSent(boolean sent) { | |
67 | + this.sent = sent; | |
68 | + } | |
69 | + | |
70 | + MqttPublishMessage getMessage() { | |
71 | + return message; | |
72 | + } | |
73 | + | |
74 | + MqttQoS getQos() { | |
75 | + return qos; | |
76 | + } | |
77 | + | |
78 | + void startPublishRetransmissionTimer(EventLoop eventLoop, Consumer<Object> sendPacket) { | |
79 | + this.publishRetransmissionHandler.setHandle(((fixedHeader, originalMessage) -> | |
80 | + sendPacket.accept(new MqttPublishMessage(fixedHeader, originalMessage.variableHeader(), this.payload.retain())))); | |
81 | + this.publishRetransmissionHandler.start(eventLoop); | |
82 | + } | |
83 | + | |
84 | + void onPubackReceived() { | |
85 | + this.publishRetransmissionHandler.stop(); | |
86 | + } | |
87 | + | |
88 | + void setPubrelMessage(MqttMessage pubrelMessage) { | |
89 | + this.pubrelRetransmissionHandler.setOriginalMessage(pubrelMessage); | |
90 | + } | |
91 | + | |
92 | + void startPubrelRetransmissionTimer(EventLoop eventLoop, Consumer<Object> sendPacket) { | |
93 | + this.pubrelRetransmissionHandler.setHandle((fixedHeader, originalMessage) -> | |
94 | + sendPacket.accept(new MqttMessage(fixedHeader, originalMessage.variableHeader()))); | |
95 | + this.pubrelRetransmissionHandler.start(eventLoop); | |
96 | + } | |
97 | + | |
98 | + void onPubcompReceived() { | |
99 | + this.pubrelRetransmissionHandler.stop(); | |
100 | + } | |
101 | +} | ... | ... |
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 | +package org.thingsboard.mqtt; | |
17 | + | |
18 | +import io.netty.channel.EventLoop; | |
19 | +import io.netty.handler.codec.mqtt.MqttSubscribeMessage; | |
20 | +import io.netty.util.concurrent.Promise; | |
21 | + | |
22 | +import java.util.HashSet; | |
23 | +import java.util.Set; | |
24 | +import java.util.function.Consumer; | |
25 | + | |
26 | +final class MqttPendingSubscribtion { | |
27 | + | |
28 | + private final Promise<Void> future; | |
29 | + private final String topic; | |
30 | + private final Set<MqttPendingHandler> handlers = new HashSet<>(); | |
31 | + private final MqttSubscribeMessage subscribeMessage; | |
32 | + | |
33 | + private final RetransmissionHandler<MqttSubscribeMessage> retransmissionHandler = new RetransmissionHandler<>(); | |
34 | + | |
35 | + private boolean sent = false; | |
36 | + | |
37 | + MqttPendingSubscribtion(Promise<Void> future, String topic, MqttSubscribeMessage message) { | |
38 | + this.future = future; | |
39 | + this.topic = topic; | |
40 | + this.subscribeMessage = message; | |
41 | + | |
42 | + this.retransmissionHandler.setOriginalMessage(message); | |
43 | + } | |
44 | + | |
45 | + Promise<Void> getFuture() { | |
46 | + return future; | |
47 | + } | |
48 | + | |
49 | + String getTopic() { | |
50 | + return topic; | |
51 | + } | |
52 | + | |
53 | + boolean isSent() { | |
54 | + return sent; | |
55 | + } | |
56 | + | |
57 | + void setSent(boolean sent) { | |
58 | + this.sent = sent; | |
59 | + } | |
60 | + | |
61 | + MqttSubscribeMessage getSubscribeMessage() { | |
62 | + return subscribeMessage; | |
63 | + } | |
64 | + | |
65 | + void addHandler(MqttHandler handler, boolean once){ | |
66 | + this.handlers.add(new MqttPendingHandler(handler, once)); | |
67 | + } | |
68 | + | |
69 | + Set<MqttPendingHandler> getHandlers() { | |
70 | + return handlers; | |
71 | + } | |
72 | + | |
73 | + void startRetransmitTimer(EventLoop eventLoop, Consumer<Object> sendPacket) { | |
74 | + if(this.sent){ //If the packet is sent, we can start the retransmit timer | |
75 | + this.retransmissionHandler.setHandle((fixedHeader, originalMessage) -> | |
76 | + sendPacket.accept(new MqttSubscribeMessage(fixedHeader, originalMessage.variableHeader(), originalMessage.payload()))); | |
77 | + this.retransmissionHandler.start(eventLoop); | |
78 | + } | |
79 | + } | |
80 | + | |
81 | + void onSubackReceived(){ | |
82 | + this.retransmissionHandler.stop(); | |
83 | + } | |
84 | + | |
85 | + final class MqttPendingHandler { | |
86 | + private final MqttHandler handler; | |
87 | + private final boolean once; | |
88 | + | |
89 | + MqttPendingHandler(MqttHandler handler, boolean once) { | |
90 | + this.handler = handler; | |
91 | + this.once = once; | |
92 | + } | |
93 | + | |
94 | + MqttHandler getHandler() { | |
95 | + return handler; | |
96 | + } | |
97 | + | |
98 | + boolean isOnce() { | |
99 | + return once; | |
100 | + } | |
101 | + } | |
102 | +} | ... | ... |
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 | +package org.thingsboard.mqtt; | |
17 | + | |
18 | +import io.netty.channel.EventLoop; | |
19 | +import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage; | |
20 | +import io.netty.util.concurrent.Promise; | |
21 | + | |
22 | +import java.util.function.Consumer; | |
23 | + | |
24 | +final class MqttPendingUnsubscribtion { | |
25 | + | |
26 | + private final Promise<Void> future; | |
27 | + private final String topic; | |
28 | + | |
29 | + private final RetransmissionHandler<MqttUnsubscribeMessage> retransmissionHandler = new RetransmissionHandler<>(); | |
30 | + | |
31 | + MqttPendingUnsubscribtion(Promise<Void> future, String topic, MqttUnsubscribeMessage unsubscribeMessage) { | |
32 | + this.future = future; | |
33 | + this.topic = topic; | |
34 | + | |
35 | + this.retransmissionHandler.setOriginalMessage(unsubscribeMessage); | |
36 | + } | |
37 | + | |
38 | + Promise<Void> getFuture() { | |
39 | + return future; | |
40 | + } | |
41 | + | |
42 | + String getTopic() { | |
43 | + return topic; | |
44 | + } | |
45 | + | |
46 | + void startRetransmissionTimer(EventLoop eventLoop, Consumer<Object> sendPacket) { | |
47 | + this.retransmissionHandler.setHandle((fixedHeader, originalMessage) -> | |
48 | + sendPacket.accept(new MqttUnsubscribeMessage(fixedHeader, originalMessage.variableHeader(), originalMessage.payload()))); | |
49 | + this.retransmissionHandler.start(eventLoop); | |
50 | + } | |
51 | + | |
52 | + void onUnsubackReceived(){ | |
53 | + this.retransmissionHandler.stop(); | |
54 | + } | |
55 | +} | ... | ... |
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 | +package org.thingsboard.mqtt; | |
17 | + | |
18 | +import io.netty.channel.Channel; | |
19 | +import io.netty.channel.ChannelFutureListener; | |
20 | +import io.netty.channel.ChannelHandlerContext; | |
21 | +import io.netty.channel.ChannelInboundHandlerAdapter; | |
22 | +import io.netty.handler.codec.mqtt.MqttFixedHeader; | |
23 | +import io.netty.handler.codec.mqtt.MqttMessage; | |
24 | +import io.netty.handler.codec.mqtt.MqttMessageType; | |
25 | +import io.netty.handler.codec.mqtt.MqttQoS; | |
26 | +import io.netty.handler.timeout.IdleStateEvent; | |
27 | +import io.netty.util.ReferenceCountUtil; | |
28 | +import io.netty.util.concurrent.ScheduledFuture; | |
29 | + | |
30 | +import java.util.concurrent.TimeUnit; | |
31 | + | |
32 | +final class MqttPingHandler extends ChannelInboundHandlerAdapter { | |
33 | + | |
34 | + private final int keepaliveSeconds; | |
35 | + | |
36 | + private ScheduledFuture<?> pingRespTimeout; | |
37 | + | |
38 | + MqttPingHandler(int keepaliveSeconds) { | |
39 | + this.keepaliveSeconds = keepaliveSeconds; | |
40 | + } | |
41 | + | |
42 | + @Override | |
43 | + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { | |
44 | + if (!(msg instanceof MqttMessage)) { | |
45 | + ctx.fireChannelRead(msg); | |
46 | + return; | |
47 | + } | |
48 | + MqttMessage message = (MqttMessage) msg; | |
49 | + if(message.fixedHeader().messageType() == MqttMessageType.PINGREQ){ | |
50 | + this.handlePingReq(ctx.channel()); | |
51 | + } else if(message.fixedHeader().messageType() == MqttMessageType.PINGRESP){ | |
52 | + this.handlePingResp(); | |
53 | + }else{ | |
54 | + ctx.fireChannelRead(ReferenceCountUtil.retain(msg)); | |
55 | + } | |
56 | + } | |
57 | + | |
58 | + @Override | |
59 | + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { | |
60 | + super.userEventTriggered(ctx, evt); | |
61 | + | |
62 | + if(evt instanceof IdleStateEvent){ | |
63 | + IdleStateEvent event = (IdleStateEvent) evt; | |
64 | + switch(event.state()){ | |
65 | + case READER_IDLE: | |
66 | + break; | |
67 | + case WRITER_IDLE: | |
68 | + this.sendPingReq(ctx.channel()); | |
69 | + break; | |
70 | + } | |
71 | + } | |
72 | + } | |
73 | + | |
74 | + private void sendPingReq(Channel channel){ | |
75 | + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PINGREQ, false, MqttQoS.AT_MOST_ONCE, false, 0); | |
76 | + channel.writeAndFlush(new MqttMessage(fixedHeader)); | |
77 | + | |
78 | + if(this.pingRespTimeout != null){ | |
79 | + this.pingRespTimeout = channel.eventLoop().schedule(() -> { | |
80 | + MqttFixedHeader fixedHeader2 = new MqttFixedHeader(MqttMessageType.DISCONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0); | |
81 | + channel.writeAndFlush(new MqttMessage(fixedHeader2)).addListener(ChannelFutureListener.CLOSE); | |
82 | + //TODO: what do when the connection is closed ? | |
83 | + }, this.keepaliveSeconds, TimeUnit.SECONDS); | |
84 | + } | |
85 | + } | |
86 | + | |
87 | + private void handlePingReq(Channel channel){ | |
88 | + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PINGRESP, false, MqttQoS.AT_MOST_ONCE, false, 0); | |
89 | + channel.writeAndFlush(new MqttMessage(fixedHeader)); | |
90 | + } | |
91 | + | |
92 | + private void handlePingResp(){ | |
93 | + if(this.pingRespTimeout != null && !this.pingRespTimeout.isCancelled() && !this.pingRespTimeout.isDone()){ | |
94 | + this.pingRespTimeout.cancel(true); | |
95 | + this.pingRespTimeout = null; | |
96 | + } | |
97 | + } | |
98 | +} | ... | ... |
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 | +package org.thingsboard.mqtt; | |
17 | + | |
18 | +import java.util.regex.Pattern; | |
19 | + | |
20 | +final class MqttSubscribtion { | |
21 | + | |
22 | + private final String topic; | |
23 | + private final Pattern topicRegex; | |
24 | + private final MqttHandler handler; | |
25 | + | |
26 | + private final boolean once; | |
27 | + | |
28 | + private boolean called; | |
29 | + | |
30 | + MqttSubscribtion(String topic, MqttHandler handler, boolean once) { | |
31 | + if(topic == null){ | |
32 | + throw new NullPointerException("topic"); | |
33 | + } | |
34 | + if(handler == null){ | |
35 | + throw new NullPointerException("handler"); | |
36 | + } | |
37 | + this.topic = topic; | |
38 | + this.handler = handler; | |
39 | + this.once = once; | |
40 | + this.topicRegex = Pattern.compile(topic.replace("+", "[^/]+").replace("#", ".+") + "$"); | |
41 | + } | |
42 | + | |
43 | + String getTopic() { | |
44 | + return topic; | |
45 | + } | |
46 | + | |
47 | + public MqttHandler getHandler() { | |
48 | + return handler; | |
49 | + } | |
50 | + | |
51 | + boolean isOnce() { | |
52 | + return once; | |
53 | + } | |
54 | + | |
55 | + boolean isCalled() { | |
56 | + return called; | |
57 | + } | |
58 | + | |
59 | + boolean matches(String topic){ | |
60 | + return this.topicRegex.matcher(topic).matches(); | |
61 | + } | |
62 | + | |
63 | + @Override | |
64 | + public boolean equals(Object o) { | |
65 | + if (this == o) return true; | |
66 | + if (o == null || getClass() != o.getClass()) return false; | |
67 | + | |
68 | + MqttSubscribtion that = (MqttSubscribtion) o; | |
69 | + | |
70 | + return once == that.once && topic.equals(that.topic) && handler.equals(that.handler); | |
71 | + } | |
72 | + | |
73 | + @Override | |
74 | + public int hashCode() { | |
75 | + int result = topic.hashCode(); | |
76 | + result = 31 * result + handler.hashCode(); | |
77 | + result = 31 * result + (once ? 1 : 0); | |
78 | + return result; | |
79 | + } | |
80 | + | |
81 | + void setCalled(boolean called) { | |
82 | + this.called = called; | |
83 | + } | |
84 | +} | ... | ... |
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 | +package org.thingsboard.mqtt; | |
17 | + | |
18 | +import io.netty.channel.EventLoop; | |
19 | +import io.netty.handler.codec.mqtt.MqttFixedHeader; | |
20 | +import io.netty.handler.codec.mqtt.MqttMessage; | |
21 | +import io.netty.util.concurrent.ScheduledFuture; | |
22 | + | |
23 | +import java.util.concurrent.TimeUnit; | |
24 | +import java.util.function.BiConsumer; | |
25 | + | |
26 | +final class RetransmissionHandler<T extends MqttMessage> { | |
27 | + | |
28 | + private ScheduledFuture<?> timer; | |
29 | + private int timeout = 10; | |
30 | + private BiConsumer<MqttFixedHeader, T> handler; | |
31 | + private T originalMessage; | |
32 | + | |
33 | + void start(EventLoop eventLoop){ | |
34 | + if(eventLoop == null){ | |
35 | + throw new NullPointerException("eventLoop"); | |
36 | + } | |
37 | + if(this.handler == null){ | |
38 | + throw new NullPointerException("handler"); | |
39 | + } | |
40 | + this.timeout = 10; | |
41 | + this.startTimer(eventLoop); | |
42 | + } | |
43 | + | |
44 | + private void startTimer(EventLoop eventLoop){ | |
45 | + this.timer = eventLoop.schedule(() -> { | |
46 | + this.timeout += 5; | |
47 | + MqttFixedHeader fixedHeader = new MqttFixedHeader(this.originalMessage.fixedHeader().messageType(), true, this.originalMessage.fixedHeader().qosLevel(), this.originalMessage.fixedHeader().isRetain(), this.originalMessage.fixedHeader().remainingLength()); | |
48 | + handler.accept(fixedHeader, originalMessage); | |
49 | + startTimer(eventLoop); | |
50 | + }, timeout, TimeUnit.SECONDS); | |
51 | + } | |
52 | + | |
53 | + void stop(){ | |
54 | + if(this.timer != null){ | |
55 | + this.timer.cancel(true); | |
56 | + } | |
57 | + } | |
58 | + | |
59 | + void setHandle(BiConsumer<MqttFixedHeader, T> runnable) { | |
60 | + this.handler = runnable; | |
61 | + } | |
62 | + | |
63 | + void setOriginalMessage(T originalMessage) { | |
64 | + this.originalMessage = originalMessage; | |
65 | + } | |
66 | +} | ... | ... |
... | ... | @@ -79,7 +79,6 @@ |
79 | 79 | <dbunit.version>2.5.3</dbunit.version> |
80 | 80 | <spring-test-dbunit.version>1.2.1</spring-test-dbunit.version> |
81 | 81 | <postgresql.driver.version>9.4.1211</postgresql.driver.version> |
82 | - <netty-mqtt-client.version>2.0.0TB</netty-mqtt-client.version> | |
83 | 82 | <sonar.exclusions>org/thingsboard/server/gen/**/*, |
84 | 83 | org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/* |
85 | 84 | </sonar.exclusions> |
... | ... | @@ -87,6 +86,7 @@ |
87 | 86 | </properties> |
88 | 87 | |
89 | 88 | <modules> |
89 | + <module>netty-mqtt</module> | |
90 | 90 | <module>common</module> |
91 | 91 | <module>rule-engine</module> |
92 | 92 | <module>dao</module> |
... | ... | @@ -326,6 +326,11 @@ |
326 | 326 | <dependencies> |
327 | 327 | <dependency> |
328 | 328 | <groupId>org.thingsboard</groupId> |
329 | + <artifactId>netty-mqtt</artifactId> | |
330 | + <version>${project.version}</version> | |
331 | + </dependency> | |
332 | + <dependency> | |
333 | + <groupId>org.thingsboard</groupId> | |
329 | 334 | <artifactId>extensions-api</artifactId> |
330 | 335 | <version>${project.version}</version> |
331 | 336 | </dependency> |
... | ... | @@ -569,6 +574,11 @@ |
569 | 574 | <version>${netty.version}</version> |
570 | 575 | </dependency> |
571 | 576 | <dependency> |
577 | + <groupId>io.netty</groupId> | |
578 | + <artifactId>netty-codec-mqtt</artifactId> | |
579 | + <version>${netty.version}</version> | |
580 | + </dependency> | |
581 | + <dependency> | |
572 | 582 | <groupId>com.datastax.cassandra</groupId> |
573 | 583 | <artifactId>cassandra-driver-core</artifactId> |
574 | 584 | <version>${cassandra.version}</version> |
... | ... | @@ -820,11 +830,6 @@ |
820 | 830 | <scope>provided</scope> |
821 | 831 | </dependency> |
822 | 832 | <dependency> |
823 | - <groupId>nl.jk5.netty-mqtt</groupId> | |
824 | - <artifactId>netty-mqtt</artifactId> | |
825 | - <version>${netty-mqtt-client.version}</version> | |
826 | - </dependency> | |
827 | - <dependency> | |
828 | 833 | <groupId>org.elasticsearch.client</groupId> |
829 | 834 | <artifactId>rest</artifactId> |
830 | 835 | <version>${elasticsearch.version}</version> | ... | ... |
... | ... | @@ -69,6 +69,10 @@ |
69 | 69 | <artifactId>rule-engine-api</artifactId> |
70 | 70 | </dependency> |
71 | 71 | <dependency> |
72 | + <groupId>org.thingsboard</groupId> | |
73 | + <artifactId>netty-mqtt</artifactId> | |
74 | + </dependency> | |
75 | + <dependency> | |
72 | 76 | <groupId>com.google.guava</groupId> |
73 | 77 | <artifactId>guava</artifactId> |
74 | 78 | </dependency> |
... | ... | @@ -91,10 +95,6 @@ |
91 | 95 | <artifactId>amqp-client</artifactId> |
92 | 96 | </dependency> |
93 | 97 | <dependency> |
94 | - <groupId>nl.jk5.netty-mqtt</groupId> | |
95 | - <artifactId>netty-mqtt</artifactId> | |
96 | - </dependency> | |
97 | - <dependency> | |
98 | 98 | <groupId>org.bouncycastle</groupId> |
99 | 99 | <artifactId>bcpkix-jdk15on</artifactId> |
100 | 100 | </dependency> | ... | ... |
... | ... | @@ -20,12 +20,19 @@ import com.google.common.util.concurrent.Futures; |
20 | 20 | import com.google.common.util.concurrent.ListenableFuture; |
21 | 21 | |
22 | 22 | import javax.annotation.Nullable; |
23 | +import java.util.concurrent.Executor; | |
23 | 24 | import java.util.function.Consumer; |
24 | 25 | |
25 | 26 | public class DonAsynchron { |
26 | 27 | |
27 | - public static <T> void withCallback(ListenableFuture<T> future, Consumer<T> onSuccess, Consumer<Throwable> onFailure) { | |
28 | - Futures.addCallback(future, new FutureCallback<T>() { | |
28 | + public static <T> void withCallback(ListenableFuture<T> future, Consumer<T> onSuccess, | |
29 | + Consumer<Throwable> onFailure) { | |
30 | + withCallback(future, onSuccess, onFailure, null); | |
31 | + } | |
32 | + | |
33 | + public static <T> void withCallback(ListenableFuture<T> future, Consumer<T> onSuccess, | |
34 | + Consumer<Throwable> onFailure, Executor executor) { | |
35 | + FutureCallback<T> callback = new FutureCallback<T>() { | |
29 | 36 | @Override |
30 | 37 | public void onSuccess(@Nullable T result) { |
31 | 38 | try { |
... | ... | @@ -33,13 +40,17 @@ public class DonAsynchron { |
33 | 40 | } catch (Throwable th) { |
34 | 41 | onFailure(th); |
35 | 42 | } |
36 | - | |
37 | 43 | } |
38 | 44 | |
39 | 45 | @Override |
40 | 46 | public void onFailure(Throwable t) { |
41 | 47 | onFailure.accept(t); |
42 | 48 | } |
43 | - }); | |
49 | + }; | |
50 | + if (executor != null) { | |
51 | + Futures.addCallback(future, callback, executor); | |
52 | + } else { | |
53 | + Futures.addCallback(future, callback); | |
54 | + } | |
44 | 55 | } |
45 | 56 | } | ... | ... |
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 | +package org.thingsboard.rule.engine.action; | |
17 | + | |
18 | +import com.fasterxml.jackson.databind.JsonNode; | |
19 | +import com.fasterxml.jackson.databind.ObjectMapper; | |
20 | +import com.google.common.util.concurrent.ListenableFuture; | |
21 | +import lombok.extern.slf4j.Slf4j; | |
22 | +import org.thingsboard.rule.engine.api.*; | |
23 | +import org.thingsboard.server.common.data.alarm.Alarm; | |
24 | +import org.thingsboard.server.common.msg.TbMsg; | |
25 | +import org.thingsboard.server.common.msg.TbMsgMetaData; | |
26 | + | |
27 | +import static org.thingsboard.rule.engine.DonAsynchron.withCallback; | |
28 | + | |
29 | +@Slf4j | |
30 | +public abstract class TbAbstractAlarmNode<C extends TbAbstractAlarmNodeConfiguration> implements TbNode { | |
31 | + | |
32 | + static final String PREV_ALARM_DETAILS = "prevAlarmDetails"; | |
33 | + | |
34 | + static final String IS_NEW_ALARM = "isNewAlarm"; | |
35 | + static final String IS_EXISTING_ALARM = "isExistingAlarm"; | |
36 | + static final String IS_CLEARED_ALARM = "isClearedAlarm"; | |
37 | + | |
38 | + private final ObjectMapper mapper = new ObjectMapper(); | |
39 | + | |
40 | + protected C config; | |
41 | + private ScriptEngine buildDetailsJsEngine; | |
42 | + | |
43 | + @Override | |
44 | + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { | |
45 | + this.config = loadAlarmNodeConfig(configuration); | |
46 | + this.buildDetailsJsEngine = ctx.createJsScriptEngine(config.getAlarmDetailsBuildJs(), "Details"); | |
47 | + } | |
48 | + | |
49 | + protected abstract C loadAlarmNodeConfig(TbNodeConfiguration configuration) throws TbNodeException; | |
50 | + | |
51 | + @Override | |
52 | + public void onMsg(TbContext ctx, TbMsg msg) { | |
53 | + withCallback(processAlarm(ctx, msg), | |
54 | + alarmResult -> { | |
55 | + if (alarmResult.alarm == null) { | |
56 | + ctx.tellNext(msg, "False"); | |
57 | + } else if (alarmResult.isCreated) { | |
58 | + ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Created"); | |
59 | + } else if (alarmResult.isUpdated) { | |
60 | + ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Updated"); | |
61 | + } else if (alarmResult.isCleared) { | |
62 | + ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Cleared"); | |
63 | + } | |
64 | + }, | |
65 | + t -> ctx.tellError(msg, t)); | |
66 | + } | |
67 | + | |
68 | + protected abstract ListenableFuture<AlarmResult> processAlarm(TbContext ctx, TbMsg msg); | |
69 | + | |
70 | + protected ListenableFuture<JsonNode> buildAlarmDetails(TbContext ctx, TbMsg msg, JsonNode previousDetails) { | |
71 | + return ctx.getJsExecutor().executeAsync(() -> { | |
72 | + TbMsg theMsg = msg; | |
73 | + if (previousDetails != null) { | |
74 | + TbMsgMetaData metaData = msg.getMetaData().copy(); | |
75 | + metaData.putValue(PREV_ALARM_DETAILS, mapper.writeValueAsString(previousDetails)); | |
76 | + theMsg = ctx.newMsg(msg.getType(), msg.getOriginator(), metaData, msg.getData()); | |
77 | + } | |
78 | + return buildDetailsJsEngine.executeJson(theMsg); | |
79 | + }); | |
80 | + } | |
81 | + | |
82 | + private TbMsg toAlarmMsg(TbContext ctx, AlarmResult alarmResult, TbMsg originalMsg) { | |
83 | + JsonNode jsonNodes = mapper.valueToTree(alarmResult.alarm); | |
84 | + String data = jsonNodes.toString(); | |
85 | + TbMsgMetaData metaData = originalMsg.getMetaData().copy(); | |
86 | + if (alarmResult.isCreated) { | |
87 | + metaData.putValue(IS_NEW_ALARM, Boolean.TRUE.toString()); | |
88 | + } else if (alarmResult.isUpdated) { | |
89 | + metaData.putValue(IS_EXISTING_ALARM, Boolean.TRUE.toString()); | |
90 | + } else if (alarmResult.isCleared) { | |
91 | + metaData.putValue(IS_CLEARED_ALARM, Boolean.TRUE.toString()); | |
92 | + } | |
93 | + return ctx.transformMsg(originalMsg, "ALARM", originalMsg.getOriginator(), metaData, data); | |
94 | + } | |
95 | + | |
96 | + | |
97 | + @Override | |
98 | + public void destroy() { | |
99 | + if (buildDetailsJsEngine != null) { | |
100 | + buildDetailsJsEngine.destroy(); | |
101 | + } | |
102 | + } | |
103 | + | |
104 | + protected static class AlarmResult { | |
105 | + boolean isCreated; | |
106 | + boolean isUpdated; | |
107 | + boolean isCleared; | |
108 | + Alarm alarm; | |
109 | + | |
110 | + AlarmResult(boolean isCreated, boolean isUpdated, boolean isCleared, Alarm alarm) { | |
111 | + this.isCreated = isCreated; | |
112 | + this.isUpdated = isUpdated; | |
113 | + this.isCleared = isCleared; | |
114 | + this.alarm = alarm; | |
115 | + } | |
116 | + } | |
117 | +} | ... | ... |
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 | +package org.thingsboard.rule.engine.action; | |
17 | + | |
18 | +import lombok.Data; | |
19 | + | |
20 | +@Data | |
21 | +public abstract class TbAbstractAlarmNodeConfiguration { | |
22 | + | |
23 | + private String alarmType; | |
24 | + private String alarmDetailsBuildJs; | |
25 | + | |
26 | +} | ... | ... |
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmNode.java
deleted
100644 → 0
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 | -package org.thingsboard.rule.engine.action; | |
17 | - | |
18 | -import com.fasterxml.jackson.databind.JsonNode; | |
19 | -import com.fasterxml.jackson.databind.ObjectMapper; | |
20 | -import com.google.common.base.Function; | |
21 | -import com.google.common.util.concurrent.AsyncFunction; | |
22 | -import com.google.common.util.concurrent.Futures; | |
23 | -import com.google.common.util.concurrent.ListenableFuture; | |
24 | -import lombok.extern.slf4j.Slf4j; | |
25 | -import org.thingsboard.rule.engine.TbNodeUtils; | |
26 | -import org.thingsboard.rule.engine.api.*; | |
27 | -import org.thingsboard.server.common.data.alarm.Alarm; | |
28 | -import org.thingsboard.server.common.data.alarm.AlarmStatus; | |
29 | -import org.thingsboard.server.common.data.id.TenantId; | |
30 | -import org.thingsboard.server.common.data.plugin.ComponentType; | |
31 | -import org.thingsboard.server.common.msg.TbMsg; | |
32 | -import org.thingsboard.server.common.msg.TbMsgMetaData; | |
33 | - | |
34 | -import static org.thingsboard.rule.engine.DonAsynchron.withCallback; | |
35 | - | |
36 | -@Slf4j | |
37 | -@RuleNode( | |
38 | - type = ComponentType.ACTION, | |
39 | - name = "alarm", relationTypes = {"Created", "Updated", "Cleared", "False"}, | |
40 | - configClazz = TbAlarmNodeConfiguration.class, | |
41 | - nodeDescription = "Create/Update/Clear Alarm", | |
42 | - nodeDetails = "isAlarm - JS function that verifies if Alarm should be CREATED for incoming message.\n" + | |
43 | - "isCleared - JS function that verifies if Alarm should be CLEARED for incoming message.\n" + | |
44 | - "Details - JS function that creates JSON object based on incoming message. This object will be added into Alarm.details field.\n" + | |
45 | - "Node output:\n" + | |
46 | - "If alarm was not created, original message is returned. Otherwise new Message returned with type 'ALARM', Alarm object in 'msg' property and 'matadata' will contains one of those properties 'isNewAlarm/isExistingAlarm/isClearedAlarm' " + | |
47 | - "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" + | |
48 | - "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>", | |
49 | - uiResources = {"static/rulenode/rulenode-core-config.js"}, | |
50 | - configDirective = "tbActionNodeAlarmConfig", | |
51 | - icon = "notifications_active" | |
52 | -) | |
53 | - | |
54 | -public class TbAlarmNode implements TbNode { | |
55 | - | |
56 | - static final String IS_NEW_ALARM = "isNewAlarm"; | |
57 | - static final String IS_EXISTING_ALARM = "isExistingAlarm"; | |
58 | - static final String IS_CLEARED_ALARM = "isClearedAlarm"; | |
59 | - | |
60 | - private final ObjectMapper mapper = new ObjectMapper(); | |
61 | - | |
62 | - private TbAlarmNodeConfiguration config; | |
63 | - private ScriptEngine createJsEngine; | |
64 | - private ScriptEngine clearJsEngine; | |
65 | - private ScriptEngine buildDetailsJsEngine; | |
66 | - | |
67 | - @Override | |
68 | - public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { | |
69 | - this.config = TbNodeUtils.convert(configuration, TbAlarmNodeConfiguration.class); | |
70 | - this.createJsEngine = ctx.createJsScriptEngine(config.getCreateConditionJs(), "isAlarm"); | |
71 | - this.clearJsEngine = ctx.createJsScriptEngine(config.getClearConditionJs(), "isCleared"); | |
72 | - this.buildDetailsJsEngine = ctx.createJsScriptEngine(config.getAlarmDetailsBuildJs(), "Details"); | |
73 | - } | |
74 | - | |
75 | - @Override | |
76 | - public void onMsg(TbContext ctx, TbMsg msg) { | |
77 | - ListeningExecutor jsExecutor = ctx.getJsExecutor(); | |
78 | - | |
79 | - ListenableFuture<Boolean> shouldCreate = jsExecutor.executeAsync(() -> createJsEngine.executeFilter(msg)); | |
80 | - ListenableFuture<AlarmResult> transform = Futures.transform(shouldCreate, (AsyncFunction<Boolean, AlarmResult>) create -> { | |
81 | - if (create) { | |
82 | - return createOrUpdate(ctx, msg); | |
83 | - } else { | |
84 | - return checkForClearIfExist(ctx, msg); | |
85 | - } | |
86 | - }, ctx.getDbCallbackExecutor()); | |
87 | - | |
88 | - withCallback(transform, | |
89 | - alarmResult -> { | |
90 | - if (alarmResult.alarm == null) { | |
91 | - ctx.tellNext(msg, "False"); | |
92 | - } else if (alarmResult.isCreated) { | |
93 | - ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Created"); | |
94 | - } else if (alarmResult.isUpdated) { | |
95 | - ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Updated"); | |
96 | - } else if (alarmResult.isCleared) { | |
97 | - ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Cleared"); | |
98 | - } | |
99 | - }, | |
100 | - t -> ctx.tellError(msg, t)); | |
101 | - | |
102 | - } | |
103 | - | |
104 | - private ListenableFuture<AlarmResult> createOrUpdate(TbContext ctx, TbMsg msg) { | |
105 | - ListenableFuture<Alarm> latest = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), config.getAlarmType()); | |
106 | - return Futures.transform(latest, (AsyncFunction<Alarm, AlarmResult>) a -> { | |
107 | - if (a == null || a.getStatus().isCleared()) { | |
108 | - return createNewAlarm(ctx, msg); | |
109 | - } else { | |
110 | - return updateAlarm(ctx, msg, a); | |
111 | - } | |
112 | - }, ctx.getDbCallbackExecutor()); | |
113 | - } | |
114 | - | |
115 | - private ListenableFuture<AlarmResult> checkForClearIfExist(TbContext ctx, TbMsg msg) { | |
116 | - ListenableFuture<Alarm> latest = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), config.getAlarmType()); | |
117 | - return Futures.transform(latest, (AsyncFunction<Alarm, AlarmResult>) a -> { | |
118 | - if (a != null && !a.getStatus().isCleared()) { | |
119 | - return clearAlarm(ctx, msg, a); | |
120 | - } | |
121 | - return Futures.immediateFuture(new AlarmResult(false, false, false, null)); | |
122 | - }, ctx.getDbCallbackExecutor()); | |
123 | - } | |
124 | - | |
125 | - private ListenableFuture<AlarmResult> createNewAlarm(TbContext ctx, TbMsg msg) { | |
126 | - ListenableFuture<Alarm> asyncAlarm = Futures.transform(buildAlarmDetails(ctx, msg), | |
127 | - (Function<JsonNode, Alarm>) details -> buildAlarm(msg, details, ctx.getTenantId())); | |
128 | - ListenableFuture<Alarm> asyncCreated = Futures.transform(asyncAlarm, | |
129 | - (Function<Alarm, Alarm>) alarm -> ctx.getAlarmService().createOrUpdateAlarm(alarm), ctx.getDbCallbackExecutor()); | |
130 | - return Futures.transform(asyncCreated, (Function<Alarm, AlarmResult>) alarm -> new AlarmResult(true, false, false, alarm)); | |
131 | - } | |
132 | - | |
133 | - private ListenableFuture<AlarmResult> updateAlarm(TbContext ctx, TbMsg msg, Alarm alarm) { | |
134 | - ListenableFuture<Alarm> asyncUpdated = Futures.transform(buildAlarmDetails(ctx, msg), (Function<JsonNode, Alarm>) details -> { | |
135 | - alarm.setSeverity(config.getSeverity()); | |
136 | - alarm.setPropagate(config.isPropagate()); | |
137 | - alarm.setDetails(details); | |
138 | - alarm.setEndTs(System.currentTimeMillis()); | |
139 | - return ctx.getAlarmService().createOrUpdateAlarm(alarm); | |
140 | - }, ctx.getDbCallbackExecutor()); | |
141 | - | |
142 | - return Futures.transform(asyncUpdated, (Function<Alarm, AlarmResult>) a -> new AlarmResult(false, true, false, a)); | |
143 | - } | |
144 | - | |
145 | - private ListenableFuture<AlarmResult> clearAlarm(TbContext ctx, TbMsg msg, Alarm alarm) { | |
146 | - ListenableFuture<Boolean> shouldClear = ctx.getJsExecutor().executeAsync(() -> clearJsEngine.executeFilter(msg)); | |
147 | - return Futures.transform(shouldClear, (AsyncFunction<Boolean, AlarmResult>) clear -> { | |
148 | - if (clear) { | |
149 | - ListenableFuture<Boolean> clearFuture = ctx.getAlarmService().clearAlarm(alarm.getId(), System.currentTimeMillis()); | |
150 | - return Futures.transform(clearFuture, (Function<Boolean, AlarmResult>) cleared -> { | |
151 | - alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK); | |
152 | - return new AlarmResult(false, false, true, alarm); | |
153 | - }); | |
154 | - } | |
155 | - return Futures.immediateFuture(new AlarmResult(false, false, false, null)); | |
156 | - }); | |
157 | - } | |
158 | - | |
159 | - private Alarm buildAlarm(TbMsg msg, JsonNode details, TenantId tenantId) { | |
160 | - return Alarm.builder() | |
161 | - .tenantId(tenantId) | |
162 | - .originator(msg.getOriginator()) | |
163 | - .status(AlarmStatus.ACTIVE_UNACK) | |
164 | - .severity(config.getSeverity()) | |
165 | - .propagate(config.isPropagate()) | |
166 | - .type(config.getAlarmType()) | |
167 | - //todo-vp: alarm date should be taken from Message or current Time should be used? | |
168 | -// .startTs(System.currentTimeMillis()) | |
169 | -// .endTs(System.currentTimeMillis()) | |
170 | - .details(details) | |
171 | - .build(); | |
172 | - } | |
173 | - | |
174 | - private ListenableFuture<JsonNode> buildAlarmDetails(TbContext ctx, TbMsg msg) { | |
175 | - return ctx.getJsExecutor().executeAsync(() -> buildDetailsJsEngine.executeJson(msg)); | |
176 | - } | |
177 | - | |
178 | - private TbMsg toAlarmMsg(TbContext ctx, AlarmResult alarmResult, TbMsg originalMsg) { | |
179 | - JsonNode jsonNodes = mapper.valueToTree(alarmResult.alarm); | |
180 | - String data = jsonNodes.toString(); | |
181 | - TbMsgMetaData metaData = originalMsg.getMetaData().copy(); | |
182 | - if (alarmResult.isCreated) { | |
183 | - metaData.putValue(IS_NEW_ALARM, Boolean.TRUE.toString()); | |
184 | - } else if (alarmResult.isUpdated) { | |
185 | - metaData.putValue(IS_EXISTING_ALARM, Boolean.TRUE.toString()); | |
186 | - } else if (alarmResult.isCleared) { | |
187 | - metaData.putValue(IS_CLEARED_ALARM, Boolean.TRUE.toString()); | |
188 | - } | |
189 | - return ctx.transformMsg(originalMsg, "ALARM", originalMsg.getOriginator(), metaData, data); | |
190 | - } | |
191 | - | |
192 | - | |
193 | - @Override | |
194 | - public void destroy() { | |
195 | - if (createJsEngine != null) { | |
196 | - createJsEngine.destroy(); | |
197 | - } | |
198 | - if (clearJsEngine != null) { | |
199 | - clearJsEngine.destroy(); | |
200 | - } | |
201 | - if (buildDetailsJsEngine != null) { | |
202 | - buildDetailsJsEngine.destroy(); | |
203 | - } | |
204 | - } | |
205 | - | |
206 | - private static class AlarmResult { | |
207 | - boolean isCreated; | |
208 | - boolean isUpdated; | |
209 | - boolean isCleared; | |
210 | - Alarm alarm; | |
211 | - | |
212 | - AlarmResult(boolean isCreated, boolean isUpdated, boolean isCleared, Alarm alarm) { | |
213 | - this.isCreated = isCreated; | |
214 | - this.isUpdated = isUpdated; | |
215 | - this.isCleared = isCleared; | |
216 | - this.alarm = alarm; | |
217 | - } | |
218 | - } | |
219 | -} |
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 | +package org.thingsboard.rule.engine.action; | |
17 | + | |
18 | +import com.fasterxml.jackson.databind.JsonNode; | |
19 | +import com.google.common.util.concurrent.AsyncFunction; | |
20 | +import com.google.common.util.concurrent.Futures; | |
21 | +import com.google.common.util.concurrent.ListenableFuture; | |
22 | +import lombok.extern.slf4j.Slf4j; | |
23 | +import org.thingsboard.rule.engine.TbNodeUtils; | |
24 | +import org.thingsboard.rule.engine.api.RuleNode; | |
25 | +import org.thingsboard.rule.engine.api.TbContext; | |
26 | +import org.thingsboard.rule.engine.api.TbNodeConfiguration; | |
27 | +import org.thingsboard.rule.engine.api.TbNodeException; | |
28 | +import org.thingsboard.server.common.data.alarm.Alarm; | |
29 | +import org.thingsboard.server.common.data.alarm.AlarmStatus; | |
30 | +import org.thingsboard.server.common.data.plugin.ComponentType; | |
31 | +import org.thingsboard.server.common.msg.TbMsg; | |
32 | + | |
33 | +@Slf4j | |
34 | +@RuleNode( | |
35 | + type = ComponentType.ACTION, | |
36 | + name = "clear alarm", relationTypes = {"Cleared", "False"}, | |
37 | + configClazz = TbClearAlarmNodeConfiguration.class, | |
38 | + nodeDescription = "Clear Alarm", | |
39 | + nodeDetails = | |
40 | + "Details - JS function that creates JSON object based on incoming message. This object will be added into Alarm.details field.\n" + | |
41 | + "Node output:\n" + | |
42 | + "If alarm was not cleared, original message is returned. Otherwise new Message returned with type 'ALARM', Alarm object in 'msg' property and 'matadata' will contains 'isClearedAlarm' property " + | |
43 | + "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" + | |
44 | + "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>", | |
45 | + uiResources = {"static/rulenode/rulenode-core-config.js"}, | |
46 | + configDirective = "tbActionNodeClearAlarmConfig", | |
47 | + icon = "notifications_off" | |
48 | +) | |
49 | +public class TbClearAlarmNode extends TbAbstractAlarmNode<TbClearAlarmNodeConfiguration> { | |
50 | + | |
51 | + @Override | |
52 | + protected TbClearAlarmNodeConfiguration loadAlarmNodeConfig(TbNodeConfiguration configuration) throws TbNodeException { | |
53 | + return TbNodeUtils.convert(configuration, TbClearAlarmNodeConfiguration.class); | |
54 | + } | |
55 | + | |
56 | + @Override | |
57 | + protected ListenableFuture<AlarmResult> processAlarm(TbContext ctx, TbMsg msg) { | |
58 | + ListenableFuture<Alarm> latest = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), config.getAlarmType()); | |
59 | + return Futures.transform(latest, (AsyncFunction<Alarm, AlarmResult>) a -> { | |
60 | + if (a != null && !a.getStatus().isCleared()) { | |
61 | + return clearAlarm(ctx, msg, a); | |
62 | + } | |
63 | + return Futures.immediateFuture(new AlarmResult(false, false, false, null)); | |
64 | + }, ctx.getDbCallbackExecutor()); | |
65 | + } | |
66 | + | |
67 | + private ListenableFuture<AlarmResult> clearAlarm(TbContext ctx, TbMsg msg, Alarm alarm) { | |
68 | + ListenableFuture<JsonNode> asyncDetails = buildAlarmDetails(ctx, msg, alarm.getDetails()); | |
69 | + return Futures.transform(asyncDetails, (AsyncFunction<JsonNode, AlarmResult>) details -> { | |
70 | + ListenableFuture<Boolean> clearFuture = ctx.getAlarmService().clearAlarm(alarm.getId(), details, System.currentTimeMillis()); | |
71 | + return Futures.transform(clearFuture, (AsyncFunction<Boolean, AlarmResult>) cleared -> { | |
72 | + if (cleared && details != null) { | |
73 | + alarm.setDetails(details); | |
74 | + } | |
75 | + alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK); | |
76 | + return Futures.immediateFuture(new AlarmResult(false, false, true, alarm)); | |
77 | + }); | |
78 | + }, ctx.getDbCallbackExecutor()); | |
79 | + } | |
80 | +} | ... | ... |
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 | +package org.thingsboard.rule.engine.action; | |
17 | + | |
18 | +import lombok.Data; | |
19 | +import org.thingsboard.rule.engine.api.NodeConfiguration; | |
20 | +import org.thingsboard.server.common.data.alarm.AlarmSeverity; | |
21 | + | |
22 | +@Data | |
23 | +public class TbClearAlarmNodeConfiguration extends TbAbstractAlarmNodeConfiguration implements NodeConfiguration<TbClearAlarmNodeConfiguration> { | |
24 | + | |
25 | + @Override | |
26 | + public TbClearAlarmNodeConfiguration defaultConfiguration() { | |
27 | + TbClearAlarmNodeConfiguration configuration = new TbClearAlarmNodeConfiguration(); | |
28 | + configuration.setAlarmDetailsBuildJs("var details = {};\n" + | |
29 | + "if (metadata.prevAlarmDetails) {\n" + | |
30 | + " details = JSON.parse(metadata.prevAlarmDetails);\n" + | |
31 | + "}\n" + | |
32 | + "return details;"); | |
33 | + configuration.setAlarmType("General Alarm"); | |
34 | + return configuration; | |
35 | + } | |
36 | +} | ... | ... |
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 | +package org.thingsboard.rule.engine.action; | |
17 | + | |
18 | +import com.fasterxml.jackson.databind.JsonNode; | |
19 | +import com.google.common.base.Function; | |
20 | +import com.google.common.util.concurrent.AsyncFunction; | |
21 | +import com.google.common.util.concurrent.Futures; | |
22 | +import com.google.common.util.concurrent.ListenableFuture; | |
23 | +import lombok.extern.slf4j.Slf4j; | |
24 | +import org.thingsboard.rule.engine.TbNodeUtils; | |
25 | +import org.thingsboard.rule.engine.api.RuleNode; | |
26 | +import org.thingsboard.rule.engine.api.TbContext; | |
27 | +import org.thingsboard.rule.engine.api.TbNodeConfiguration; | |
28 | +import org.thingsboard.rule.engine.api.TbNodeException; | |
29 | +import org.thingsboard.server.common.data.alarm.Alarm; | |
30 | +import org.thingsboard.server.common.data.alarm.AlarmStatus; | |
31 | +import org.thingsboard.server.common.data.id.TenantId; | |
32 | +import org.thingsboard.server.common.data.plugin.ComponentType; | |
33 | +import org.thingsboard.server.common.msg.TbMsg; | |
34 | + | |
35 | +@Slf4j | |
36 | +@RuleNode( | |
37 | + type = ComponentType.ACTION, | |
38 | + name = "create alarm", relationTypes = {"Created", "Updated", "False"}, | |
39 | + configClazz = TbCreateAlarmNodeConfiguration.class, | |
40 | + nodeDescription = "Create or Update Alarm", | |
41 | + nodeDetails = | |
42 | + "Details - JS function that creates JSON object based on incoming message. This object will be added into Alarm.details field.\n" + | |
43 | + "Node output:\n" + | |
44 | + "If alarm was not created, original message is returned. Otherwise new Message returned with type 'ALARM', Alarm object in 'msg' property and 'matadata' will contains one of those properties 'isNewAlarm/isExistingAlarm' " + | |
45 | + "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" + | |
46 | + "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>", | |
47 | + uiResources = {"static/rulenode/rulenode-core-config.js"}, | |
48 | + configDirective = "tbActionNodeCreateAlarmConfig", | |
49 | + icon = "notifications_active" | |
50 | +) | |
51 | +public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConfiguration> { | |
52 | + | |
53 | + @Override | |
54 | + protected TbCreateAlarmNodeConfiguration loadAlarmNodeConfig(TbNodeConfiguration configuration) throws TbNodeException { | |
55 | + return TbNodeUtils.convert(configuration, TbCreateAlarmNodeConfiguration.class); | |
56 | + } | |
57 | + | |
58 | + @Override | |
59 | + protected ListenableFuture<AlarmResult> processAlarm(TbContext ctx, TbMsg msg) { | |
60 | + ListenableFuture<Alarm> latest = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), config.getAlarmType()); | |
61 | + return Futures.transform(latest, (AsyncFunction<Alarm, AlarmResult>) a -> { | |
62 | + if (a == null || a.getStatus().isCleared()) { | |
63 | + return createNewAlarm(ctx, msg); | |
64 | + } else { | |
65 | + return updateAlarm(ctx, msg, a); | |
66 | + } | |
67 | + }, ctx.getDbCallbackExecutor()); | |
68 | + | |
69 | + } | |
70 | + | |
71 | + private ListenableFuture<AlarmResult> createNewAlarm(TbContext ctx, TbMsg msg) { | |
72 | + ListenableFuture<Alarm> asyncAlarm = Futures.transform(buildAlarmDetails(ctx, msg, null), | |
73 | + (Function<JsonNode, Alarm>) details -> buildAlarm(msg, details, ctx.getTenantId())); | |
74 | + ListenableFuture<Alarm> asyncCreated = Futures.transform(asyncAlarm, | |
75 | + (Function<Alarm, Alarm>) alarm -> ctx.getAlarmService().createOrUpdateAlarm(alarm), ctx.getDbCallbackExecutor()); | |
76 | + return Futures.transform(asyncCreated, (Function<Alarm, AlarmResult>) alarm -> new AlarmResult(true, false, false, alarm)); | |
77 | + } | |
78 | + | |
79 | + private ListenableFuture<AlarmResult> updateAlarm(TbContext ctx, TbMsg msg, Alarm alarm) { | |
80 | + ListenableFuture<Alarm> asyncUpdated = Futures.transform(buildAlarmDetails(ctx, msg, alarm.getDetails()), (Function<JsonNode, Alarm>) details -> { | |
81 | + alarm.setSeverity(config.getSeverity()); | |
82 | + alarm.setPropagate(config.isPropagate()); | |
83 | + alarm.setDetails(details); | |
84 | + alarm.setEndTs(System.currentTimeMillis()); | |
85 | + return ctx.getAlarmService().createOrUpdateAlarm(alarm); | |
86 | + }, ctx.getDbCallbackExecutor()); | |
87 | + | |
88 | + return Futures.transform(asyncUpdated, (Function<Alarm, AlarmResult>) a -> new AlarmResult(false, true, false, a)); | |
89 | + } | |
90 | + | |
91 | + private Alarm buildAlarm(TbMsg msg, JsonNode details, TenantId tenantId) { | |
92 | + return Alarm.builder() | |
93 | + .tenantId(tenantId) | |
94 | + .originator(msg.getOriginator()) | |
95 | + .status(AlarmStatus.ACTIVE_UNACK) | |
96 | + .severity(config.getSeverity()) | |
97 | + .propagate(config.isPropagate()) | |
98 | + .type(config.getAlarmType()) | |
99 | + //todo-vp: alarm date should be taken from Message or current Time should be used? | |
100 | +// .startTs(System.currentTimeMillis()) | |
101 | +// .endTs(System.currentTimeMillis()) | |
102 | + .details(details) | |
103 | + .build(); | |
104 | + } | |
105 | + | |
106 | +} | ... | ... |
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java
renamed from
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmNodeConfiguration.java
... | ... | @@ -20,22 +20,19 @@ import org.thingsboard.rule.engine.api.NodeConfiguration; |
20 | 20 | import org.thingsboard.server.common.data.alarm.AlarmSeverity; |
21 | 21 | |
22 | 22 | @Data |
23 | -public class TbAlarmNodeConfiguration implements NodeConfiguration { | |
23 | +public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfiguration implements NodeConfiguration<TbCreateAlarmNodeConfiguration> { | |
24 | 24 | |
25 | - private String createConditionJs; | |
26 | - private String clearConditionJs; | |
27 | - private String alarmDetailsBuildJs; | |
28 | - private String alarmType; | |
29 | 25 | private AlarmSeverity severity; |
30 | 26 | private boolean propagate; |
31 | 27 | |
32 | - | |
33 | 28 | @Override |
34 | - public TbAlarmNodeConfiguration defaultConfiguration() { | |
35 | - TbAlarmNodeConfiguration configuration = new TbAlarmNodeConfiguration(); | |
36 | - configuration.setCreateConditionJs("return 'incoming message = ' + msg + meta;"); | |
37 | - configuration.setClearConditionJs("return 'incoming message = ' + msg + meta;"); | |
38 | - configuration.setAlarmDetailsBuildJs("return 'incoming message = ' + msg + meta;"); | |
29 | + public TbCreateAlarmNodeConfiguration defaultConfiguration() { | |
30 | + TbCreateAlarmNodeConfiguration configuration = new TbCreateAlarmNodeConfiguration(); | |
31 | + configuration.setAlarmDetailsBuildJs("var details = {};\n" + | |
32 | + "if (metadata.prevAlarmDetails) {\n" + | |
33 | + " details = JSON.parse(metadata.prevAlarmDetails);\n" + | |
34 | + "}\n"+ | |
35 | + "return details;"); | |
39 | 36 | configuration.setAlarmType("General Alarm"); |
40 | 37 | configuration.setSeverity(AlarmSeverity.CRITICAL); |
41 | 38 | configuration.setPropagate(false); | ... | ... |
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 | +package org.thingsboard.rule.engine.data; | |
17 | + | |
18 | +import lombok.Data; | |
19 | +import org.thingsboard.server.common.data.relation.EntitySearchDirection; | |
20 | + | |
21 | +import java.util.List; | |
22 | + | |
23 | +@Data | |
24 | +public class DeviceRelationsQuery { | |
25 | + private EntitySearchDirection direction; | |
26 | + private int maxLevel = 1; | |
27 | + private String relationType; | |
28 | + private List<String> deviceTypes; | |
29 | +} | ... | ... |
... | ... | @@ -52,7 +52,7 @@ public class TbJsFilterNode implements TbNode { |
52 | 52 | public void onMsg(TbContext ctx, TbMsg msg) { |
53 | 53 | ListeningExecutor jsExecutor = ctx.getJsExecutor(); |
54 | 54 | withCallback(jsExecutor.executeAsync(() -> jsEngine.executeFilter(msg)), |
55 | - filterResult -> ctx.tellNext(msg, Boolean.toString(filterResult)), | |
55 | + filterResult -> ctx.tellNext(msg, filterResult.booleanValue() ? "True" : "False"), | |
56 | 56 | t -> ctx.tellError(msg, t)); |
57 | 57 | } |
58 | 58 | ... | ... |
... | ... | @@ -26,8 +26,7 @@ public class TbJsFilterNodeConfiguration implements NodeConfiguration<TbJsFilter |
26 | 26 | @Override |
27 | 27 | public TbJsFilterNodeConfiguration defaultConfiguration() { |
28 | 28 | TbJsFilterNodeConfiguration configuration = new TbJsFilterNodeConfiguration(); |
29 | - configuration.setJsScript("return msg.passed < 15 && msg.name === 'Vit' " + | |
30 | - "&& metadata.temp == 10 && msg.bigObj.prop == 42 && msgType === 'POST_TELEMETRY';"); | |
29 | + configuration.setJsScript("return msg.temperature > 20;"); | |
31 | 30 | return configuration; |
32 | 31 | } |
33 | 32 | } | ... | ... |
... | ... | @@ -31,8 +31,8 @@ public class TbJsSwitchNodeConfiguration implements NodeConfiguration<TbJsSwitch |
31 | 31 | TbJsSwitchNodeConfiguration configuration = new TbJsSwitchNodeConfiguration(); |
32 | 32 | configuration.setJsScript("function nextRelation(metadata, msg) {\n" + |
33 | 33 | " return ['one','nine'];\n" + |
34 | - "};\n" + | |
35 | - "if(msgType === 'POST_TELEMETRY') {\n" + | |
34 | + "}\n" + | |
35 | + "if(msgType === 'POST_TELEMETRY_REQUEST') {\n" + | |
36 | 36 | " return ['two'];\n" + |
37 | 37 | "}\n" + |
38 | 38 | "return nextRelation(metadata, msg);"); | ... | ... |
... | ... | @@ -45,7 +45,7 @@ public class TbMsgTypeFilterNode implements TbNode { |
45 | 45 | |
46 | 46 | @Override |
47 | 47 | public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { |
48 | - ctx.tellNext(msg, Boolean.toString(config.getMessageTypes().contains(msg.getType()))); | |
48 | + ctx.tellNext(msg, config.getMessageTypes().contains(msg.getType()) ? "True" : "False"); | |
49 | 49 | } |
50 | 50 | |
51 | 51 | @Override | ... | ... |
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 | +package org.thingsboard.rule.engine.metadata; | |
17 | + | |
18 | +import com.google.common.base.Function; | |
19 | +import com.google.common.util.concurrent.Futures; | |
20 | +import com.google.common.util.concurrent.ListenableFuture; | |
21 | +import org.apache.commons.collections.CollectionUtils; | |
22 | +import org.thingsboard.rule.engine.api.TbContext; | |
23 | +import org.thingsboard.rule.engine.api.TbNode; | |
24 | +import org.thingsboard.rule.engine.api.TbNodeConfiguration; | |
25 | +import org.thingsboard.rule.engine.api.TbNodeException; | |
26 | +import org.thingsboard.server.common.data.id.EntityId; | |
27 | +import org.thingsboard.server.common.data.kv.AttributeKvEntry; | |
28 | +import org.thingsboard.server.common.data.kv.TsKvEntry; | |
29 | +import org.thingsboard.server.common.msg.TbMsg; | |
30 | + | |
31 | +import java.util.List; | |
32 | + | |
33 | +import static org.thingsboard.rule.engine.DonAsynchron.withCallback; | |
34 | +import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE; | |
35 | +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; | |
36 | +import static org.thingsboard.server.common.data.DataConstants.*; | |
37 | + | |
38 | +public abstract class TbAbstractGetAttributesNode<C extends TbGetAttributesNodeConfiguration, T extends EntityId> implements TbNode { | |
39 | + | |
40 | + protected C config; | |
41 | + | |
42 | + @Override | |
43 | + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { | |
44 | + this.config = loadGetAttributesNodeConfig(configuration); | |
45 | + } | |
46 | + | |
47 | + protected abstract C loadGetAttributesNodeConfig(TbNodeConfiguration configuration) throws TbNodeException; | |
48 | + | |
49 | + @Override | |
50 | + public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { | |
51 | + try { | |
52 | + withCallback( | |
53 | + findEntityAsync(ctx, msg.getOriginator()), | |
54 | + entityId -> safePutAttributes(ctx, msg, entityId), | |
55 | + t -> ctx.tellError(msg, t), ctx.getDbCallbackExecutor()); | |
56 | + } catch (Throwable th) { | |
57 | + ctx.tellError(msg, th); | |
58 | + } | |
59 | + } | |
60 | + | |
61 | + private void safePutAttributes(TbContext ctx, TbMsg msg, T entityId) { | |
62 | + if(entityId == null || entityId.isNullUid()) { | |
63 | + ctx.tellNext(msg, FAILURE); | |
64 | + return; | |
65 | + } | |
66 | + ListenableFuture<List<Void>> allFutures = Futures.allAsList( | |
67 | + putLatestTelemetry(ctx, entityId, msg, config.getLatestTsKeyNames()), | |
68 | + putAttrAsync(ctx, entityId, msg, CLIENT_SCOPE, config.getClientAttributeNames(), "cs_"), | |
69 | + putAttrAsync(ctx, entityId, msg, SHARED_SCOPE, config.getSharedAttributeNames(), "shared_"), | |
70 | + putAttrAsync(ctx, entityId, msg, SERVER_SCOPE, config.getServerAttributeNames(), "ss_") | |
71 | + ); | |
72 | + withCallback(allFutures, i -> ctx.tellNext(msg, SUCCESS), t -> ctx.tellError(msg, t)); | |
73 | + } | |
74 | + | |
75 | + private ListenableFuture<Void> putAttrAsync(TbContext ctx, EntityId entityId, TbMsg msg, String scope, List<String> keys, String prefix) { | |
76 | + if (CollectionUtils.isEmpty(keys)) { | |
77 | + return Futures.immediateFuture(null); | |
78 | + } | |
79 | + ListenableFuture<List<AttributeKvEntry>> latest = ctx.getAttributesService().find(entityId, scope, keys); | |
80 | + return Futures.transform(latest, (Function<? super List<AttributeKvEntry>, Void>) l -> { | |
81 | + l.forEach(r -> msg.getMetaData().putValue(prefix + r.getKey(), r.getValueAsString())); | |
82 | + return null; | |
83 | + }); | |
84 | + } | |
85 | + | |
86 | + private ListenableFuture<Void> putLatestTelemetry(TbContext ctx, EntityId entityId, TbMsg msg, List<String> keys) { | |
87 | + if (CollectionUtils.isEmpty(keys)) { | |
88 | + return Futures.immediateFuture(null); | |
89 | + } | |
90 | + ListenableFuture<List<TsKvEntry>> latest = ctx.getTimeseriesService().findLatest(entityId, keys); | |
91 | + return Futures.transform(latest, (Function<? super List<TsKvEntry>, Void>) l -> { | |
92 | + l.forEach(r -> msg.getMetaData().putValue(r.getKey(), r.getValueAsString())); | |
93 | + return null; | |
94 | + }); | |
95 | + } | |
96 | + | |
97 | + @Override | |
98 | + public void destroy() { | |
99 | + | |
100 | + } | |
101 | + | |
102 | + protected abstract ListenableFuture<T> findEntityAsync(TbContext ctx, EntityId originator); | |
103 | +} | ... | ... |
... | ... | @@ -15,23 +15,16 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.rule.engine.metadata; |
17 | 17 | |
18 | -import com.google.common.base.Function; | |
19 | 18 | import com.google.common.util.concurrent.Futures; |
20 | 19 | import com.google.common.util.concurrent.ListenableFuture; |
21 | 20 | import lombok.extern.slf4j.Slf4j; |
22 | -import org.apache.commons.collections.CollectionUtils; | |
23 | 21 | import org.thingsboard.rule.engine.TbNodeUtils; |
24 | -import org.thingsboard.rule.engine.api.*; | |
25 | -import org.thingsboard.server.common.data.kv.AttributeKvEntry; | |
26 | -import org.thingsboard.server.common.data.kv.TsKvEntry; | |
22 | +import org.thingsboard.rule.engine.api.RuleNode; | |
23 | +import org.thingsboard.rule.engine.api.TbContext; | |
24 | +import org.thingsboard.rule.engine.api.TbNodeConfiguration; | |
25 | +import org.thingsboard.rule.engine.api.TbNodeException; | |
26 | +import org.thingsboard.server.common.data.id.EntityId; | |
27 | 27 | import org.thingsboard.server.common.data.plugin.ComponentType; |
28 | -import org.thingsboard.server.common.msg.TbMsg; | |
29 | - | |
30 | -import java.util.List; | |
31 | - | |
32 | -import static org.thingsboard.rule.engine.DonAsynchron.withCallback; | |
33 | -import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; | |
34 | -import static org.thingsboard.server.common.data.DataConstants.*; | |
35 | 28 | |
36 | 29 | /** |
37 | 30 | * Created by ashvayka on 19.01.18. |
... | ... | @@ -47,50 +40,15 @@ import static org.thingsboard.server.common.data.DataConstants.*; |
47 | 40 | "<code>metadata.cs_temperature</code> or <code>metadata.shared_limit</code> ", |
48 | 41 | uiResources = {"static/rulenode/rulenode-core-config.js"}, |
49 | 42 | configDirective = "tbEnrichmentNodeOriginatorAttributesConfig") |
50 | -public class TbGetAttributesNode implements TbNode { | |
51 | - | |
52 | - private TbGetAttributesNodeConfiguration config; | |
43 | +public class TbGetAttributesNode extends TbAbstractGetAttributesNode<TbGetAttributesNodeConfiguration, EntityId> { | |
53 | 44 | |
54 | 45 | @Override |
55 | - public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { | |
56 | - this.config = TbNodeUtils.convert(configuration, TbGetAttributesNodeConfiguration.class); | |
46 | + protected TbGetAttributesNodeConfiguration loadGetAttributesNodeConfig(TbNodeConfiguration configuration) throws TbNodeException { | |
47 | + return TbNodeUtils.convert(configuration, TbGetAttributesNodeConfiguration.class); | |
57 | 48 | } |
58 | 49 | |
59 | 50 | @Override |
60 | - public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { | |
61 | - ListenableFuture<List<Void>> allFutures = Futures.allAsList( | |
62 | - putLatestTelemetry(ctx, msg, config.getLatestTsKeyNames()), | |
63 | - putAttrAsync(ctx, msg, CLIENT_SCOPE, config.getClientAttributeNames(), "cs_"), | |
64 | - putAttrAsync(ctx, msg, SHARED_SCOPE, config.getSharedAttributeNames(), "shared_"), | |
65 | - putAttrAsync(ctx, msg, SERVER_SCOPE, config.getServerAttributeNames(), "ss_") | |
66 | - ); | |
67 | - withCallback(allFutures, i -> ctx.tellNext(msg, SUCCESS), t -> ctx.tellError(msg, t)); | |
68 | - } | |
69 | - | |
70 | - private ListenableFuture<Void> putAttrAsync(TbContext ctx, TbMsg msg, String scope, List<String> keys, String prefix) { | |
71 | - if (CollectionUtils.isEmpty(keys)) { | |
72 | - return Futures.immediateFuture(null); | |
73 | - } | |
74 | - ListenableFuture<List<AttributeKvEntry>> latest = ctx.getAttributesService().find(msg.getOriginator(), scope, keys); | |
75 | - return Futures.transform(latest, (Function<? super List<AttributeKvEntry>, Void>) l -> { | |
76 | - l.forEach(r -> msg.getMetaData().putValue(prefix + r.getKey(), r.getValueAsString())); | |
77 | - return null; | |
78 | - }); | |
79 | - } | |
80 | - | |
81 | - private ListenableFuture<Void> putLatestTelemetry(TbContext ctx, TbMsg msg, List<String> keys) { | |
82 | - if (CollectionUtils.isEmpty(keys)) { | |
83 | - return Futures.immediateFuture(null); | |
84 | - } | |
85 | - ListenableFuture<List<TsKvEntry>> latest = ctx.getTimeseriesService().findLatest(msg.getOriginator(), keys); | |
86 | - return Futures.transform(latest, (Function<? super List<TsKvEntry>, Void>) l -> { | |
87 | - l.forEach(r -> msg.getMetaData().putValue(r.getKey(), r.getValueAsString())); | |
88 | - return null; | |
89 | - }); | |
90 | - } | |
91 | - | |
92 | - @Override | |
93 | - public void destroy() { | |
94 | - | |
51 | + protected ListenableFuture<EntityId> findEntityAsync(TbContext ctx, EntityId originator) { | |
52 | + return Futures.immediateFuture(originator); | |
95 | 53 | } |
96 | 54 | } | ... | ... |
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 | +package org.thingsboard.rule.engine.metadata; | |
17 | + | |
18 | +import com.google.common.util.concurrent.ListenableFuture; | |
19 | +import lombok.extern.slf4j.Slf4j; | |
20 | +import org.thingsboard.rule.engine.TbNodeUtils; | |
21 | +import org.thingsboard.rule.engine.api.RuleNode; | |
22 | +import org.thingsboard.rule.engine.api.TbContext; | |
23 | +import org.thingsboard.rule.engine.api.TbNodeConfiguration; | |
24 | +import org.thingsboard.rule.engine.api.TbNodeException; | |
25 | +import org.thingsboard.rule.engine.util.EntitiesRelatedDeviceIdAsyncLoader; | |
26 | +import org.thingsboard.server.common.data.id.DeviceId; | |
27 | +import org.thingsboard.server.common.data.id.EntityId; | |
28 | +import org.thingsboard.server.common.data.plugin.ComponentType; | |
29 | + | |
30 | +@Slf4j | |
31 | +@RuleNode(type = ComponentType.ENRICHMENT, | |
32 | + name = "device attributes", | |
33 | + configClazz = TbGetDeviceAttrNodeConfiguration.class, | |
34 | + nodeDescription = "Add Originators Related Device Attributes or Latest Telemetry into Message Metadata", | |
35 | + nodeDetails = "If Attributes enrichment configured, <b>CLIENT/SHARED/SERVER</b> attributes are added into Message metadata " + | |
36 | + "with specific prefix: <i>cs/shared/ss</i>. Latest telemetry value added into metadata without prefix. " + | |
37 | + "To access those attributes in other nodes this template can be used " + | |
38 | + "<code>metadata.cs_temperature</code> or <code>metadata.shared_limit</code> ", | |
39 | + uiResources = {"static/rulenode/rulenode-core-config.js"}, | |
40 | + configDirective = "tbEnrichmentNodeDeviceAttributesConfig") | |
41 | +public class TbGetDeviceAttrNode extends TbAbstractGetAttributesNode<TbGetDeviceAttrNodeConfiguration, DeviceId> { | |
42 | + | |
43 | + @Override | |
44 | + protected TbGetDeviceAttrNodeConfiguration loadGetAttributesNodeConfig(TbNodeConfiguration configuration) throws TbNodeException { | |
45 | + return TbNodeUtils.convert(configuration, TbGetDeviceAttrNodeConfiguration.class); | |
46 | + } | |
47 | + | |
48 | + @Override | |
49 | + protected ListenableFuture<DeviceId> findEntityAsync(TbContext ctx, EntityId originator) { | |
50 | + return EntitiesRelatedDeviceIdAsyncLoader.findDeviceAsync(ctx, originator, config.getDeviceRelationsQuery()); | |
51 | + } | |
52 | +} | ... | ... |
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 | +package org.thingsboard.rule.engine.metadata; | |
17 | + | |
18 | +import lombok.Data; | |
19 | +import org.thingsboard.rule.engine.data.DeviceRelationsQuery; | |
20 | +import org.thingsboard.server.common.data.relation.EntityRelation; | |
21 | +import org.thingsboard.server.common.data.relation.EntitySearchDirection; | |
22 | + | |
23 | +import java.util.Collections; | |
24 | + | |
25 | +@Data | |
26 | +public class TbGetDeviceAttrNodeConfiguration extends TbGetAttributesNodeConfiguration { | |
27 | + | |
28 | + private DeviceRelationsQuery deviceRelationsQuery; | |
29 | + | |
30 | + @Override | |
31 | + public TbGetDeviceAttrNodeConfiguration defaultConfiguration() { | |
32 | + TbGetDeviceAttrNodeConfiguration configuration = new TbGetDeviceAttrNodeConfiguration(); | |
33 | + configuration.setClientAttributeNames(Collections.emptyList()); | |
34 | + configuration.setSharedAttributeNames(Collections.emptyList()); | |
35 | + configuration.setServerAttributeNames(Collections.emptyList()); | |
36 | + configuration.setLatestTsKeyNames(Collections.emptyList()); | |
37 | + | |
38 | + DeviceRelationsQuery deviceRelationsQuery = new DeviceRelationsQuery(); | |
39 | + deviceRelationsQuery.setDirection(EntitySearchDirection.FROM); | |
40 | + deviceRelationsQuery.setMaxLevel(1); | |
41 | + deviceRelationsQuery.setRelationType(EntityRelation.CONTAINS_TYPE); | |
42 | + deviceRelationsQuery.setDeviceTypes(Collections.singletonList("default")); | |
43 | + | |
44 | + configuration.setDeviceRelationsQuery(deviceRelationsQuery); | |
45 | + | |
46 | + return configuration; | |
47 | + } | |
48 | +} | ... | ... |
... | ... | @@ -34,7 +34,7 @@ public class TbGetEntityAttrNodeConfiguration implements NodeConfiguration<TbGet |
34 | 34 | Map<String, String> attrMapping = new HashMap<>(); |
35 | 35 | attrMapping.putIfAbsent("temperature", "tempo"); |
36 | 36 | configuration.setAttrMapping(attrMapping); |
37 | - configuration.setTelemetry(true); | |
37 | + configuration.setTelemetry(false); | |
38 | 38 | return configuration; |
39 | 39 | } |
40 | 40 | } | ... | ... |
... | ... | @@ -36,7 +36,7 @@ public class TbGetRelatedAttrNodeConfiguration extends TbGetEntityAttrNodeConfig |
36 | 36 | Map<String, String> attrMapping = new HashMap<>(); |
37 | 37 | attrMapping.putIfAbsent("temperature", "tempo"); |
38 | 38 | configuration.setAttrMapping(attrMapping); |
39 | - configuration.setTelemetry(true); | |
39 | + configuration.setTelemetry(false); | |
40 | 40 | |
41 | 41 | RelationsQuery relationsQuery = new RelationsQuery(); |
42 | 42 | relationsQuery.setDirection(EntitySearchDirection.FROM); | ... | ... |
... | ... | @@ -24,9 +24,9 @@ import io.netty.handler.ssl.SslContext; |
24 | 24 | import io.netty.handler.ssl.SslContextBuilder; |
25 | 25 | import io.netty.util.concurrent.Future; |
26 | 26 | import lombok.extern.slf4j.Slf4j; |
27 | -import nl.jk5.mqtt.MqttClient; | |
28 | -import nl.jk5.mqtt.MqttClientConfig; | |
29 | -import nl.jk5.mqtt.MqttConnectResult; | |
27 | +import org.thingsboard.mqtt.MqttClient; | |
28 | +import org.thingsboard.mqtt.MqttClientConfig; | |
29 | +import org.thingsboard.mqtt.MqttConnectResult; | |
30 | 30 | import org.springframework.util.StringUtils; |
31 | 31 | import org.thingsboard.rule.engine.TbNodeUtils; |
32 | 32 | import org.thingsboard.rule.engine.api.*; |
... | ... | @@ -80,8 +80,7 @@ public class TbMqttNode implements TbNode { |
80 | 80 | this.mqttClient.publish(topic, Unpooled.wrappedBuffer(msg.getData().getBytes(UTF8)), MqttQoS.AT_LEAST_ONCE) |
81 | 81 | .addListener(future -> { |
82 | 82 | if (future.isSuccess()) { |
83 | - TbMsg next = ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), msg.getData()); | |
84 | - ctx.tellNext(next, TbRelationTypes.SUCCESS); | |
83 | + ctx.tellNext(msg, TbRelationTypes.SUCCESS); | |
85 | 84 | } else { |
86 | 85 | TbMsg next = processException(ctx, msg, future.cause()); |
87 | 86 | ctx.tellNext(next, TbRelationTypes.FAILURE, future.cause()); | ... | ... |
... | ... | @@ -18,7 +18,7 @@ package org.thingsboard.rule.engine.mqtt.credentials; |
18 | 18 | |
19 | 19 | import io.netty.handler.ssl.SslContext; |
20 | 20 | import lombok.Data; |
21 | -import nl.jk5.mqtt.MqttClientConfig; | |
21 | +import org.thingsboard.mqtt.MqttClientConfig; | |
22 | 22 | |
23 | 23 | import java.util.Optional; |
24 | 24 | ... | ... |
... | ... | @@ -22,7 +22,7 @@ import io.netty.handler.ssl.SslContext; |
22 | 22 | import io.netty.handler.ssl.SslContextBuilder; |
23 | 23 | import lombok.Data; |
24 | 24 | import lombok.extern.slf4j.Slf4j; |
25 | -import nl.jk5.mqtt.MqttClientConfig; | |
25 | +import org.thingsboard.mqtt.MqttClientConfig; | |
26 | 26 | import org.apache.commons.codec.binary.Base64; |
27 | 27 | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
28 | 28 | import org.bouncycastle.openssl.PEMDecryptorProvider; | ... | ... |
... | ... | @@ -19,7 +19,7 @@ package org.thingsboard.rule.engine.mqtt.credentials; |
19 | 19 | import com.fasterxml.jackson.annotation.JsonSubTypes; |
20 | 20 | import com.fasterxml.jackson.annotation.JsonTypeInfo; |
21 | 21 | import io.netty.handler.ssl.SslContext; |
22 | -import nl.jk5.mqtt.MqttClientConfig; | |
22 | +import org.thingsboard.mqtt.MqttClientConfig; | |
23 | 23 | |
24 | 24 | import java.util.Optional; |
25 | 25 | ... | ... |
... | ... | @@ -106,7 +106,7 @@ public class TbRabbitMqNode implements TbNode { |
106 | 106 | routingKey, |
107 | 107 | properties, |
108 | 108 | msg.getData().getBytes(UTF8)); |
109 | - return ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), msg.getData()); | |
109 | + return msg; | |
110 | 110 | } |
111 | 111 | |
112 | 112 | private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable t) { | ... | ... |
... | ... | @@ -63,12 +63,22 @@ public class TbMsgTimeseriesNode implements TbNode { |
63 | 63 | ctx.tellError(msg, new IllegalArgumentException("Unsupported msg type: " + msg.getType())); |
64 | 64 | return; |
65 | 65 | } |
66 | - | |
66 | + long ts = -1; | |
67 | + String tsStr = msg.getMetaData().getValue("ts"); | |
68 | + if (!StringUtils.isEmpty(tsStr)) { | |
69 | + try { | |
70 | + ts = Long.parseLong(tsStr); | |
71 | + } catch (NumberFormatException e) {} | |
72 | + } | |
73 | + if (ts == -1) { | |
74 | + ctx.tellError(msg, new IllegalArgumentException("Msg metadata doesn't contain valid ts value: " + msg.getMetaData())); | |
75 | + return; | |
76 | + } | |
67 | 77 | String src = msg.getData(); |
68 | - TelemetryUploadRequest telemetryUploadRequest = JsonConverter.convertToTelemetry(new JsonParser().parse(src)); | |
78 | + TelemetryUploadRequest telemetryUploadRequest = JsonConverter.convertToTelemetry(new JsonParser().parse(src), ts); | |
69 | 79 | Map<Long, List<KvEntry>> tsKvMap = telemetryUploadRequest.getData(); |
70 | 80 | if (tsKvMap == null) { |
71 | - ctx.tellError(msg, new IllegalArgumentException("Msg body us empty: " + src)); | |
81 | + ctx.tellError(msg, new IllegalArgumentException("Msg body is empty: " + src)); | |
72 | 82 | return; |
73 | 83 | } |
74 | 84 | List<TsKvEntry> tsKvEntryList = new ArrayList<>(); | ... | ... |
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 | +package org.thingsboard.rule.engine.util; | |
17 | + | |
18 | +import com.google.common.util.concurrent.AsyncFunction; | |
19 | +import com.google.common.util.concurrent.Futures; | |
20 | +import com.google.common.util.concurrent.ListenableFuture; | |
21 | +import org.apache.commons.collections.CollectionUtils; | |
22 | +import org.thingsboard.rule.engine.api.TbContext; | |
23 | +import org.thingsboard.rule.engine.data.DeviceRelationsQuery; | |
24 | +import org.thingsboard.server.common.data.Device; | |
25 | +import org.thingsboard.server.common.data.device.DeviceSearchQuery; | |
26 | +import org.thingsboard.server.common.data.id.DeviceId; | |
27 | +import org.thingsboard.server.common.data.id.EntityId; | |
28 | +import org.thingsboard.server.common.data.relation.RelationsSearchParameters; | |
29 | +import org.thingsboard.server.dao.device.DeviceService; | |
30 | + | |
31 | +import java.util.List; | |
32 | + | |
33 | +public class EntitiesRelatedDeviceIdAsyncLoader { | |
34 | + | |
35 | + public static ListenableFuture<DeviceId> findDeviceAsync(TbContext ctx, EntityId originator, | |
36 | + DeviceRelationsQuery deviceRelationsQuery) { | |
37 | + DeviceService deviceService = ctx.getDeviceService(); | |
38 | + DeviceSearchQuery query = buildQuery(originator, deviceRelationsQuery); | |
39 | + | |
40 | + ListenableFuture<List<Device>> asyncDevices = deviceService.findDevicesByQuery(query); | |
41 | + | |
42 | + return Futures.transform(asyncDevices, (AsyncFunction<List<Device>, DeviceId>) | |
43 | + d -> CollectionUtils.isNotEmpty(d) ? Futures.immediateFuture(d.get(0).getId()) | |
44 | + : Futures.immediateFuture(null)); | |
45 | + } | |
46 | + | |
47 | + private static DeviceSearchQuery buildQuery(EntityId originator, DeviceRelationsQuery deviceRelationsQuery) { | |
48 | + DeviceSearchQuery query = new DeviceSearchQuery(); | |
49 | + RelationsSearchParameters parameters = new RelationsSearchParameters(originator, | |
50 | + deviceRelationsQuery.getDirection(), deviceRelationsQuery.getMaxLevel()); | |
51 | + query.setParameters(parameters); | |
52 | + query.setRelationType(deviceRelationsQuery.getRelationType()); | |
53 | + query.setDeviceTypes(deviceRelationsQuery.getDeviceTypes()); | |
54 | + return query; | |
55 | + } | |
56 | +} | ... | ... |
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
1 | -!function(e){function t(a){if(n[a])return n[a].exports;var r=n[a]={exports:{},id:a,loaded:!1};return e[a].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),a=e[t[0]];return function(e,t,r){a.apply(this,[e,t,r].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(62)},function(e,t){},1,1,1,function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-create-condition</label> <tb-js-func ng-model=configuration.createConditionJs function-name=isAlarm function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=\"testConditionJs($event, true)\" class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-condition-function' | translate }} </md-button> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-clear-condition</label> <tb-js-func ng-model=configuration.clearConditionJs function-name=isCleared function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=\"testConditionJs($event, false)\" class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-condition-function' | translate }} </md-button> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <section layout=column layout-gt-sm=row> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-severity</label> <md-select required name=severity ng-model=configuration.severity> <md-option ng-repeat=\"(severityKey, severity) in types.alarmSeverity\" ng-value=severityKey> {{ severity.name | translate}} </md-option> </md-select> <div ng-messages=alarmConfigForm.severity.$error> <div ng-message=required translate>tb.rulenode.alarm-severity-required</div> </div> </md-input-container> </section> <md-checkbox aria-label=\"{{ 'tb.rulenode.propagate' | translate }}\" ng-model=configuration.propagate>{{ 'tb.rulenode.propagate' | translate }} </md-checkbox> </section> "},function(e,t){e.exports=' <section ng-form name=attributesConfigForm layout=column> <md-input-container class=md-block> <label translate>attribute.attributes-scope</label> <md-select ng-model=configuration.scope ng-disabled=$root.loading> <md-option ng-repeat="scope in types.attributesScope" ng-value=scope.value> {{scope.name | translate}} </md-option> </md-select> </md-input-container> </section> '},function(e,t){e.exports=" <section class=tb-generator-config ng-form name=generatorConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.message-count</label> <input ng-required=true type=number step=1 name=messageCount ng-model=configuration.msgCount min=0> <div ng-messages=generatorConfigForm.messageCount.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.message-count-required</div> <div ng-message=min translate>tb.rulenode.min-message-count-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=1> <div ng-messages=generatorConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-seconds-message</div> </div> </md-input-container> <div layout=column> <label class=tb-small>{{ 'tb.rulenode.originator' | translate }}</label> <tb-entity-select the-form=generatorConfigForm tb-required=false ng-model=originator> </tb-entity-select> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.generate</label> <tb-js-func ng-model=configuration.jsScript function-name=Generate function-args=\"{{ ['prevMsg', 'prevMetadata', 'prevMsgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-generator-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section ng-form name=kafkaConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=kafkaConfigForm.topicPattern.$error> <div ng-message=required translate>tb.rulenode.topic-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bootstrap-servers</label> <input ng-required=true name=bootstrapServers ng-model=configuration.bootstrapServers> <div ng-messages=kafkaConfigForm.bootstrapServers.$error> <div ng-message=required translate>tb.rulenode.bootstrap-servers-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.retries</label> <input type=number step=1 name=retries ng-model=configuration.retries min=0> <div ng-messages=kafkaConfigForm.retries.$error> <div ng-message=min translate>tb.rulenode.min-retries-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.batch-size-bytes</label> <input type=number step=1 name=batchSize ng-model=configuration.batchSize min=0> <div ng-messages=kafkaConfigForm.batchSize.$error> <div ng-message=min translate>tb.rulenode.min-batch-size-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.linger-ms</label> <input type=number step=1 name=linger ng-model=configuration.linger min=0> <div ng-messages=kafkaConfigForm.linger.$error> <div ng-message=min translate>tb.rulenode.min-linger-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.buffer-memory-bytes</label> <input type=number step=1 name=bufferMemory ng-model=configuration.bufferMemory min=0> <div ng-messages=kafkaConfigForm.bufferMemory.$error> <div ng-message=min translate>tb.rulenode.min-buffer-memory-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.acks</label> <md-select ng-model=configuration.acks ng-disabled=$root.loading> <md-option ng-repeat="ackValue in ackValues" ng-value=ackValue> {{ ackValue }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.key-serializer</label> <input ng-required=true name=keySerializer ng-model=configuration.keySerializer> <div ng-messages=kafkaConfigForm.keySerializer.$error> <div ng-message=required translate>tb.rulenode.key-serializer-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.value-serializer</label> <input ng-required=true name=valueSerializer ng-model=configuration.valueSerializer> <div ng-messages=kafkaConfigForm.valueSerializer.$error> <div ng-message=required translate>tb.rulenode.value-serializer-required</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.other-properties</label> <tb-kv-map-config ng-model=configuration.otherProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.to-string</label> <tb-js-func ng-model=configuration.jsScript function-name=ToString function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-to-string-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-mqtt-config ng-form name=mqttConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=mqttConfigForm.topicPattern.$error> <div translate ng-message=required>tb.rulenode.topic-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.mqtt-topic-pattern-hint</div> </md-input-container> <div flex layout=column layout-gt-sm=row> <md-input-container flex=60 class=md-block> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=mqttConfigForm.host.$error> <div translate ng-message=required>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.port> <div ng-messages=mqttConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.port-required</div> <div translate ng-message=min>tb.rulenode.port-range</div> <div translate ng-message=max>tb.rulenode.port-range</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.connect-timeout</label> <input type=number step=1 min=1 max=200 ng-required=true name=connectTimeoutSec ng-model=configuration.connectTimeoutSec> <div ng-messages=mqttConfigForm.connectTimeoutSec.$error> <div translate ng-message=required>tb.rulenode.connect-timeout-required</div> <div translate ng-message=min>tb.rulenode.connect-timeout-range</div> <div translate ng-message=max>tb.rulenode.connect-timeout-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.client-id</label> <input name=clientId ng-model=configuration.clientId> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-ssl\' | translate }}" ng-model=configuration.ssl> {{ \'tb.rulenode.enable-ssl\' | translate }} </md-checkbox> <md-expansion-panel-group class=tb-credentials-panel-group ng-class="{\'disabled\': $root.loading || readonly}" md-component-id=credentialsPanelGroup> <md-expansion-panel md-component-id=credentialsPanel> <md-expansion-panel-collapsed> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-collapsed> <md-expansion-panel-expanded> <md-expansion-panel-header ng-click="$mdExpansionPanel(\'credentialsPanel\').collapse()"> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-header> <md-expansion-panel-content> <div layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.credentials-type</label> <md-select ng-required=true name=credentialsType ng-model=configuration.credentials.type ng-disabled="$root.loading || readonly" ng-change=credentialsTypeChanged()> <md-option ng-repeat="(credentialsType, credentialsValue) in ruleNodeTypes.mqttCredentialTypes" ng-value=credentialsValue.value> {{credentialsValue.name | translate}} </md-option> </md-select> <div ng-messages=mqttConfigForm.credentialsType.$error> <div translate ng-message=required>tb.rulenode.credentials-type-required</div> </div> </md-input-container> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes.basic.value"> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input ng-required=true name=mqttUsername ng-model=configuration.credentials.username> <div ng-messages=mqttConfigForm.mqttUsername.$error> <div translate ng-message=required>tb.rulenode.username-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input type=password ng-required=true name=mqttPassword ng-model=configuration.credentials.password> <div ng-messages=mqttConfigForm.mqttPassword.$error> <div translate ng-message=required>tb.rulenode.password-required</div> </div> </md-input-container> </section> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes[\'cert.PEM\'].value" class=dropdown-section> <div class=tb-container ng-class="configuration.credentials.caCertFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.ca-cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'caCert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'caCert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=caCertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=caCertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.caCertFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.caCertFileName>{{configuration.credentials.caCertFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.certFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'Cert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'Cert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=CertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=CertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.certFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.certFileName>{{configuration.credentials.certFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.privateKeyFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.private-key</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'privateKey\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'privateKey\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=privateKeySelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=privateKeySelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.privateKeyFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.privateKeyFileName>{{configuration.credentials.privateKeyFileName}}</div> </div> <md-input-container class=md-block> <label translate>tb.rulenode.private-key-password</label> <input type=password name=privateKeyPassword ng-model=configuration.credentials.password> </md-input-container> </section> </div> </md-expansion-panel-content> </md-expansion-panel-expanded> </md-expansion-panel> </md-expansion-panel-group> </section>'},function(e,t){e.exports=' <section ng-form name=rabbitMqConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.exchange-name-pattern</label> <input name=exchangeNamePattern ng-model=configuration.exchangeNamePattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.routing-key-pattern</label> <input name=routingKeyPattern ng-model=configuration.routingKeyPattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.message-properties</label> <md-select ng-model=configuration.messageProperties ng-disabled="$root.loading || readonly"> <md-option ng-repeat="property in messageProperties" ng-value=property> {{ property }} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=rabbitMqConfigForm.host.$error> <div ng-message=required translate>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.port</label> <input ng-required=true type=number step=1 name=port ng-model=configuration.port min=0 max=65535> <div ng-messages=rabbitMqConfigForm.port.$error> <div ng-message=required translate>tb.rulenode.port-required</div> <div ng-message=min translate>tb.rulenode.port-range</div> <div ng-message=max translate>tb.rulenode.port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.virtual-host</label> <input name=virtualHost ng-model=configuration.virtualHost> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=virtualHost ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=virtualHost type=password ng-model=configuration.password> </md-input-container> <md-input-container class=md-block> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.automatic-recovery\' | translate }}" ng-model=ruleNode.automaticRecoveryEnabled>{{ \'tb.rulenode.automatic-recovery\' | translate }} </md-checkbox> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.connection-timeout-ms</label> <input type=number step=1 name=connectionTimeout ng-model=configuration.connectionTimeout min=0> <div ng-messages=rabbitMqConfigForm.connectionTimeout.$error> <div ng-message=min translate>tb.rulenode.min-connection-timeout-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.handshake-timeout-ms</label> <input type=number step=1 name=handshakeTimeout ng-model=configuration.handshakeTimeout min=0> <div ng-messages=rabbitMqConfigForm.handshakeTimeout.$error> <div ng-message=min translate>tb.rulenode.min-handshake-timeout-ms-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.client-properties</label> <tb-kv-map-config ng-model=configuration.clientProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=' <section ng-form name=restApiCallConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.endpoint-url-pattern</label> <input ng-required=true name=endpointUrlPattern ng-model=configuration.restEndpointUrlPattern> <div ng-messages=restApiCallConfigForm.endpointUrlPattern.$error> <div ng-message=required translate>tb.rulenode.endpoint-url-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.endpoint-url-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.request-method</label> <md-select ng-model=configuration.requestMethod ng-disabled=$root.loading> <md-option ng-repeat="type in ruleNodeTypes.httpRequestType" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <label translate class=tb-title>tb.rulenode.headers</label> <tb-kv-map-config ng-model=configuration.headers ng-required=false key-text="\'tb.rulenode.header\'" key-required-text="\'tb.rulenode.header-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section ng-form name=rpcReplyConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.request-id-metadata-attribute</label> <input name=requestIdMetaDataAttribute ng-model=configuration.requestIdMetaDataAttribute> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=rpcRequestConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.timeout-sec</label> <input ng-required=true type=number step=1 name=timeoutInSeconds ng-model=configuration.timeoutInSeconds min=0> <div ng-messages=rpcRequestConfigForm.timeoutInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.timeout-required</div> <div ng-message=min translate>tb.rulenode.min-timeout-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=sendEmailConfigForm layout=column> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}" ng-model=configuration.useSystemSmtpSettings> {{ \'tb.rulenode.use-system-smtp-settings\' | translate }} </md-checkbox> <section layout=column ng-if=!configuration.useSystemSmtpSettings> <md-input-container class=md-block> <label translate>tb.rulenode.smtp-protocol</label> <md-select ng-disabled="$root.loading || readonly" ng-model=configuration.smtpProtocol> <md-option ng-repeat="smtpProtocol in smtpProtocols" value={{smtpProtocol}}> {{smtpProtocol.toUpperCase()}} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.smtp-host</label> <input ng-required=true name=smtpHost ng-model=configuration.smtpHost> <div ng-messages=sendEmailConfigForm.smtpHost.$error> <div translate ng-message=required>tb.rulenode.smtp-host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.smtp-port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.smtpPort> <div ng-messages=sendEmailConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.smtp-port-required</div> <div translate ng-message=min>tb.rulenode.smtp-port-range</div> <div translate ng-message=max>tb.rulenode.smtp-port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.timeout-msec</label> <input type=number step=1 min=0 ng-required=true name=timeout ng-model=configuration.timeout> <div ng-messages=sendEmailConfigForm.timeout.$error> <div translate ng-message=required>tb.rulenode.timeout-required</div> <div translate ng-message=min>tb.rulenode.min-timeout-msec-message</div> </div> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-tls\' | translate }}" ng-model=configuration.enableTls>{{ \'tb.rulenode.enable-tls\' | translate }}</md-checkbox> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=username placeholder="{{ \'tb.rulenode.enter-username\' | translate }}" ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=password placeholder="{{ \'tb.rulenode.enter-password\' | translate }}" type=password ng-model=configuration.password> </md-input-container> </section> </section> '},function(e,t){e.exports=" <section ng-form name=snsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-arn-pattern</label> <input ng-required=true name=topicArnPattern ng-model=configuration.topicArnPattern> <div ng-messages=snsConfigForm.topicArnPattern.$error> <div ng-message=required translate>tb.rulenode.topic-arn-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=sqsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.queue-type</label> <md-select ng-model=configuration.queueType ng-disabled="$root.loading || readonly"> <md-option ng-repeat="type in ruleNodeTypes.sqsQueueType" ng-value=type.value> {{ type.name | translate }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.queue-url-pattern</label> <input ng-required=true name=queueUrlPattern ng-model=configuration.queueUrlPattern> <div ng-messages=sqsConfigForm.queueUrlPattern.$error> <div ng-message=required translate>tb.rulenode.queue-url-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block ng-if="configuration.queueType == ruleNodeTypes.sqsQueueType.STANDARD.value"> <label translate>tb.rulenode.delay-seconds</label> <input type=number step=1 name=delaySeconds ng-model=configuration.delaySeconds min=0 max=900> <div ng-messages=sqsConfigForm.delaySeconds.$error> <div ng-message=min translate>tb.rulenode.min-delay-seconds-message</div> <div ng-message=max translate>tb.rulenode.max-delay-seconds-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.message-attributes</label> <tb-kv-map-config ng-model=configuration.messageAttributes ng-required=false key-text="\'tb.rulenode.name\'" key-required-text="\'tb.rulenode.name-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> '},function(e,t){e.exports=" <section ng-form name=timeseriesConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.default-ttl</label> <input ng-required=true type=number step=1 name=defaultTTL ng-model=configuration.defaultTTL min=0> <div ng-messages=timeseriesConfigForm.defaultTTL.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.default-ttl-required</div> <div ng-message=min translate>tb.rulenode.min-default-ttl-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "; | |
2 | -},19,function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" ng-class="{\'tb-required\': required}">tb.rulenode.message-types-filter</label> <md-chips id=message_type_chips ng-required=required readonly=readonly ng-model=messageTypes md-autocomplete-snap md-transform-chip=transformMessageTypeChip($chip) md-require-match=false> <md-autocomplete id=message_type md-no-cache=true md-selected-item=selectedMessageType md-search-text=messageTypeSearchText md-items="item in messageTypesSearch(messageTypeSearchText)" md-item-text=item.name md-min-length=0 placeholder="{{\'tb.rulenode.message-type\' | translate }}" md-menu-class=tb-message-type-autocomplete> <span md-highlight-text=messageTypeSearchText md-highlight-flags=^i>{{item}}</span> <md-not-found> <div class=tb-not-found> <div class=tb-no-entries ng-if="!messageTypeSearchText || !messageTypeSearchText.length"> <span translate>tb.rulenode.no-message-types-found</span> </div> <div ng-if="messageTypeSearchText && messageTypeSearchText.length"> <span translate translate-values=\'{ messageType: "{{messageTypeSearchText | truncate:true:6:'...'}}" }\'>tb.rulenode.no-message-type-matching</span> <span> <a translate ng-click="createMessageType($event, \'#message_type_chips\')">tb.rulenode.create-new-message-type</a> </span> </div> </div> </md-not-found> </md-autocomplete> <md-chip-template> <span>{{$chip.name}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=messageTypes class=tb-error-message>tb.rulenode.message-types-required</div> </div> </section>'},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-filter-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.switch</label> <tb-js-func ng-model=configuration.jsScript function-name=Switch function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-switch-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-kv-map-config layout=column> <div class=header flex layout=row> <span class=cell flex translate>{{ keyText }}</span> <span class=cell flex translate>{{ valText }}</span> <span ng-show=!disabled style=width:52px> </span> </div> <div class=body> <div class=row ng-form name=kvForm flex layout=row layout-align="start center" ng-repeat="keyVal in kvList track by $index"> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ keyText | translate }}" ng-required=true name=key ng-model=keyVal.key> <div ng-messages=kvForm.key.$error> <div translate ng-message=required>{{keyRequiredText}}</div> </div> </md-input-container> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ valText | translate }}" ng-required=true name=value ng-model=keyVal.value> <div ng-messages=kvForm.value.$error> <div translate ng-message=required>{{valRequiredText}}</div> </div> </md-input-container> <md-button ng-show=!disabled ng-disabled=loading class="md-icon-button md-primary" ng-click=removeKeyVal($index) aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.remove-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.delete\' | translate }}" class=material-icons> close </md-icon> </md-button> </div> </div> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=kvMap class=tb-error-message>{{requiredText}}</div> </div> <div> <md-button ng-show=!disabled ng-disabled=loading class="md-primary md-raised" ng-click=addKeyVal() aria-label="{{ \'action.add\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.add-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.add\' | translate }}" class=material-icons> add </md-icon> {{ \'action.add\' | translate }} </md-button> </div> </section> '},function(e,t){e.exports=" <section layout=column> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder=\"{{ 'tb.rulenode.unlimited-level' | translate }}\" ng-model=query.maxLevel aria-label=\"{{ 'tb.rulenode.max-relation-level' | translate }}\"> </md-input-container> </div> <div class=md-caption style=padding-bottom:10px;color:rgba(0,0,0,.57) translate>relation.relation-filters</div> <tb-relation-filters ng-model=query.filters> </tb-relation-filters> </section> "},function(e,t){e.exports=' <section layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.originator-source</label> <md-select required ng-model=configuration.originatorSource> <md-option ng-repeat="source in ruleNodeTypes.originatorSource" ng-value=source.value> {{ source.name | translate}} </md-option> </md-select> </md-input-container> <section layout=column ng-if="configuration.originatorSource == ruleNodeTypes.originatorSource.RELATED.value"> <label translate class="tb-title tb-required">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> </section> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.transform</label> <tb-js-func ng-model=configuration.jsScript function-name=Transform function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-transformer-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section ng-form name=toEmailConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.from-template</label> <textarea ng-required=true name=fromTemplate ng-model=configuration.fromTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.fromTemplate.$error> <div ng-message=required translate>tb.rulenode.from-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.from-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.to-template</label> <textarea ng-required=true name=toTemplate ng-model=configuration.toTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.toTemplate.$error> <div ng-message=required translate>tb.rulenode.to-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.cc-template</label> <textarea name=ccTemplate ng-model=configuration.ccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bcc-template</label> <textarea name=ccTemplate ng-model=configuration.bccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.subject-template</label> <textarea ng-required=true name=subjectTemplate ng-model=configuration.subjectTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.subjectTemplate.$error> <div ng-message=required translate>tb.rulenode.subject-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.subject-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.body-template</label> <textarea ng-required=true name=bodyTemplate ng-model=configuration.bodyTemplate rows=6></textarea> <div ng-messages=toEmailConfigForm.bodyTemplate.$error> <div ng-message=required translate>tb.rulenode.body-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.body-template-hint</div> </md-input-container> </section> "},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue},r.testConditionJs=function(e,n){var i=angular.copy(n?r.configuration.createConditionJs:r.configuration.clearConditionJs),o={temperature:22.4,humidity:78},l={sensorType:"temperature"};a.testNodeScript(e,i,"filter",t.instant("tb.rulenode.condition")+"",n?"isAlarm":"isCleared",["msg","metadata","msgType"],o,l,"POST_TELEMETRY").then(function(e){n?r.configuration.createConditionJs=e:r.configuration.clearConditionJs=e,s.$setDirty()})},r.testDetailsBuildJs=function(e){var n=angular.copy(r.configuration.alarmDetailsBuildJs),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){r.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(5),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(6),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.originator=null,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue,r.configuration.originatorId&&r.configuration.originatorType?r.originator={id:r.configuration.originatorId,entityType:r.configuration.originatorType}:r.originator=null,r.$watch("originator",function(e,t){angular.equals(e,t)||(r.originator?(s.$viewValue.originatorId=r.originator.id,s.$viewValue.originatorType=r.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},r.testScript=function(e){var n=angular.copy(r.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};a.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],i,o,"DebugMsg").then(function(e){r.configuration.jsScript=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(1);var i=n(7),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(45),i=a(r),o=n(32),l=a(o),s=n(33),u=a(s),d=n(31),c=a(d),m=n(36),g=a(m),p=n(40),f=a(p),b=n(41),v=a(b),y=n(39),q=a(y),T=n(35),h=a(T),$=n(43),k=a($),w=n(44),C=a(w),x=n(38),_=a(x),M=n(37),S=a(M),E=n(42),V=a(E);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",i.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",u.default).directive("tbActionNodeAlarmConfig",c.default).directive("tbActionNodeLogConfig",g.default).directive("tbActionNodeRpcReplyConfig",f.default).directive("tbActionNodeRpcRequestConfig",v.default).directive("tbActionNodeRestApiCallConfig",q.default).directive("tbActionNodeKafkaConfig",h.default).directive("tbActionNodeSnsConfig",k.default).directive("tbActionNodeSqsConfig",C.default).directive("tbActionNodeRabbitMqConfig",_.default).directive("tbActionNodeMqttConfig",S.default).directive("tbActionNodeSendEmailConfig",V.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(8),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,r,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(9),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$mdExpansionPanel=t,a.ruleNodeTypes=n,a.credentialsTypeChanged=function(){var e=a.configuration.credentials.type;a.configuration.credentials={},a.configuration.credentials.type=e,a.updateValidity()},a.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){a.$apply(function(){if(n.target.result){l.$setDirty();var r=n.target.result;r&&r.length>0&&("caCert"==t&&(a.configuration.credentials.caCertFileName=e.name,a.configuration.credentials.caCert=r),"privateKey"==t&&(a.configuration.credentials.privateKeyFileName=e.name,a.configuration.credentials.privateKey=r),"Cert"==t&&(a.configuration.credentials.certFileName=e.name,a.configuration.credentials.cert=r)),a.updateValidity()}})},n.readAsText(e.file)},a.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(a.configuration.credentials.caCertFileName=null,a.configuration.credentials.caCert=null),"privateKey"==e&&(a.configuration.credentials.privateKeyFileName=null,a.configuration.credentials.privateKey=null),"Cert"==e&&(a.configuration.credentials.certFileName=null,a.configuration.credentials.cert=null),a.updateValidity()},a.updateValidity=function(){var e=!0,t=a.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:a}}r.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(2);var i=n(10),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(11),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(12),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(13),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(14),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(15),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(16),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(17),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(18),o=a(i)},function(e,t){"use strict";function n(e){var t=function(t,n,a,r){n.html("<div></div>"),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(19),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(49),i=a(r),o=n(50),l=a(o),s=n(47),u=a(s),d=n(51),c=a(d);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeRelatedAttributesConfig",l.default).directive("tbEnrichmentNodeCustomerAttributesConfig",u.default).directive("tbEnrichmentNodeTenantAttributesConfig",c.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(20),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(21),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(22),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(54),i=a(r),o=n(53),l=a(o),s=n(55),u=a(s);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){function s(){if(l.$viewValue){for(var e=[],t=0;t<a.messageTypes.length;t++)e.push(a.messageTypes[t].value);l.$viewValue.messageTypes=e,u()}}function u(){if(a.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var d=o.default;r.html(d),a.selectedMessageType=null,a.messageTypeSearchText=null,a.ngModelCtrl=l;var c=[];for(var m in n.messageType){var g={name:n.messageType[m].name,value:n.messageType[m].value};c.push(g)}a.transformMessageTypeChip=function(e){var n,a=t("filter")(c,{name:e},!0);return n=a&&a.length?angular.copy(a[0]):{name:e,value:e}},a.messageTypesSearch=function(e){var n=e?t("filter")(c,{name:e}):c;return n.map(function(e){return e.name})},a.createMessageType=function(e,t){var n=angular.element(t,r)[0].firstElementChild,a=angular.element(n),i=a.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),a.scope().$mdChipsCtrl.appendChip(i.trim()),a.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var r=0;r<e.messageTypes.length;r++){var i=e.messageTypes[r];n.messageType[i]?t.push(angular.copy(n.messageType[i])):t.push({name:i,value:i})}a.messageTypes=t,a.$watch("messageTypes",function(e,t){angular.equals(e,t)||s()},!0)},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:a}}r.$inject=["$compile","$filter","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(3);var i=n(23),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript),i={passed:12,name:"Vit",bigObj:{prop:42}},o={temp:10};n.testNodeScript(e,r,"filter",t.instant("tb.rulenode.filter")+"","Filter",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(24),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,r,"switch",t.instant("tb.rulenode.switch")+"","Switch",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(25),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){function i(e){e>-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),r.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),r.$setValidity("kvMap",e)}var d=o.default;n.html(d),t.ngModelCtrl=r,t.removeKeyVal=i,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||r.$setViewValue(t.query)}),r.$render=function(){if(r.$viewValue){var e=r.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),u()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(26),o=a(i);n(4)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(27),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(28),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(58),i=a(r),o=n(60),l=a(o),s=n(61),u=a(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",u.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,r,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(29),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(30),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(65),i=a(r),o=n(52),l=a(o),s=n(48),u=a(s),d=n(59),c=a(d),m=n(34),g=a(m),p=n(46),f=a(p),b=n(57),v=a(b),y=n(56),q=a(y),T=n(64),h=a(T);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbKvMapConfig",q.default).config(h.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","relations-query":"Relations query","max-relation-level":"Max relation level","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping", | |
3 | -"source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use <code>${metaKeyName}</code> to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use <code>${metaKeyName}</code> to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use <code>${metaKeyName}</code> to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use <code>${metaKeyName}</code> to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","request-method":"Request method",headers:"Headers",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","message-attributes":"Message attributes","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","smtp-protocol":"SMTP protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout (msec)","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};angular.merge(e.en_US,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){(0,o.default)(t);for(var n in t){var a=t[n];e.translations(n,a)}}r.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(63),o=a(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{messageType:{POST_ATTRIBUTES_REQUEST:{name:"Post attributes",value:"POST_ATTRIBUTES_REQUEST"},POST_TELEMETRY_REQUEST:{name:"Post telemetry",value:"POST_TELEMETRY_REQUEST"},TO_SERVER_RPC_REQUEST:{name:"RPC Request",value:"TO_SERVER_RPC_REQUEST"},ACTIVITY_EVENT:{name:"Activity Event",value:"ACTIVITY_EVENT"},INACTIVITY_EVENT:{name:"Inactivity Event",value:"INACTIVITY_EVENT"},CONNECT_EVENT:{name:"Connect Event",value:"CONNECT_EVENT"},DISCONNECT_EVENT:{name:"Disconnect Event",value:"DISCONNECT_EVENT"}},originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"}},httpRequestType:["GET","POST","PUT","DELETE"],sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}])); | |
1 | +!function(e){function t(a){if(n[a])return n[a].exports;var r=n[a]={exports:{},id:a,loaded:!1};return e[a].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),a=e[t[0]];return function(e,t,r){a.apply(this,[e,t,r].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(68)},function(e,t){},1,1,1,function(e,t){e.exports=' <section ng-form name=attributesConfigForm layout=column> <md-input-container class=md-block> <label translate>attribute.attributes-scope</label> <md-select ng-model=configuration.scope ng-disabled=$root.loading> <md-option ng-repeat="scope in types.attributesScope" ng-value=scope.value> {{scope.name | translate}} </md-option> </md-select> </md-input-container> </section> '},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <md-input-container class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <section layout=column layout-gt-sm=row> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-severity</label> <md-select required name=severity ng-model=configuration.severity> <md-option ng-repeat=\"(severityKey, severity) in types.alarmSeverity\" ng-value=severityKey> {{ severity.name | translate}} </md-option> </md-select> <div ng-messages=alarmConfigForm.severity.$error> <div ng-message=required translate>tb.rulenode.alarm-severity-required</div> </div> </md-input-container> </section> <md-checkbox aria-label=\"{{ 'tb.rulenode.propagate' | translate }}\" ng-model=configuration.propagate>{{ 'tb.rulenode.propagate' | translate }} </md-checkbox> </section> "},function(e,t){e.exports=" <section class=tb-generator-config ng-form name=generatorConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.message-count</label> <input ng-required=true type=number step=1 name=messageCount ng-model=configuration.msgCount min=0> <div ng-messages=generatorConfigForm.messageCount.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.message-count-required</div> <div ng-message=min translate>tb.rulenode.min-message-count-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=1> <div ng-messages=generatorConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-seconds-message</div> </div> </md-input-container> <div layout=column> <label class=tb-small>{{ 'tb.rulenode.originator' | translate }}</label> <tb-entity-select the-form=generatorConfigForm tb-required=false ng-model=originator> </tb-entity-select> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.generate</label> <tb-js-func ng-model=configuration.jsScript function-name=Generate function-args=\"{{ ['prevMsg', 'prevMetadata', 'prevMsgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-generator-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section ng-form name=kafkaConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=kafkaConfigForm.topicPattern.$error> <div ng-message=required translate>tb.rulenode.topic-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bootstrap-servers</label> <input ng-required=true name=bootstrapServers ng-model=configuration.bootstrapServers> <div ng-messages=kafkaConfigForm.bootstrapServers.$error> <div ng-message=required translate>tb.rulenode.bootstrap-servers-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.retries</label> <input type=number step=1 name=retries ng-model=configuration.retries min=0> <div ng-messages=kafkaConfigForm.retries.$error> <div ng-message=min translate>tb.rulenode.min-retries-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.batch-size-bytes</label> <input type=number step=1 name=batchSize ng-model=configuration.batchSize min=0> <div ng-messages=kafkaConfigForm.batchSize.$error> <div ng-message=min translate>tb.rulenode.min-batch-size-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.linger-ms</label> <input type=number step=1 name=linger ng-model=configuration.linger min=0> <div ng-messages=kafkaConfigForm.linger.$error> <div ng-message=min translate>tb.rulenode.min-linger-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.buffer-memory-bytes</label> <input type=number step=1 name=bufferMemory ng-model=configuration.bufferMemory min=0> <div ng-messages=kafkaConfigForm.bufferMemory.$error> <div ng-message=min translate>tb.rulenode.min-buffer-memory-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.acks</label> <md-select ng-model=configuration.acks ng-disabled=$root.loading> <md-option ng-repeat="ackValue in ackValues" ng-value=ackValue> {{ ackValue }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.key-serializer</label> <input ng-required=true name=keySerializer ng-model=configuration.keySerializer> <div ng-messages=kafkaConfigForm.keySerializer.$error> <div ng-message=required translate>tb.rulenode.key-serializer-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.value-serializer</label> <input ng-required=true name=valueSerializer ng-model=configuration.valueSerializer> <div ng-messages=kafkaConfigForm.valueSerializer.$error> <div ng-message=required translate>tb.rulenode.value-serializer-required</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.other-properties</label> <tb-kv-map-config ng-model=configuration.otherProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.to-string</label> <tb-js-func ng-model=configuration.jsScript function-name=ToString function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-to-string-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-mqtt-config ng-form name=mqttConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=mqttConfigForm.topicPattern.$error> <div translate ng-message=required>tb.rulenode.topic-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.mqtt-topic-pattern-hint</div> </md-input-container> <div flex layout=column layout-gt-sm=row> <md-input-container flex=60 class=md-block> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=mqttConfigForm.host.$error> <div translate ng-message=required>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.port> <div ng-messages=mqttConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.port-required</div> <div translate ng-message=min>tb.rulenode.port-range</div> <div translate ng-message=max>tb.rulenode.port-range</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.connect-timeout</label> <input type=number step=1 min=1 max=200 ng-required=true name=connectTimeoutSec ng-model=configuration.connectTimeoutSec> <div ng-messages=mqttConfigForm.connectTimeoutSec.$error> <div translate ng-message=required>tb.rulenode.connect-timeout-required</div> <div translate ng-message=min>tb.rulenode.connect-timeout-range</div> <div translate ng-message=max>tb.rulenode.connect-timeout-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.client-id</label> <input name=clientId ng-model=configuration.clientId> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-ssl\' | translate }}" ng-model=configuration.ssl> {{ \'tb.rulenode.enable-ssl\' | translate }} </md-checkbox> <md-expansion-panel-group class=tb-credentials-panel-group ng-class="{\'disabled\': $root.loading || readonly}" md-component-id=credentialsPanelGroup> <md-expansion-panel md-component-id=credentialsPanel> <md-expansion-panel-collapsed> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-collapsed> <md-expansion-panel-expanded> <md-expansion-panel-header ng-click="$mdExpansionPanel(\'credentialsPanel\').collapse()"> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-header> <md-expansion-panel-content> <div layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.credentials-type</label> <md-select ng-required=true name=credentialsType ng-model=configuration.credentials.type ng-disabled="$root.loading || readonly" ng-change=credentialsTypeChanged()> <md-option ng-repeat="(credentialsType, credentialsValue) in ruleNodeTypes.mqttCredentialTypes" ng-value=credentialsValue.value> {{credentialsValue.name | translate}} </md-option> </md-select> <div ng-messages=mqttConfigForm.credentialsType.$error> <div translate ng-message=required>tb.rulenode.credentials-type-required</div> </div> </md-input-container> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes.basic.value"> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input ng-required=true name=mqttUsername ng-model=configuration.credentials.username> <div ng-messages=mqttConfigForm.mqttUsername.$error> <div translate ng-message=required>tb.rulenode.username-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input type=password ng-required=true name=mqttPassword ng-model=configuration.credentials.password> <div ng-messages=mqttConfigForm.mqttPassword.$error> <div translate ng-message=required>tb.rulenode.password-required</div> </div> </md-input-container> </section> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes[\'cert.PEM\'].value" class=dropdown-section> <div class=tb-container ng-class="configuration.credentials.caCertFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.ca-cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'caCert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'caCert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=caCertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=caCertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.caCertFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.caCertFileName>{{configuration.credentials.caCertFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.certFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'Cert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'Cert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=CertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=CertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.certFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.certFileName>{{configuration.credentials.certFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.privateKeyFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.private-key</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'privateKey\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'privateKey\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=privateKeySelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=privateKeySelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.privateKeyFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.privateKeyFileName>{{configuration.credentials.privateKeyFileName}}</div> </div> <md-input-container class=md-block> <label translate>tb.rulenode.private-key-password</label> <input type=password name=privateKeyPassword ng-model=configuration.credentials.password> </md-input-container> </section> </div> </md-expansion-panel-content> </md-expansion-panel-expanded> </md-expansion-panel> </md-expansion-panel-group> </section>'},function(e,t){e.exports=' <section ng-form name=rabbitMqConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.exchange-name-pattern</label> <input name=exchangeNamePattern ng-model=configuration.exchangeNamePattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.routing-key-pattern</label> <input name=routingKeyPattern ng-model=configuration.routingKeyPattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.message-properties</label> <md-select ng-model=configuration.messageProperties ng-disabled="$root.loading || readonly"> <md-option ng-repeat="property in messageProperties" ng-value=property> {{ property }} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=rabbitMqConfigForm.host.$error> <div ng-message=required translate>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.port</label> <input ng-required=true type=number step=1 name=port ng-model=configuration.port min=0 max=65535> <div ng-messages=rabbitMqConfigForm.port.$error> <div ng-message=required translate>tb.rulenode.port-required</div> <div ng-message=min translate>tb.rulenode.port-range</div> <div ng-message=max translate>tb.rulenode.port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.virtual-host</label> <input name=virtualHost ng-model=configuration.virtualHost> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=virtualHost ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=virtualHost type=password ng-model=configuration.password> </md-input-container> <md-input-container class=md-block> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.automatic-recovery\' | translate }}" ng-model=ruleNode.automaticRecoveryEnabled>{{ \'tb.rulenode.automatic-recovery\' | translate }} </md-checkbox> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.connection-timeout-ms</label> <input type=number step=1 name=connectionTimeout ng-model=configuration.connectionTimeout min=0> <div ng-messages=rabbitMqConfigForm.connectionTimeout.$error> <div ng-message=min translate>tb.rulenode.min-connection-timeout-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.handshake-timeout-ms</label> <input type=number step=1 name=handshakeTimeout ng-model=configuration.handshakeTimeout min=0> <div ng-messages=rabbitMqConfigForm.handshakeTimeout.$error> <div ng-message=min translate>tb.rulenode.min-handshake-timeout-ms-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.client-properties</label> <tb-kv-map-config ng-model=configuration.clientProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=' <section ng-form name=restApiCallConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.endpoint-url-pattern</label> <input ng-required=true name=endpointUrlPattern ng-model=configuration.restEndpointUrlPattern> <div ng-messages=restApiCallConfigForm.endpointUrlPattern.$error> <div ng-message=required translate>tb.rulenode.endpoint-url-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.endpoint-url-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.request-method</label> <md-select ng-model=configuration.requestMethod ng-disabled=$root.loading> <md-option ng-repeat="type in ruleNodeTypes.httpRequestType" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <label translate class=tb-title>tb.rulenode.headers</label> <tb-kv-map-config ng-model=configuration.headers ng-required=false key-text="\'tb.rulenode.header\'" key-required-text="\'tb.rulenode.header-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section ng-form name=rpcReplyConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.request-id-metadata-attribute</label> <input name=requestIdMetaDataAttribute ng-model=configuration.requestIdMetaDataAttribute> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=rpcRequestConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.timeout-sec</label> <input ng-required=true type=number step=1 name=timeoutInSeconds ng-model=configuration.timeoutInSeconds min=0> <div ng-messages=rpcRequestConfigForm.timeoutInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.timeout-required</div> <div ng-message=min translate>tb.rulenode.min-timeout-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=sendEmailConfigForm layout=column> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}" ng-model=configuration.useSystemSmtpSettings> {{ \'tb.rulenode.use-system-smtp-settings\' | translate }} </md-checkbox> <section layout=column ng-if=!configuration.useSystemSmtpSettings> <md-input-container class=md-block> <label translate>tb.rulenode.smtp-protocol</label> <md-select ng-disabled="$root.loading || readonly" ng-model=configuration.smtpProtocol> <md-option ng-repeat="smtpProtocol in smtpProtocols" value={{smtpProtocol}}> {{smtpProtocol.toUpperCase()}} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.smtp-host</label> <input ng-required=true name=smtpHost ng-model=configuration.smtpHost> <div ng-messages=sendEmailConfigForm.smtpHost.$error> <div translate ng-message=required>tb.rulenode.smtp-host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.smtp-port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.smtpPort> <div ng-messages=sendEmailConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.smtp-port-required</div> <div translate ng-message=min>tb.rulenode.smtp-port-range</div> <div translate ng-message=max>tb.rulenode.smtp-port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.timeout-msec</label> <input type=number step=1 min=0 ng-required=true name=timeout ng-model=configuration.timeout> <div ng-messages=sendEmailConfigForm.timeout.$error> <div translate ng-message=required>tb.rulenode.timeout-required</div> <div translate ng-message=min>tb.rulenode.min-timeout-msec-message</div> </div> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-tls\' | translate }}" ng-model=configuration.enableTls>{{ \'tb.rulenode.enable-tls\' | translate }}</md-checkbox> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=username placeholder="{{ \'tb.rulenode.enter-username\' | translate }}" ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=password placeholder="{{ \'tb.rulenode.enter-password\' | translate }}" type=password ng-model=configuration.password> </md-input-container> </section> </section> '},function(e,t){e.exports=" <section ng-form name=snsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-arn-pattern</label> <input ng-required=true name=topicArnPattern ng-model=configuration.topicArnPattern> <div ng-messages=snsConfigForm.topicArnPattern.$error> <div ng-message=required translate>tb.rulenode.topic-arn-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=sqsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.queue-type</label> <md-select ng-model=configuration.queueType ng-disabled="$root.loading || readonly"> <md-option ng-repeat="type in ruleNodeTypes.sqsQueueType" ng-value=type.value> {{ type.name | translate }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.queue-url-pattern</label> <input ng-required=true name=queueUrlPattern ng-model=configuration.queueUrlPattern> <div ng-messages=sqsConfigForm.queueUrlPattern.$error> <div ng-message=required translate>tb.rulenode.queue-url-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block ng-if="configuration.queueType == ruleNodeTypes.sqsQueueType.STANDARD.value"> <label translate>tb.rulenode.delay-seconds</label> <input type=number step=1 name=delaySeconds ng-model=configuration.delaySeconds min=0 max=900> <div ng-messages=sqsConfigForm.delaySeconds.$error> <div ng-message=min translate>tb.rulenode.min-delay-seconds-message</div> <div ng-message=max translate>tb.rulenode.max-delay-seconds-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.message-attributes</label> <tb-kv-map-config ng-model=configuration.messageAttributes ng-required=false key-text="\'tb.rulenode.name\'" key-required-text="\'tb.rulenode.name-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> '},function(e,t){e.exports=" <section ng-form name=timeseriesConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.default-ttl</label> <input ng-required=true type=number step=1 name=defaultTTL ng-model=configuration.defaultTTL min=0> <div ng-messages=timeseriesConfigForm.defaultTTL.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.default-ttl-required</div> <div ng-message=min translate>tb.rulenode.min-default-ttl-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section layout=column> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat="direction in types.entitySearchDirection" ng-value=direction> {{ (\'relation.search-direction.\' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder="{{ \'tb.rulenode.unlimited-level\' | translate }}" ng-model=query.maxLevel aria-label="{{ \'tb.rulenode.max-relation-level\' | translate }}"> </md-input-container> </div> <div class=md-caption style=color:rgba(0,0,0,.57) translate>relation.relation-type</div> <tb-relation-type-autocomplete flex hide-label ng-model=query.relationType tb-required=false> </tb-relation-type-autocomplete> <div class="md-caption tb-required" style=color:rgba(0,0,0,.57) translate>device.device-types</div> <tb-entity-subtype-list tb-required=true entity-type=types.entityType.device ng-model=query.deviceTypes> </tb-entity-subtype-list> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title tb-required">tb.rulenode.device-relations-query</label> <tb-device-relations-query-config style=padding-bottom:15px ng-model=configuration.deviceRelationsQuery> </tb-device-relations-query-config> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> </section> '; | |
2 | +},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},21,function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" ng-class="{\'tb-required\': required}">tb.rulenode.message-types-filter</label> <md-chips id=message_type_chips ng-required=required readonly=readonly ng-model=messageTypes md-autocomplete-snap md-transform-chip=transformMessageTypeChip($chip) md-require-match=false> <md-autocomplete id=message_type md-no-cache=true md-selected-item=selectedMessageType md-search-text=messageTypeSearchText md-items="item in messageTypesSearch(messageTypeSearchText)" md-item-text=item.name md-min-length=0 placeholder="{{\'tb.rulenode.message-type\' | translate }}" md-menu-class=tb-message-type-autocomplete> <span md-highlight-text=messageTypeSearchText md-highlight-flags=^i>{{item}}</span> <md-not-found> <div class=tb-not-found> <div class=tb-no-entries ng-if="!messageTypeSearchText || !messageTypeSearchText.length"> <span translate>tb.rulenode.no-message-types-found</span> </div> <div ng-if="messageTypeSearchText && messageTypeSearchText.length"> <span translate translate-values=\'{ messageType: "{{messageTypeSearchText | truncate:true:6:'...'}}" }\'>tb.rulenode.no-message-type-matching</span> <span> <a translate ng-click="createMessageType($event, \'#message_type_chips\')">tb.rulenode.create-new-message-type</a> </span> </div> </div> </md-not-found> </md-autocomplete> <md-chip-template> <span>{{$chip.name}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=messageTypes class=tb-error-message>tb.rulenode.message-types-required</div> </div> </section>'},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-filter-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.switch</label> <tb-js-func ng-model=configuration.jsScript function-name=Switch function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-switch-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-kv-map-config layout=column> <div class=header flex layout=row> <span class=cell flex translate>{{ keyText }}</span> <span class=cell flex translate>{{ valText }}</span> <span ng-show=!disabled style=width:52px> </span> </div> <div class=body> <div class=row ng-form name=kvForm flex layout=row layout-align="start center" ng-repeat="keyVal in kvList track by $index"> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ keyText | translate }}" ng-required=true name=key ng-model=keyVal.key> <div ng-messages=kvForm.key.$error> <div translate ng-message=required>{{keyRequiredText}}</div> </div> </md-input-container> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ valText | translate }}" ng-required=true name=value ng-model=keyVal.value> <div ng-messages=kvForm.value.$error> <div translate ng-message=required>{{valRequiredText}}</div> </div> </md-input-container> <md-button ng-show=!disabled ng-disabled=loading class="md-icon-button md-primary" ng-click=removeKeyVal($index) aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.remove-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.delete\' | translate }}" class=material-icons> close </md-icon> </md-button> </div> </div> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=kvMap class=tb-error-message>{{requiredText}}</div> </div> <div> <md-button ng-show=!disabled ng-disabled=loading class="md-primary md-raised" ng-click=addKeyVal() aria-label="{{ \'action.add\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.add-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.add\' | translate }}" class=material-icons> add </md-icon> {{ \'action.add\' | translate }} </md-button> </div> </section> '},function(e,t){e.exports=" <section layout=column> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder=\"{{ 'tb.rulenode.unlimited-level' | translate }}\" ng-model=query.maxLevel aria-label=\"{{ 'tb.rulenode.max-relation-level' | translate }}\"> </md-input-container> </div> <div class=md-caption style=padding-bottom:10px;color:rgba(0,0,0,.57) translate>relation.relation-filters</div> <tb-relation-filters ng-model=query.filters> </tb-relation-filters> </section> "},function(e,t){e.exports=' <section layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.originator-source</label> <md-select required ng-model=configuration.originatorSource> <md-option ng-repeat="source in ruleNodeTypes.originatorSource" ng-value=source.value> {{ source.name | translate}} </md-option> </md-select> </md-input-container> <section layout=column ng-if="configuration.originatorSource == ruleNodeTypes.originatorSource.RELATED.value"> <label translate class="tb-title tb-required">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> </section> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.transform</label> <tb-js-func ng-model=configuration.jsScript function-name=Transform function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-transformer-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section ng-form name=toEmailConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.from-template</label> <textarea ng-required=true name=fromTemplate ng-model=configuration.fromTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.fromTemplate.$error> <div ng-message=required translate>tb.rulenode.from-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.from-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.to-template</label> <textarea ng-required=true name=toTemplate ng-model=configuration.toTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.toTemplate.$error> <div ng-message=required translate>tb.rulenode.to-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.cc-template</label> <textarea name=ccTemplate ng-model=configuration.ccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bcc-template</label> <textarea name=ccTemplate ng-model=configuration.bccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.subject-template</label> <textarea ng-required=true name=subjectTemplate ng-model=configuration.subjectTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.subjectTemplate.$error> <div ng-message=required translate>tb.rulenode.subject-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.subject-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.body-template</label> <textarea ng-required=true name=bodyTemplate ng-model=configuration.bodyTemplate rows=6></textarea> <div ng-messages=toEmailConfigForm.bodyTemplate.$error> <div ng-message=required translate>tb.rulenode.body-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.body-template-hint</div> </md-input-container> </section> "},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(5),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue},r.testDetailsBuildJs=function(e){var n=angular.copy(r.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(6),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue},r.testDetailsBuildJs=function(e){var n=angular.copy(r.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(7),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.originator=null,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue,r.configuration.originatorId&&r.configuration.originatorType?r.originator={id:r.configuration.originatorId,entityType:r.configuration.originatorType}:r.originator=null,r.$watch("originator",function(e,t){angular.equals(e,t)||(r.originator?(s.$viewValue.originatorId=r.originator.id,s.$viewValue.originatorType=r.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},r.testScript=function(e){var n=angular.copy(r.configuration.jsScript);a.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],r.ruleNodeId).then(function(e){r.configuration.jsScript=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(1);var i=n(8),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(49),i=a(r),o=n(34),l=a(o),s=n(37),u=a(s),d=n(36),c=a(d),m=n(35),g=a(m),p=n(40),f=a(p),b=n(44),v=a(b),y=n(45),q=a(y),h=n(43),$=a(h),T=n(39),k=a(T),w=n(47),C=a(w),x=n(48),_=a(x),M=n(42),S=a(M),N=n(41),E=a(N),V=n(46),P=a(V);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",i.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",u.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",q.default).directive("tbActionNodeRestApiCallConfig",$.default).directive("tbActionNodeKafkaConfig",k.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",_.default).directive("tbActionNodeRabbitMqConfig",S.default).directive("tbActionNodeMqttConfig",E.default).directive("tbActionNodeSendEmailConfig",P.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(9),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(10),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$mdExpansionPanel=t,a.ruleNodeTypes=n,a.credentialsTypeChanged=function(){var e=a.configuration.credentials.type;a.configuration.credentials={},a.configuration.credentials.type=e,a.updateValidity()},a.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){a.$apply(function(){if(n.target.result){l.$setDirty();var r=n.target.result;r&&r.length>0&&("caCert"==t&&(a.configuration.credentials.caCertFileName=e.name,a.configuration.credentials.caCert=r),"privateKey"==t&&(a.configuration.credentials.privateKeyFileName=e.name,a.configuration.credentials.privateKey=r),"Cert"==t&&(a.configuration.credentials.certFileName=e.name,a.configuration.credentials.cert=r)),a.updateValidity()}})},n.readAsText(e.file)},a.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(a.configuration.credentials.caCertFileName=null,a.configuration.credentials.caCert=null),"privateKey"==e&&(a.configuration.credentials.privateKeyFileName=null,a.configuration.credentials.privateKey=null),"Cert"==e&&(a.configuration.credentials.certFileName=null,a.configuration.credentials.cert=null),a.updateValidity()},a.updateValidity=function(){var e=!0,t=a.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:a}}r.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(2);var i=n(11),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(12),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(13),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(14),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(15),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(16),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(17),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(18),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(19),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(20),o=a(i)},function(e,t){"use strict";function n(e){var t=function(t,n,a,r){n.html("<div></div>"),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(21),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(22),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(55),i=a(r),o=n(53),l=a(o),s=n(56),u=a(s),d=n(52),c=a(d),m=n(57),g=a(m);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeDeviceAttributesConfig",l.default).directive("tbEnrichmentNodeRelatedAttributesConfig",u.default).directive("tbEnrichmentNodeCustomerAttributesConfig",c.default).directive("tbEnrichmentNodeTenantAttributesConfig",g.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(23),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(24),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(25),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(60),i=a(r),o=n(59),l=a(o),s=n(61),u=a(s);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){function s(){if(l.$viewValue){for(var e=[],t=0;t<a.messageTypes.length;t++)e.push(a.messageTypes[t].value);l.$viewValue.messageTypes=e,u()}}function u(){if(a.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var d=o.default;r.html(d),a.selectedMessageType=null,a.messageTypeSearchText=null,a.ngModelCtrl=l;var c=[];for(var m in n.messageType){var g={name:n.messageType[m].name,value:n.messageType[m].value};c.push(g)}a.transformMessageTypeChip=function(e){var n,a=t("filter")(c,{name:e},!0);return n=a&&a.length?angular.copy(a[0]):{name:e,value:e}},a.messageTypesSearch=function(e){var n=e?t("filter")(c,{name:e}):c;return n.map(function(e){return e.name})},a.createMessageType=function(e,t){var n=angular.element(t,r)[0].firstElementChild,a=angular.element(n),i=a.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),a.scope().$mdChipsCtrl.appendChip(i.trim()),a.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var r=0;r<e.messageTypes.length;r++){var i=e.messageTypes[r];n.messageType[i]?t.push(angular.copy(n.messageType[i])):t.push({name:i,value:i})}a.messageTypes=t,a.$watch("messageTypes",function(e,t){angular.equals(e,t)||s()},!0)},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:a}}r.$inject=["$compile","$filter","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(3);var i=n(26),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"filter",t.instant("tb.rulenode.filter")+"","Filter",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(27),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"switch",t.instant("tb.rulenode.switch")+"","Switch",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(28),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){function i(e){e>-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),r.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),r.$setValidity("kvMap",e)}var d=o.default;n.html(d),t.ngModelCtrl=r,t.removeKeyVal=i,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||r.$setViewValue(t.query)}),r.$render=function(){if(r.$viewValue){var e=r.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),u()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(29),o=a(i);n(4)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{ | |
3 | +value:!0}),t.default=r;var i=n(30),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(31),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(64),i=a(r),o=n(66),l=a(o),s=n(67),u=a(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",u.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(32),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(33),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(71),i=a(r),o=n(58),l=a(o),s=n(54),u=a(s),d=n(65),c=a(d),m=n(38),g=a(m),p=n(51),f=a(p),b=n(63),v=a(b),y=n(50),q=a(y),h=n(62),$=a(h),T=n(70),k=a(T);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",q.default).directive("tbKvMapConfig",$.default).config(k.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use <code>${metaKeyName}</code> to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use <code>${metaKeyName}</code> to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use <code>${metaKeyName}</code> to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use <code>${metaKeyName}</code> to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","request-method":"Request method",headers:"Headers",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","message-attributes":"Message attributes","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","smtp-protocol":"SMTP protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout (msec)","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};angular.merge(e.en_US,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){(0,o.default)(t);for(var n in t){var a=t[n];e.translations(n,a)}}r.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(69),o=a(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{messageType:{POST_ATTRIBUTES_REQUEST:{name:"Post attributes",value:"POST_ATTRIBUTES_REQUEST"},POST_TELEMETRY_REQUEST:{name:"Post telemetry",value:"POST_TELEMETRY_REQUEST"},TO_SERVER_RPC_REQUEST:{name:"RPC Request",value:"TO_SERVER_RPC_REQUEST"},ACTIVITY_EVENT:{name:"Activity Event",value:"ACTIVITY_EVENT"},INACTIVITY_EVENT:{name:"Inactivity Event",value:"INACTIVITY_EVENT"},CONNECT_EVENT:{name:"Connect Event",value:"CONNECT_EVENT"},DISCONNECT_EVENT:{name:"Disconnect Event",value:"DISCONNECT_EVENT"}},originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"}},httpRequestType:["GET","POST","PUT","DELETE"],sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}])); | |
4 | 4 | //# sourceMappingURL=rulenode-core-config.js.map |
\ No newline at end of file | ... | ... |