Commit 4a3b28d331afef9a74f070e9dd6b1d9f343f3ec7

Authored by Andrii Shvaika
1 parent e353ab3c

Method to create default rule chain for device profile

  1 +{
  2 + "ruleChain": {
  3 + "additionalInfo": {
  4 + "description": ""
  5 + },
  6 + "name": "Device Profile Rule Chain Template",
  7 + "firstRuleNodeId": null,
  8 + "root": false,
  9 + "debugMode": false,
  10 + "configuration": null
  11 + },
  12 + "metadata": {
  13 + "firstNodeIndex": 6,
  14 + "nodes": [
  15 + {
  16 + "additionalInfo": {
  17 + "layoutX": 822,
  18 + "layoutY": 294
  19 + },
  20 + "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
  21 + "name": "Save Timeseries",
  22 + "debugMode": false,
  23 + "configuration": {
  24 + "defaultTTL": 0
  25 + }
  26 + },
  27 + {
  28 + "additionalInfo": {
  29 + "layoutX": 824,
  30 + "layoutY": 221
  31 + },
  32 + "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
  33 + "name": "Save Client Attributes",
  34 + "debugMode": false,
  35 + "configuration": {
  36 + "scope": "CLIENT_SCOPE"
  37 + }
  38 + },
  39 + {
  40 + "additionalInfo": {
  41 + "layoutX": 494,
  42 + "layoutY": 309
  43 + },
  44 + "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
  45 + "name": "Message Type Switch",
  46 + "debugMode": false,
  47 + "configuration": {
  48 + "version": 0
  49 + }
  50 + },
  51 + {
  52 + "additionalInfo": {
  53 + "layoutX": 824,
  54 + "layoutY": 383
  55 + },
  56 + "type": "org.thingsboard.rule.engine.action.TbLogNode",
  57 + "name": "Log RPC from Device",
  58 + "debugMode": false,
  59 + "configuration": {
  60 + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
  61 + }
  62 + },
  63 + {
  64 + "additionalInfo": {
  65 + "layoutX": 823,
  66 + "layoutY": 444
  67 + },
  68 + "type": "org.thingsboard.rule.engine.action.TbLogNode",
  69 + "name": "Log Other",
  70 + "debugMode": false,
  71 + "configuration": {
  72 + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
  73 + }
  74 + },
  75 + {
  76 + "additionalInfo": {
  77 + "layoutX": 822,
  78 + "layoutY": 507
  79 + },
  80 + "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
  81 + "name": "RPC Call Request",
  82 + "debugMode": false,
  83 + "configuration": {
  84 + "timeoutInSeconds": 60
  85 + }
  86 + },
  87 + {
  88 + "additionalInfo": {
  89 + "description": "",
  90 + "layoutX": 209,
  91 + "layoutY": 307
  92 + },
  93 + "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
  94 + "name": "Device Profile Node",
  95 + "debugMode": false,
  96 + "configuration": {
  97 + "version": 0
  98 + }
  99 + }
  100 + ],
  101 + "connections": [
  102 + {
  103 + "fromIndex": 2,
  104 + "toIndex": 4,
  105 + "type": "Other"
  106 + },
  107 + {
  108 + "fromIndex": 2,
  109 + "toIndex": 1,
  110 + "type": "Post attributes"
  111 + },
  112 + {
  113 + "fromIndex": 2,
  114 + "toIndex": 0,
  115 + "type": "Post telemetry"
  116 + },
  117 + {
  118 + "fromIndex": 2,
  119 + "toIndex": 3,
  120 + "type": "RPC Request from Device"
  121 + },
  122 + {
  123 + "fromIndex": 2,
  124 + "toIndex": 5,
  125 + "type": "RPC Request to Device"
  126 + },
  127 + {
  128 + "fromIndex": 6,
  129 + "toIndex": 2,
  130 + "type": "Success"
  131 + }
  132 + ],
  133 + "ruleChainConnections": null
  134 + }
  135 +}
\ No newline at end of file
... ...
... ... @@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.id.TenantId;
47 47 import org.thingsboard.server.common.data.page.PageData;
48 48 import org.thingsboard.server.common.data.page.PageLink;
49 49 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
  50 +import org.thingsboard.server.common.data.rule.DefaultRuleChainCreateRequest;
