Method to create default rule chain for device profile
This commit is contained in:
parent
e353ab3c81
commit
4a3b28d331
@ -0,0 +1,135 @@
|
||||
{
|
||||
"ruleChain": {
|
||||
"additionalInfo": {
|
||||
"description": ""
|
||||
},
|
||||
"name": "Device Profile Rule Chain Template",
|
||||
"firstRuleNodeId": null,
|
||||
"root": false,
|
||||
"debugMode": false,
|
||||
"configuration": null
|
||||
},
|
||||
"metadata": {
|
||||
"firstNodeIndex": 6,
|
||||
"nodes": [
|
||||
{
|
||||
"additionalInfo": {
|
||||
"layoutX": 822,
|
||||
"layoutY": 294
|
||||
},
|
||||
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
|
||||
"name": "Save Timeseries",
|
||||
"debugMode": false,
|
||||
"configuration": {
|
||||
"defaultTTL": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"additionalInfo": {
|
||||
"layoutX": 824,
|
||||
"layoutY": 221
|
||||
},
|
||||
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
|
||||
"name": "Save Client Attributes",
|
||||
"debugMode": false,
|
||||
"configuration": {
|
||||
"scope": "CLIENT_SCOPE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"additionalInfo": {
|
||||
"layoutX": 494,
|
||||
"layoutY": 309
|
||||
},
|
||||
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
|
||||
"name": "Message Type Switch",
|
||||
"debugMode": false,
|
||||
"configuration": {
|
||||
"version": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"additionalInfo": {
|
||||
"layoutX": 824,
|
||||
"layoutY": 383
|
||||
},
|
||||
"type": "org.thingsboard.rule.engine.action.TbLogNode",
|
||||
"name": "Log RPC from Device",
|
||||
"debugMode": false,
|
||||
"configuration": {
|
||||
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
|
||||
}
|
||||
},
|
||||
{
|
||||
"additionalInfo": {
|
||||
"layoutX": 823,
|
||||
"layoutY": 444
|
||||
},
|
||||
"type": "org.thingsboard.rule.engine.action.TbLogNode",
|
||||
"name": "Log Other",
|
||||
"debugMode": false,
|
||||
"configuration": {
|
||||
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
|
||||
}
|
||||
},
|
||||
{
|
||||
"additionalInfo": {
|
||||
"layoutX": 822,
|
||||
"layoutY": 507
|
||||
},
|
||||
"type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
|
||||
"name": "RPC Call Request",
|
||||
"debugMode": false,
|
||||
"configuration": {
|
||||
"timeoutInSeconds": 60
|
||||
}
|
||||
},
|
||||
{
|
||||
"additionalInfo": {
|
||||
"description": "",
|
||||
"layoutX": 209,
|
||||
"layoutY": 307
|
||||
},
|
||||
"type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
|
||||
"name": "Device Profile Node",
|
||||
"debugMode": false,
|
||||
"configuration": {
|
||||
"version": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"fromIndex": 2,
|
||||
"toIndex": 4,
|
||||
"type": "Other"
|
||||
},
|
||||
{
|
||||
"fromIndex": 2,
|
||||
"toIndex": 1,
|
||||
"type": "Post attributes"
|
||||
},
|
||||
{
|
||||
"fromIndex": 2,
|
||||
"toIndex": 0,
|
||||
"type": "Post telemetry"
|
||||
},
|
||||
{
|
||||
"fromIndex": 2,
|
||||
"toIndex": 3,
|
||||
"type": "RPC Request from Device"
|
||||
},
|
||||
{
|
||||
"fromIndex": 2,
|
||||
"toIndex": 5,
|
||||
"type": "RPC Request to Device"
|
||||
},
|
||||
{
|
||||
"fromIndex": 6,
|
||||
"toIndex": 2,
|
||||
"type": "Success"
|
||||
}
|
||||
],
|
||||
"ruleChainConnections": null
|
||||
}
|
||||
}
|
||||
@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.page.PageData;
|
||||
import org.thingsboard.server.common.data.page.PageLink;
|
||||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
|
||||
import org.thingsboard.server.common.data.rule.DefaultRuleChainCreateRequest;
|
||||
import org.thingsboard.server.common.data.rule.RuleChain;
|
||||
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
|
||||
import org.thingsboard.server.common.data.rule.RuleNode;
|
||||
@ -55,6 +56,7 @@ import org.thingsboard.server.common.msg.TbMsgDataType;
|
||||
import org.thingsboard.server.common.msg.TbMsgMetaData;
|
||||
import org.thingsboard.server.dao.event.EventService;
|
||||
import org.thingsboard.server.queue.util.TbCoreComponent;
|
||||
import org.thingsboard.server.service.install.InstallScripts;
|
||||
import org.thingsboard.server.service.script.JsInvokeService;
|
||||
import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
|
||||
import org.thingsboard.server.service.security.permission.Operation;
|
||||
@ -77,6 +79,9 @@ public class RuleChainController extends BaseController {
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Autowired
|
||||
private InstallScripts installScripts;
|
||||
|
||||
@Autowired
|
||||
private EventService eventService;
|
||||
|
||||
@ -146,6 +151,27 @@ public class RuleChainController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
|
||||
@RequestMapping(value = "/ruleChain/device/default", method = RequestMethod.POST)
|
||||
@ResponseBody
|
||||
public RuleChain saveRuleChain(@RequestBody DefaultRuleChainCreateRequest request) throws ThingsboardException {
|
||||
try {
|
||||
checkNotNull(request);
|
||||
checkNotNull(request.getName());
|
||||
|
||||
RuleChain savedRuleChain = installScripts.createDefaultRuleChain(getCurrentUser().getTenantId(), request.getName());
|
||||
|
||||
logEntityAction(savedRuleChain.getId(), savedRuleChain, null, ActionType.ADDED, null);
|
||||
|
||||
return savedRuleChain;
|
||||
} catch (Exception e) {
|
||||
RuleChain ruleChain = new RuleChain();
|
||||
ruleChain.setName(request.getName());
|
||||
logEntityAction(emptyId(EntityType.RULE_CHAIN), ruleChain, null, ActionType.ADDED, e);
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
|
||||
@RequestMapping(value = "/ruleChain/{ruleChainId}/root", method = RequestMethod.POST)
|
||||
@ResponseBody
|
||||
|
||||
@ -57,6 +57,7 @@ public class InstallScripts {
|
||||
public static final String JSON_DIR = "json";
|
||||
public static final String SYSTEM_DIR = "system";
|
||||
public static final String TENANT_DIR = "tenant";
|
||||
public static final String DEVICE_PROFILE_DIR = "device_profile";
|
||||
public static final String DEMO_DIR = "demo";
|
||||
public static final String RULE_CHAINS_DIR = "rule_chains";
|
||||
public static final String WIDGET_BUNDLES_DIR = "widget_bundles";
|
||||
@ -83,6 +84,10 @@ public class InstallScripts {
|
||||
return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR);
|
||||
}
|
||||
|
||||
public Path getDeviceProfileDefaultRuleChainTemplateFilePath() {
|
||||
return Paths.get(getDataDir(), JSON_DIR, DEVICE_PROFILE_DIR, "rule_chain_template.json");
|
||||
}
|
||||
|
||||
public String getDataDir() {
|
||||
if (!StringUtils.isEmpty(dataDir)) {
|
||||
if (!Paths.get(this.dataDir).toFile().isDirectory()) {
|
||||
@ -110,15 +115,7 @@ public class InstallScripts {
|
||||
dirStream.forEach(
|
||||
path -> {
|
||||
try {
|
||||
JsonNode ruleChainJson = objectMapper.readTree(path.toFile());
|
||||
RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class);
|
||||
RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class);
|
||||
|
||||
ruleChain.setTenantId(tenantId);
|
||||
ruleChain = ruleChainService.saveRuleChain(ruleChain);
|
||||
|
||||
ruleChainMetaData.setRuleChainId(ruleChain.getId());
|
||||
ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData);
|
||||
createRuleChainFromFile(tenantId, path, null);
|
||||
} catch (Exception e) {
|
||||
log.error("Unable to load rule chain from json: [{}]", path.toString());
|
||||
throw new RuntimeException("Unable to load rule chain from json", e);
|
||||
@ -128,6 +125,28 @@ public class InstallScripts {
|
||||
}
|
||||
}
|
||||
|
||||
public RuleChain createDefaultRuleChain(TenantId tenantId, String ruleChainName) throws IOException {
|
||||
return createRuleChainFromFile(tenantId, getDeviceProfileDefaultRuleChainTemplateFilePath(), ruleChainName);
|
||||
}
|
||||
|
||||
public RuleChain createRuleChainFromFile(TenantId tenantId, Path templateFilePath, String newRuleChainName) throws IOException {
|
||||
JsonNode ruleChainJson = objectMapper.readTree(templateFilePath.toFile());
|
||||
RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class);
|
||||
RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class);
|
||||
|
||||
ruleChain.setTenantId(tenantId);
|
||||
if (!StringUtils.isEmpty(newRuleChainName)) {
|
||||
ruleChain.setName(newRuleChainName);
|
||||
}
|
||||
ruleChain = ruleChainService.saveRuleChain(ruleChain);
|
||||
|
||||
ruleChainMetaData.setRuleChainId(ruleChain.getId());
|
||||
ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData);
|
||||
|
||||
return ruleChain;
|
||||
}
|
||||
|
||||
|
||||
public void loadSystemWidgets() throws Exception {
|
||||
Path widgetBundlesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR);
|
||||
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) {
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright © 2016-2020 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.thingsboard.server.common.data.rule;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@Slf4j
|
||||
public class DefaultRuleChainCreateRequest implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 5600333716030561537L;
|
||||
|
||||
private String name;
|
||||
|
||||
}
|
||||
@ -22,6 +22,7 @@ import org.thingsboard.rule.engine.api.TbContext;
|
||||
import org.thingsboard.server.common.data.DataConstants;
|
||||
import org.thingsboard.server.common.data.alarm.Alarm;
|
||||
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
|
||||
import org.thingsboard.server.common.data.alarm.AlarmStatus;
|
||||
import org.thingsboard.server.common.data.device.profile.AlarmCondition;
|
||||
import org.thingsboard.server.common.data.device.profile.AlarmRule;
|
||||
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
|
||||
@ -34,11 +35,13 @@ import org.thingsboard.server.common.data.query.NumericFilterPredicate;
|
||||
import org.thingsboard.server.common.data.query.StringFilterPredicate;
|
||||
import org.thingsboard.server.common.msg.TbMsg;
|
||||
import org.thingsboard.server.common.msg.TbMsgMetaData;
|
||||
import org.thingsboard.server.dao.alarm.AlarmService;
|
||||
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@Data
|
||||
class DeviceProfileAlarmState {
|
||||
@ -47,6 +50,7 @@ class DeviceProfileAlarmState {
|
||||
private final DeviceProfileAlarm alarmDefinition;
|
||||
private volatile Map<AlarmSeverity, AlarmRule> createRulesSortedBySeverityDesc;
|
||||
private volatile Alarm currentAlarm;
|
||||
private volatile boolean initialFetchDone;
|
||||
|
||||
public DeviceProfileAlarmState(EntityId originator, DeviceProfileAlarm alarmDefinition) {
|
||||
this.originator = originator;
|
||||
@ -55,7 +59,15 @@ class DeviceProfileAlarmState {
|
||||
this.createRulesSortedBySeverityDesc.putAll(alarmDefinition.getCreateRules());
|
||||
}
|
||||
|
||||
public void process(TbContext ctx, TbMsg msg, DeviceDataSnapshot data) {
|
||||
public void process(TbContext ctx, TbMsg msg, DeviceDataSnapshot data) throws ExecutionException, InterruptedException {
|
||||
if (!initialFetchDone) {
|
||||
Alarm alarm = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), originator, alarmDefinition.getAlarmType()).get();
|
||||
if (alarm != null && !alarm.getStatus().isCleared()) {
|
||||
currentAlarm = alarm;
|
||||
}
|
||||
initialFetchDone = true;
|
||||
}
|
||||
|
||||
AlarmSeverity resultSeverity = null;
|
||||
for (Map.Entry<AlarmSeverity, AlarmRule> kv : createRulesSortedBySeverityDesc.entrySet()) {
|
||||
AlarmRule alarmRule = kv.getValue();
|
||||
@ -69,6 +81,7 @@ class DeviceProfileAlarmState {
|
||||
} else if (currentAlarm != null) {
|
||||
AlarmRule clearRule = alarmDefinition.getClearRule();
|
||||
if (eval(clearRule.getCondition(), data)) {
|
||||
ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis());
|
||||
pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm), msg);
|
||||
currentAlarm = null;
|
||||
}
|
||||
@ -112,6 +125,8 @@ class DeviceProfileAlarmState {
|
||||
}
|
||||
} else {
|
||||
currentAlarm = new Alarm();
|
||||
currentAlarm.setType(alarmDefinition.getAlarmType());
|
||||
currentAlarm.setStatus(AlarmStatus.ACTIVE_UNACK);
|
||||
currentAlarm.setSeverity(severity);
|
||||
currentAlarm.setStartTs(System.currentTimeMillis());
|
||||
currentAlarm.setEndTs(currentAlarm.getStartTs());
|
||||
|
||||
@ -62,15 +62,17 @@ class DeviceState {
|
||||
}
|
||||
}
|
||||
|
||||
private void processTelemetry(TbContext ctx, TbMsg msg) {
|
||||
private void processTelemetry(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException {
|
||||
Map<Long, List<KvEntry>> tsKvMap = JsonConverter.convertToSortedTelemetry(new JsonParser().parse(msg.getData()), TbMsgTimeseriesNode.getTs(msg));
|
||||
tsKvMap.forEach((ts, data) -> {
|
||||
for (Map.Entry<Long, List<KvEntry>> entry : tsKvMap.entrySet()) {
|
||||
Long ts = entry.getKey();
|
||||
List<KvEntry> data = entry.getValue();
|
||||
latestValues = merge(latestValues, ts, data);
|
||||
for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) {
|
||||
DeviceProfileAlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), a -> new DeviceProfileAlarmState(msg.getOriginator(), alarm));
|
||||
alarmState.process(ctx, msg, latestValues);
|
||||
}
|
||||
});
|
||||
}
|
||||
ctx.tellSuccess(msg);
|
||||
}
|
||||
|
||||
@ -140,7 +142,9 @@ class DeviceState {
|
||||
if (!latestTsKeys.isEmpty()) {
|
||||
List<TsKvEntry> data = ctx.getTimeseriesService().findLatest(ctx.getTenantId(), originator, latestTsKeys).get();
|
||||
for (TsKvEntry entry : data) {
|
||||
result.putValue(new EntityKey(EntityKeyType.TIME_SERIES, entry.getKey()), toEntityValue(entry));
|
||||
if (entry.getValue() != null) {
|
||||
result.putValue(new EntityKey(EntityKeyType.TIME_SERIES, entry.getKey()), toEntityValue(entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!clientAttributeKeys.isEmpty()) {
|
||||
@ -161,10 +165,12 @@ class DeviceState {
|
||||
|
||||
private void addToSnapshot(DeviceDataSnapshot snapshot, Set<String> commonAttributeKeys, List<AttributeKvEntry> data) {
|
||||
for (AttributeKvEntry entry : data) {
|
||||
EntityKeyValue value = toEntityValue(entry);
|
||||
snapshot.putValue(new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, entry.getKey()), value);
|
||||
if (commonAttributeKeys.contains(entry.getKey())) {
|
||||
snapshot.putValue(new EntityKey(EntityKeyType.ATTRIBUTE, entry.getKey()), value);
|
||||
if (entry.getValue() != null) {
|
||||
EntityKeyValue value = toEntityValue(entry);
|
||||
snapshot.putValue(new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, entry.getKey()), value);
|
||||
if (commonAttributeKeys.contains(entry.getKey())) {
|
||||
snapshot.putValue(new EntityKey(EntityKeyType.ATTRIBUTE, entry.getKey()), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,6 +137,7 @@ public class TbDeviceProfileNodeTest {
|
||||
alarmRule.setCondition(alarmCondition);
|
||||
DeviceProfileAlarm dpa = new DeviceProfileAlarm();
|
||||
dpa.setId("highTemperatureAlarmID");
|
||||
dpa.setAlarmType("highTemperatureAlarm");
|
||||
dpa.setCreateRules(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule));
|
||||
deviceProfileData.setAlarms(Collections.singletonList(dpa));
|
||||
deviceProfile.setProfileData(deviceProfileData);
|
||||
@ -144,6 +145,7 @@ public class TbDeviceProfileNodeTest {
|
||||
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
|
||||
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
|
||||
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
|
||||
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")).thenReturn(Futures.immediateFuture(null));
|
||||
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
|
||||
|
||||
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user