50 51 import org.thingsboard.server.common.data.rule.RuleChain;
51 52 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
52 53 import org.thingsboard.server.common.data.rule.RuleNode;
... ... @@ -55,6 +56,7 @@ import org.thingsboard.server.common.msg.TbMsgDataType;
55 56 import org.thingsboard.server.common.msg.TbMsgMetaData;
56 57 import org.thingsboard.server.dao.event.EventService;
57 58 import org.thingsboard.server.queue.util.TbCoreComponent;
  59 +import org.thingsboard.server.service.install.InstallScripts;
58 60 import org.thingsboard.server.service.script.JsInvokeService;
59 61 import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
60 62 import org.thingsboard.server.service.security.permission.Operation;
... ... @@ -78,6 +80,9 @@ public class RuleChainController extends BaseController {
78 80 private static final ObjectMapper objectMapper = new ObjectMapper();
79 81
80 82 @Autowired
  83 + private InstallScripts installScripts;
  84 +
  85 + @Autowired
81 86 private EventService eventService;
82 87
83 88 @Autowired
... ... @@ -147,6 +152,27 @@ public class RuleChainController extends BaseController {
147 152 }
148 153
149 154 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  155 + @RequestMapping(value = "/ruleChain/device/default", method = RequestMethod.POST)
  156 + @ResponseBody
  157 + public RuleChain saveRuleChain(@RequestBody DefaultRuleChainCreateRequest request) throws ThingsboardException {
  158 + try {
  159 + checkNotNull(request);
  160 + checkNotNull(request.getName());
  161 +
  162 + RuleChain savedRuleChain = installScripts.createDefaultRuleChain(getCurrentUser().getTenantId(), request.getName());
  163 +
  164 + logEntityAction(savedRuleChain.getId(), savedRuleChain, null, ActionType.ADDED, null);
  165 +
  166 + return savedRuleChain;
  167 + } catch (Exception e) {
  168 + RuleChain ruleChain = new RuleChain();
  169 + ruleChain.setName(request.getName());
  170 + logEntityAction(emptyId(EntityType.RULE_CHAIN), ruleChain, null, ActionType.ADDED, e);
  171 + throw handleException(e);
  172 + }
  173 + }
  174 +
  175 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
150 176 @RequestMapping(value = "/ruleChain/{ruleChainId}/root", method = RequestMethod.POST)
151 177 @ResponseBody
152 178 public RuleChain setRootRuleChain(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
... ...
... ... @@ -57,6 +57,7 @@ public class InstallScripts {
57 57 public static final String JSON_DIR = "json";
58 58 public static final String SYSTEM_DIR = "system";
59 59 public static final String TENANT_DIR = "tenant";
  60 + public static final String DEVICE_PROFILE_DIR = "device_profile";
60 61 public static final String DEMO_DIR = "demo";
61 62 public static final String RULE_CHAINS_DIR = "rule_chains";
62 63 public static final String WIDGET_BUNDLES_DIR = "widget_bundles";
... ... @@ -83,6 +84,10 @@ public class InstallScripts {
83 84 return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR);
84 85 }
85 86
  87 + public Path getDeviceProfileDefaultRuleChainTemplateFilePath() {
  88 + return Paths.get(getDataDir(), JSON_DIR, DEVICE_PROFILE_DIR, "rule_chain_template.json");
  89 + }
  90 +
86 91 public String getDataDir() {
87 92 if (!StringUtils.isEmpty(dataDir)) {
88 93 if (!Paths.get(this.dataDir).toFile().isDirectory()) {
... ... @@ -110,15 +115,7 @@ public class InstallScripts {
110 115 dirStream.forEach(
111 116 path -> {
112 117 try {
113   - JsonNode ruleChainJson = objectMapper.readTree(path.toFile());
114   - RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class);
115   - RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class);
116   -
117   - ruleChain.setTenantId(tenantId);
118   - ruleChain = ruleChainService.saveRuleChain(ruleChain);
119   -
120   - ruleChainMetaData.setRuleChainId(ruleChain.getId());
121   - ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData);
  118 + createRuleChainFromFile(tenantId, path, null);
122 119 } catch (Exception e) {
123 120 log.error("Unable to load rule chain from json: [{}]", path.toString());
124 121 throw new RuntimeException("Unable to load rule chain from json", e);
... ... @@ -128,6 +125,28 @@ public class InstallScripts {
128 125 }
129 126 }
130 127
  128 + public RuleChain createDefaultRuleChain(TenantId tenantId, String ruleChainName) throws IOException {
  129 + return createRuleChainFromFile(tenantId, getDeviceProfileDefaultRuleChainTemplateFilePath(), ruleChainName);
  130 + }
  131 +
  132 + public RuleChain createRuleChainFromFile(TenantId tenantId, Path templateFilePath, String newRuleChainName) throws IOException {
  133 + JsonNode ruleChainJson = objectMapper.readTree(templateFilePath.toFile());
  134 + RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class);
  135 + RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class);
  136 +
  137 + ruleChain.setTenantId(tenantId);
  138 + if (!StringUtils.isEmpty(newRuleChainName)) {
  139 + ruleChain.setName(newRuleChainName);
  140 + }
  141 + ruleChain = ruleChainService.saveRuleChain(ruleChain);
  142 +
  143 + ruleChainMetaData.setRuleChainId(ruleChain.getId());
  144 + ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData);
  145 +
  146 + return ruleChain;
  147 + }
  148 +
  149 +
131 150 public void loadSystemWidgets() throws Exception {
132 151 Path widgetBundlesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR);
133 152 try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) {
... ...
  1 +/**
  2 + * Copyright © 2016-2020 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.server.common.data.rule;
  17 +
  18 +import lombok.Data;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +
  21 +import java.io.Serializable;
  22 +
  23 +@Data
  24 +@Slf4j
  25 +public class DefaultRuleChainCreateRequest implements Serializable {
  26 +
  27 + private static final long serialVersionUID = 5600333716030561537L;
  28 +
  29 + private String name;
  30 +
  31 +}
... ...
... ... @@ -22,6 +22,7 @@ import org.thingsboard.rule.engine.api.TbContext;
22 22 import org.thingsboard.server.common.data.DataConstants;
23 23 import org.thingsboard.server.common.data.alarm.Alarm;
24 24 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
  25 +import org.thingsboard.server.common.data.alarm.AlarmStatus;
25 26 import org.thingsboard.server.common.data.device.profile.AlarmCondition;
26 27 import org.thingsboard.server.common.data.device.profile.AlarmRule;
27 28 import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
... ... @@ -34,11 +35,13 @@ import org.thingsboard.server.common.data.query.NumericFilterPredicate;
34 35 import org.thingsboard.server.common.data.query.StringFilterPredicate;
35 36 import org.thingsboard.server.common.msg.TbMsg;
36 37 import org.thingsboard.server.common.msg.TbMsgMetaData;
  38 +import org.thingsboard.server.dao.alarm.AlarmService;
37 39 import org.thingsboard.server.dao.util.mapping.JacksonUtil;
38 40
39 41 import java.util.Comparator;
40 42 import java.util.Map;
41 43 import java.util.TreeMap;
  44 +import java.util.concurrent.ExecutionException;
42 45
43 46 @Data
44 47 class DeviceProfileAlarmState {
... ... @@ -47,6 +50,7 @@ class DeviceProfileAlarmState {
47 50 private final DeviceProfileAlarm alarmDefinition;
48 51 private volatile Map<AlarmSeverity, AlarmRule> createRulesSortedBySeverityDesc;
49 52 private volatile Alarm currentAlarm;
  53 + private volatile boolean initialFetchDone;
50 54
51 55 public DeviceProfileAlarmState(EntityId originator, DeviceProfileAlarm alarmDefinition) {
52 56 this.originator = originator;
... ... @@ -55,7 +59,15 @@ class DeviceProfileAlarmState {
55 59 this.createRulesSortedBySeverityDesc.putAll(alarmDefinition.getCreateRules());
56 60 }
57 61
58   - public void process(TbContext ctx, TbMsg msg, DeviceDataSnapshot data) {
  62 + public void process(TbContext ctx, TbMsg msg, DeviceDataSnapshot data) throws ExecutionException, InterruptedException {
  63 + if (!initialFetchDone) {
  64 + Alarm alarm = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), originator, alarmDefinition.getAlarmType()).get();
  65 + if (alarm != null && !alarm.getStatus().isCleared()) {
  66 + currentAlarm = alarm;
  67 + }
  68 + initialFetchDone = true;
  69 + }
  70 +
59 71 AlarmSeverity resultSeverity = null;
60 72 for (Map.Entry<AlarmSeverity, AlarmRule> kv : createRulesSortedBySeverityDesc.entrySet()) {
61 73 AlarmRule alarmRule = kv.getValue();
... ... @@ -69,6 +81,7 @@ class DeviceProfileAlarmState {
69 81 } else if (currentAlarm != null) {
70 82 AlarmRule clearRule = alarmDefinition.getClearRule();
71 83 if (eval(clearRule.getCondition(), data)) {
  84 + ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis());
72 85 pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm), msg);
73 86 currentAlarm = null;
74 87 }
... ... @@ -112,6 +125,8 @@ class DeviceProfileAlarmState {
112 125 }
113 126 } else {
114 127 currentAlarm = new Alarm();
  128 + currentAlarm.setType(alarmDefinition.getAlarmType());
  129 + currentAlarm.setStatus(AlarmStatus.ACTIVE_UNACK);
115 130 currentAlarm.setSeverity(severity);
116 131 currentAlarm.setStartTs(System.currentTimeMillis());
117 132 currentAlarm.setEndTs(currentAlarm.getStartTs());
... ...
... ... @@ -62,15 +62,17 @@ class DeviceState {
62 62 }
63 63 }
64 64
65   - private void processTelemetry(TbContext ctx, TbMsg msg) {
  65 + private void processTelemetry(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException {
66 66 Map<Long, List<KvEntry>> tsKvMap = JsonConverter.convertToSortedTelemetry(new JsonParser().parse(msg.getData()), TbMsgTimeseriesNode.getTs(msg));
67   - tsKvMap.forEach((ts, data) -> {
  67 + for (Map.Entry<Long, List<KvEntry>> entry : tsKvMap.entrySet()) {
  68 + Long ts = entry.getKey();
  69 + List<KvEntry> data = entry.getValue();
68 70 latestValues = merge(latestValues, ts, data);
69 71 for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) {
70 72 DeviceProfileAlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), a -> new DeviceProfileAlarmState(msg.getOriginator(), alarm));
71 73 alarmState.process(ctx, msg, latestValues);
72 74 }
73   - });
  75 + }
74 76 ctx.tellSuccess(msg);
75 77 }
76 78
... ... @@ -140,7 +142,9 @@ class DeviceState {
140 142 if (!latestTsKeys.isEmpty()) {
141 143 List<TsKvEntry> data = ctx.getTimeseriesService().findLatest(ctx.getTenantId(), originator, latestTsKeys).get();
142 144 for (TsKvEntry entry : data) {
143   - result.putValue(new EntityKey(EntityKeyType.TIME_SERIES, entry.getKey()), toEntityValue(entry));
  145 + if (entry.getValue() != null) {
  146 + result.putValue(new EntityKey(EntityKeyType.TIME_SERIES, entry.getKey()), toEntityValue(entry));
  147 + }
144 148 }
145 149 }
146 150 if (!clientAttributeKeys.isEmpty()) {
... ... @@ -161,10 +165,12 @@ class DeviceState {
161 165
162 166 private void addToSnapshot(DeviceDataSnapshot snapshot, Set<String> commonAttributeKeys, List<AttributeKvEntry> data) {
163 167 for (AttributeKvEntry entry : data) {
164   - EntityKeyValue value = toEntityValue(entry);
165   - snapshot.putValue(new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, entry.getKey()), value);
166   - if (commonAttributeKeys.contains(entry.getKey())) {
167   - snapshot.putValue(new EntityKey(EntityKeyType.ATTRIBUTE, entry.getKey()), value);
  168 + if (entry.getValue() != null) {
  169 + EntityKeyValue value = toEntityValue(entry);
  170 + snapshot.putValue(new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, entry.getKey()), value);
  171 + if (commonAttributeKeys.contains(entry.getKey())) {
  172 + snapshot.putValue(new EntityKey(EntityKeyType.ATTRIBUTE, entry.getKey()), value);
  173 + }
168 174 }
169 175 }
170 176 }
... ...
... ... @@ -137,6 +137,7 @@ public class TbDeviceProfileNodeTest {
137 137 alarmRule.setCondition(alarmCondition);
138 138 DeviceProfileAlarm dpa = new DeviceProfileAlarm();
139 139 dpa.setId("highTemperatureAlarmID");
  140 + dpa.setAlarmType("highTemperatureAlarm");
140 141 dpa.setCreateRules(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule));
141 142 deviceProfileData.setAlarms(Collections.singletonList(dpa));
142 143 deviceProfile.setProfileData(deviceProfileData);
... ... @@ -144,6 +145,7 @@ public class TbDeviceProfileNodeTest {
144 145 Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
145 146 Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
146 147 .thenReturn(Futures.immediateFuture(Collections.emptyList()));
  148 + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")).thenReturn(Futures.immediateFuture(null));
147 149 Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
148 150
149 151 TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
... ...