merge with master

This commit is contained in:
ShvaykaD 2020-11-16 19:13:00 +02:00
commit dc310f4cab
11 changed files with 332 additions and 191 deletions

View File

@ -219,8 +219,8 @@
"defaultPageSize": 10, "defaultPageSize": 10,
"defaultSortOrder": "-createdTime", "defaultSortOrder": "-createdTime",
"enableSelectColumnDisplay": false, "enableSelectColumnDisplay": false,
"enableStatusFilter": true, "alarmsTitle": "Alarms",
"alarmsTitle": "Alarms" "enableFilter": true
}, },
"title": "New Alarms table", "title": "New Alarms table",
"dropShadow": true, "dropShadow": true,
@ -234,6 +234,9 @@
"showLegend": false, "showLegend": false,
"alarmSource": { "alarmSource": {
"type": "entity", "type": "entity",
"name": "alarms",
"entityAliasId": "68a058e1-fdda-8482-715b-3ae4a488568e",
"filterId": null,
"dataKeys": [ "dataKeys": [
{ {
"name": "createdTime", "name": "createdTime",
@ -275,9 +278,7 @@
"settings": {}, "settings": {},
"_hash": 0.7977920750136249 "_hash": 0.7977920750136249
} }
], ]
"entityAliasId": "ce27a9d0-93bf-b7a4-054d-d0369a8cf813",
"name": "alarms"
}, },
"alarmSearchStatus": "ANY", "alarmSearchStatus": "ANY",
"alarmsPollingInterval": 5, "alarmsPollingInterval": 5,
@ -1031,7 +1032,8 @@
"markerImageFunction": "var res;\nif(dsData[dsIndex].active !== \"true\"){\n\tvar res = {\n\t url: images[0],\n\t size: 48\n\t}\n} else {\n var res = {\n\t url: images[1],\n\t size: 48\n\t}\n}\nreturn res;", "markerImageFunction": "var res;\nif(dsData[dsIndex].active !== \"true\"){\n\tvar res = {\n\t url: images[0],\n\t size: 48\n\t}\n} else {\n var res = {\n\t url: images[1],\n\t size: 48\n\t}\n}\nreturn res;",
"useLabelFunction": true, "useLabelFunction": true,
"provider": "openstreet-map", "provider": "openstreet-map",
"draggableMarker": true "draggableMarker": true,
"editablePolygon": true
}, },
"title": "New Markers Placement - OpenStreetMap", "title": "New Markers Placement - OpenStreetMap",
"dropShadow": true, "dropShadow": true,
@ -1062,61 +1064,6 @@
"displayTimewindow": true "displayTimewindow": true
}, },
"id": "0a430429-9078-9ae6-2b67-e4a15a2bf8bf" "id": "0a430429-9078-9ae6-2b67-e4a15a2bf8bf"
},
"f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3": {
"isSystemType": true,
"bundleAlias": "input_widgets",
"typeAlias": "update_double_timeseries",
"type": "latest",
"title": "New widget",
"sizeX": 7.5,
"sizeY": 3,
"config": {
"datasources": [
{
"type": "entity",
"name": null,
"entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547",
"dataKeys": [
{
"name": "temperature",
"type": "timeseries",
"label": "temperature",
"color": "#2196f3",
"settings": {},
"_hash": 0.4164505192982848
}
]
}
],
"timewindow": {
"realtime": {
"timewindowMs": 60000
}
},
"showTitle": true,
"backgroundColor": "#fff",
"color": "rgba(0, 0, 0, 0.87)",
"padding": "8px",
"settings": {
"showResultMessage": true,
"showLabel": true
},
"title": "New Update double timeseries",
"dropShadow": true,
"enableFullscreen": false,
"widgetStyle": {},
"titleStyle": {
"fontSize": "16px",
"fontWeight": 400
},
"useDashboardTimewindow": true,
"showLegend": false,
"actions": {}
},
"row": 0,
"col": 0,
"id": "f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3"
} }
}, },
"states": { "states": {
@ -1215,12 +1162,6 @@
"sizeY": 6, "sizeY": 6,
"row": 6, "row": 6,
"col": 0 "col": 0
},
"f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3": {
"sizeX": 7.5,
"sizeY": 3,
"row": 12,
"col": 0
} }
}, },
"gridSettings": { "gridSettings": {
@ -1257,16 +1198,6 @@
"stateEntityParamName": null, "stateEntityParamName": null,
"defaultStateEntity": null "defaultStateEntity": null
} }
},
"ce27a9d0-93bf-b7a4-054d-d0369a8cf813": {
"id": "ce27a9d0-93bf-b7a4-054d-d0369a8cf813",
"alias": "Thermostat-alarm",
"filter": {
"type": "entityName",
"resolveMultiple": false,
"entityType": "ASSET",
"entityNameFilter": "Thermostat Alarms"
}
} }
}, },
"timewindow": { "timewindow": {
@ -1301,7 +1232,8 @@
"showDashboardTimewindow": true, "showDashboardTimewindow": true,
"showDashboardExport": true, "showDashboardExport": true,
"toolbarAlwaysOpen": true "toolbarAlwaysOpen": true
} },
"filters": {}
}, },
"name": "Thermostats" "name": "Thermostats"
} }

View File

@ -1,6 +1,8 @@
{ {
"ruleChain": { "ruleChain": {
"additionalInfo": null, "additionalInfo": {
"description": ""
},
"name": "Thermostat Alarms", "name": "Thermostat Alarms",
"firstRuleNodeId": null, "firstRuleNodeId": null,
"root": false, "root": false,
@ -8,131 +10,126 @@
"configuration": null "configuration": null
}, },
"metadata": { "metadata": {
"firstNodeIndex": 5, "firstNodeIndex": 6,
"nodes": [ "nodes": [
{ {
"additionalInfo": { "additionalInfo": {
"layoutX": 929, "layoutX": 822,
"layoutY": 67 "layoutY": 294
}, },
"type": "org.thingsboard.rule.engine.action.TbCreateAlarmNode", "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "Create Temp Alarm", "name": "Save Timeseries",
"debugMode": false, "debugMode": false,
"configuration": { "configuration": {
"alarmType": "High Temperature", "defaultTTL": 0
"alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\ndetails.triggerValue = msg.temperature;\nreturn details;",
"severity": "MAJOR",
"propagate": true,
"useMessageAlarmData": false,
"relationTypes": [
"ToAlarmPropagationAsset"
]
} }
}, },
{ {
"additionalInfo": { "additionalInfo": {
"layoutX": 930, "description": null,
"layoutY": 201 "layoutX": 824,
"layoutY": 221
}, },
"type": "org.thingsboard.rule.engine.action.TbClearAlarmNode", "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Clear Temp Alarm", "name": "Save Client Attributes",
"debugMode": false, "debugMode": false,
"configuration": { "configuration": {
"alarmType": "High Temperature", "scope": "SERVER_SCOPE",
"alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\nreturn details;" "notifyDevice": null
} }
}, },
{ {
"additionalInfo": { "additionalInfo": {
"layoutX": 930, "layoutX": 494,
"layoutY": 131 "layoutY": 309
}, },
"type": "org.thingsboard.rule.engine.action.TbCreateAlarmNode", "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "Create Humidity Alarm", "name": "Message Type Switch",
"debugMode": false, "debugMode": false,
"configuration": { "configuration": {
"alarmType": "Low Humidity", "version": 0
"alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\ndetails.triggerValue = msg.humidity;\nreturn details;",
"severity": "MINOR",
"propagate": true,
"useMessageAlarmData": false,
"relationTypes": [
"ToAlarmPropagationAsset"
]
} }
}, },
{ {
"additionalInfo": { "additionalInfo": {
"layoutX": 929, "layoutX": 824,
"layoutY": 275 "layoutY": 383
}, },
"type": "org.thingsboard.rule.engine.action.TbClearAlarmNode", "type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Clear Humidity Alarm", "name": "Log RPC from Device",
"debugMode": false, "debugMode": false,
"configuration": { "configuration": {
"alarmType": "Low Humidity", "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
"alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\nreturn details;"
} }
}, },
{ {
"additionalInfo": { "additionalInfo": {
"layoutX": 586, "layoutX": 823,
"layoutY": 148 "layoutY": 444
}, },
"type": "org.thingsboard.rule.engine.filter.TbJsSwitchNode", "type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Check Alarms", "name": "Log Other",
"debugMode": false, "debugMode": false,
"configuration": { "configuration": {
"jsScript": "var relations = [];\nif(metadata[\"ss_alarmTemperature\"] === \"true\"){\n if(msg.temperature > metadata[\"ss_thresholdTemperature\"]){\n relations.push(\"NewTempAlarm\");\n } else {\n relations.push(\"ClearTempAlarm\");\n }\n}\nif(metadata[\"ss_alarmHumidity\"] === \"true\"){\n if(msg.humidity < metadata[\"ss_thresholdHumidity\"]){\n relations.push(\"NewHumidityAlarm\");\n } else {\n relations.push(\"ClearHumidityAlarm\");\n }\n}\n\nreturn relations;" "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
} }
}, },
{ {
"additionalInfo": { "additionalInfo": {
"layoutX": 321, "layoutX": 822,
"layoutY": 149 "layoutY": 507
}, },
"type": "org.thingsboard.rule.engine.metadata.TbGetAttributesNode", "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
"name": "Fetch Configuration", "name": "RPC Call Request",
"debugMode": false, "debugMode": false,
"configuration": { "configuration": {
"clientAttributeNames": [], "timeoutInSeconds": 60
"sharedAttributeNames": [], }
"serverAttributeNames": [ },
"alarmTemperature", {
"thresholdTemperature", "additionalInfo": {
"alarmHumidity", "description": "",
"thresholdHumidity" "layoutX": 209,
], "layoutY": 307
"latestTsKeyNames": [], },
"tellFailureIfAbsent": false, "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
"getLatestValueWithTs": false "name": "Device Profile Node",
"debugMode": false,
"configuration": {
"persistAlarmRulesState": false,
"fetchAlarmRulesStateOnStart": false
} }
} }
], ],
"connections": [ "connections": [
{ {
"fromIndex": 4, "fromIndex": 2,
"toIndex": 0,
"type": "NewTempAlarm"
},
{
"fromIndex": 4,
"toIndex": 1,
"type": "ClearTempAlarm"
},
{
"fromIndex": 4,
"toIndex": 2,
"type": "NewHumidityAlarm"
},
{
"fromIndex": 4,
"toIndex": 3,
"type": "ClearHumidityAlarm"
},
{
"fromIndex": 5,
"toIndex": 4, "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" "type": "Success"
} }
], ],

View File

@ -28,12 +28,21 @@ import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
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.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
@ -42,19 +51,29 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.DynamicValueSourceType;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
@ -62,6 +81,8 @@ import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.dao.widget.WidgetsBundleService;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
@Service @Service
@Profile("install") @Profile("install")
@ -96,12 +117,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
@Autowired @Autowired
private CustomerService customerService; private CustomerService customerService;
@Autowired
private RelationService relationService;
@Autowired
private AssetService assetService;
@Autowired @Autowired
private DeviceService deviceService; private DeviceService deviceService;
@ -114,6 +129,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
@Autowired @Autowired
private DeviceCredentialsService deviceCredentialsService; private DeviceCredentialsService deviceCredentialsService;
@Autowired
private RuleChainService ruleChainService;
@Bean @Bean
protected BCryptPasswordEncoder passwordEncoder() { protected BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); return new BCryptPasswordEncoder();
@ -245,19 +263,133 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " + createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " +
"Raspberry Pi GPIO control sample application"); "Raspberry Pi GPIO control sample application");
Asset thermostatAlarms = new Asset(); DeviceProfile thermostatDeviceProfile = new DeviceProfile();
thermostatAlarms.setTenantId(demoTenant.getId()); thermostatDeviceProfile.setTenantId(demoTenant.getId());
thermostatAlarms.setName("Thermostat Alarms"); thermostatDeviceProfile.setDefault(false);
thermostatAlarms.setType("AlarmPropagationAsset"); thermostatDeviceProfile.setName("thermostat");
thermostatAlarms = assetService.saveAsset(thermostatAlarms); thermostatDeviceProfile.setType(DeviceProfileType.DEFAULT);
thermostatDeviceProfile.setTransportType(DeviceTransportType.DEFAULT);
thermostatDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED);
thermostatDeviceProfile.setDescription("Thermostat device profile");
thermostatDeviceProfile.setDefaultRuleChainId(ruleChainService.findTenantRuleChains(
demoTenant.getId(), new PageLink(1, 0, "Thermostat Alarms")).getData().get(0).getId());
DeviceProfile thermostatDeviceProfile = this.deviceProfileService.findOrCreateDeviceProfile(demoTenant.getId(), "thermostat"); DeviceProfileData deviceProfileData = new DeviceProfileData();
DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
DefaultDeviceProfileTransportConfiguration transportConfiguration = new DefaultDeviceProfileTransportConfiguration();
DisabledDeviceProfileProvisionConfiguration provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(null);
deviceProfileData.setConfiguration(configuration);
deviceProfileData.setTransportConfiguration(transportConfiguration);
deviceProfileData.setProvisionConfiguration(provisionConfiguration);
thermostatDeviceProfile.setProfileData(deviceProfileData);
DeviceId t1Id = createDevice(demoTenant.getId(), null, thermostatDeviceProfile.getId(), "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); DeviceProfileAlarm highTemperature = new DeviceProfileAlarm();
DeviceId t2Id = createDevice(demoTenant.getId(), null, thermostatDeviceProfile.getId(), "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); highTemperature.setId("highTemperatureAlarmID");
highTemperature.setAlarmType("High Temperature");
AlarmRule temperatureRule = new AlarmRule();
AlarmCondition temperatureCondition = new AlarmCondition();
temperatureCondition.setSpec(new SimpleAlarmConditionSpec());
relationService.saveRelation(thermostatAlarms.getTenantId(), new EntityRelation(thermostatAlarms.getId(), t1Id, "ToAlarmPropagationAsset")); KeyFilter alarmTemperatureAttributeFilter = new KeyFilter();
relationService.saveRelation(thermostatAlarms.getTenantId(), new EntityRelation(thermostatAlarms.getId(), t2Id, "ToAlarmPropagationAsset")); alarmTemperatureAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "alarmTemperature"));
alarmTemperatureAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);
BooleanFilterPredicate alarmTemperatureAttributePredicate = new BooleanFilterPredicate();
alarmTemperatureAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
alarmTemperatureAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));
alarmTemperatureAttributeFilter.setPredicate(alarmTemperatureAttributePredicate);
KeyFilter temperatureTimeseriesFilter = new KeyFilter();
temperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"));
temperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate temperatureTimeseriesFilterPredicate = new NumericFilterPredicate();
temperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
FilterPredicateValue<Double> temperatureTimeseriesPredicateValue =
new FilterPredicateValue<>(25.0, null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdTemperature"));
temperatureTimeseriesFilterPredicate.setValue(temperatureTimeseriesPredicateValue);
temperatureTimeseriesFilter.setPredicate(temperatureTimeseriesFilterPredicate);
temperatureCondition.setCondition(Arrays.asList(alarmTemperatureAttributeFilter, temperatureTimeseriesFilter));
temperatureRule.setAlarmDetails("Current temperature = ${temperature}");
temperatureRule.setCondition(temperatureCondition);
highTemperature.setCreateRules(new LinkedHashMap<>(Collections.singletonMap(AlarmSeverity.MAJOR, temperatureRule)));
AlarmRule clearTemperatureRule = new AlarmRule();
AlarmCondition clearTemperatureCondition = new AlarmCondition();
clearTemperatureCondition.setSpec(new SimpleAlarmConditionSpec());
KeyFilter clearTemperatureTimeseriesFilter = new KeyFilter();
clearTemperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"));
clearTemperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate clearTemperatureTimeseriesFilterPredicate = new NumericFilterPredicate();
clearTemperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL);
FilterPredicateValue<Double> clearTemperatureTimeseriesPredicateValue =
new FilterPredicateValue<>(25.0, null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdTemperature"));
clearTemperatureTimeseriesFilterPredicate.setValue(clearTemperatureTimeseriesPredicateValue);
clearTemperatureTimeseriesFilter.setPredicate(clearTemperatureTimeseriesFilterPredicate);
clearTemperatureCondition.setCondition(Collections.singletonList(clearTemperatureTimeseriesFilter));
clearTemperatureRule.setCondition(clearTemperatureCondition);
clearTemperatureRule.setAlarmDetails("Current temperature = ${temperature}");
highTemperature.setClearRule(clearTemperatureRule);
DeviceProfileAlarm lowHumidity = new DeviceProfileAlarm();
lowHumidity.setId("lowHumidityAlarmID");
lowHumidity.setAlarmType("Low Humidity");
AlarmRule humidityRule = new AlarmRule();
AlarmCondition humidityCondition = new AlarmCondition();
humidityCondition.setSpec(new SimpleAlarmConditionSpec());
KeyFilter alarmHumidityAttributeFilter = new KeyFilter();
alarmHumidityAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "alarmHumidity"));
alarmHumidityAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);
BooleanFilterPredicate alarmHumidityAttributePredicate = new BooleanFilterPredicate();
alarmHumidityAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
alarmHumidityAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));
alarmHumidityAttributeFilter.setPredicate(alarmHumidityAttributePredicate);
KeyFilter humidityTimeseriesFilter = new KeyFilter();
humidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity"));
humidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate humidityTimeseriesFilterPredicate = new NumericFilterPredicate();
humidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS);
FilterPredicateValue<Double> humidityTimeseriesPredicateValue =
new FilterPredicateValue<>(60.0, null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdHumidity"));
humidityTimeseriesFilterPredicate.setValue(humidityTimeseriesPredicateValue);
humidityTimeseriesFilter.setPredicate(humidityTimeseriesFilterPredicate);
humidityCondition.setCondition(Arrays.asList(alarmHumidityAttributeFilter, humidityTimeseriesFilter));
humidityRule.setCondition(humidityCondition);
humidityRule.setAlarmDetails("Current humidity = ${humidity}");
lowHumidity.setCreateRules(new LinkedHashMap<>(Collections.singletonMap(AlarmSeverity.MINOR, humidityRule)));
AlarmRule clearHumidityRule = new AlarmRule();
AlarmCondition clearHumidityCondition = new AlarmCondition();
clearHumidityCondition.setSpec(new SimpleAlarmConditionSpec());
KeyFilter clearHumidityTimeseriesFilter = new KeyFilter();
clearHumidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity"));
clearHumidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate clearHumidityTimeseriesFilterPredicate = new NumericFilterPredicate();
clearHumidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL);
FilterPredicateValue<Double> clearHumidityTimeseriesPredicateValue =
new FilterPredicateValue<>(60.0, null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdHumidity"));
clearHumidityTimeseriesFilterPredicate.setValue(clearHumidityTimeseriesPredicateValue);
clearHumidityTimeseriesFilter.setPredicate(clearHumidityTimeseriesFilterPredicate);
clearHumidityCondition.setCondition(Collections.singletonList(clearHumidityTimeseriesFilter));
clearHumidityRule.setCondition(clearHumidityCondition);
clearHumidityRule.setAlarmDetails("Current humidity = ${humidity}");
lowHumidity.setClearRule(clearHumidityRule);
deviceProfileData.setAlarms(Arrays.asList(highTemperature, lowHumidity));
DeviceProfile savedThermostatDeviceProfile = deviceProfileService.saveDeviceProfile(thermostatDeviceProfile);
DeviceId t1Id = createDevice(demoTenant.getId(), null, savedThermostatDeviceProfile.getId(), "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
DeviceId t2Id = createDevice(demoTenant.getId(), null, savedThermostatDeviceProfile.getId(), "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
attributesService.save(demoTenant.getId(), t1Id, DataConstants.SERVER_SCOPE, attributesService.save(demoTenant.getId(), t1Id, DataConstants.SERVER_SCOPE,
Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.3948)), Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.3948)),

View File

@ -0,0 +1,27 @@
/**
* 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.transport.mqtt.util;
import lombok.Data;
@Data
public class AlwaysTrueTopicFilter implements MqttTopicFilter {
@Override
public boolean filter(String topic) {
return true;
}
}

View File

@ -33,7 +33,9 @@ public class MqttTopicFilterFactory {
throw new IllegalArgumentException("Topic filter can't be empty!"); throw new IllegalArgumentException("Topic filter can't be empty!");
} }
return filters.computeIfAbsent(topicFilter, filter -> { return filters.computeIfAbsent(topicFilter, filter -> {
if (filter.contains("+") || filter.contains("#")) { if (filter.equals("#")) {
return new AlwaysTrueTopicFilter();
} else if (filter.contains("+") || filter.contains("#")) {
String regex = filter String regex = filter
.replace("\\", "\\\\") .replace("\\", "\\\\")
.replace("+", "[^/]+") .replace("+", "[^/]+")

View File

@ -20,7 +20,6 @@ import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import javax.script.ScriptException; import javax.script.ScriptException;
import java.util.regex.Pattern;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -31,6 +30,9 @@ public class MqttTopicFilterFactoryTest {
private static String TEST_STR_1 = "Sensor/Temperature/House/48"; private static String TEST_STR_1 = "Sensor/Temperature/House/48";
private static String TEST_STR_2 = "Sensor/Temperature"; private static String TEST_STR_2 = "Sensor/Temperature";
private static String TEST_STR_3 = "Sensor/Temperature2/House/48"; private static String TEST_STR_3 = "Sensor/Temperature2/House/48";
private static String TEST_STR_4 = "/Sensor/Temperature2/House/48";
private static String TEST_STR_5 = "Sensor/ Temperature";
private static String TEST_STR_6 = "/";
@Test @Test
public void metadataCanBeUpdated() throws ScriptException { public void metadataCanBeUpdated() throws ScriptException {
@ -51,6 +53,17 @@ public class MqttTopicFilterFactoryTest {
assertTrue(filter.filter(TEST_STR_1)); assertTrue(filter.filter(TEST_STR_1));
assertTrue(filter.filter(TEST_STR_2)); assertTrue(filter.filter(TEST_STR_2));
assertFalse(filter.filter(TEST_STR_3)); assertFalse(filter.filter(TEST_STR_3));
filter = MqttTopicFilterFactory.toFilter("#");
assertTrue(filter.filter(TEST_STR_1));
assertTrue(filter.filter(TEST_STR_2));
assertTrue(filter.filter(TEST_STR_3));
assertTrue(filter.filter(TEST_STR_4));
assertTrue(filter.filter(TEST_STR_5));
assertTrue(filter.filter(TEST_STR_6));
filter = MqttTopicFilterFactory.toFilter("Sensor/Temperature#");
assertFalse(filter.filter(TEST_STR_2));
} }
} }

View File

@ -1,12 +1,12 @@
/** /**
* Copyright © 2016-2020 The Thingsboard Authors * Copyright © 2016-2020 The Thingsboard Authors
* * <p>
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* * <p>
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* * <p>
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -33,6 +33,7 @@ import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceProfileInfo;
@ -42,6 +43,7 @@ import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
@ -60,8 +62,10 @@ import org.thingsboard.server.dao.tenant.TenantDao;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.Set;
import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE; import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE;
import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateId;
@ -347,6 +351,22 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
} }
} }
} }
List<DeviceProfileAlarm> profileAlarms = deviceProfile.getProfileData().getAlarms();
if (!CollectionUtils.isEmpty(profileAlarms)) {
Set<String> alarmTypes = new HashSet<>();
for (DeviceProfileAlarm alarm : profileAlarms) {
String alarmType = alarm.getAlarmType();
if (StringUtils.isEmpty(alarmType)) {
throw new DataValidationException("Alarm rule type should be specified!");
}
if (!alarmTypes.add(alarmType)) {
throw new DataValidationException(String.format("Can't create device profile with the same alarm rule types: \"%s\"!", alarmType));
}
}
}
} }
@Override @Override
@ -393,6 +413,7 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
" for " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!"); " for " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!");
} }
} }
private void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage) { private void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage) {
if (!isEmptySettings) { if (!isEmptySettings) {
throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + invalidSettingsMessage); throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + invalidSettingsMessage);

View File

@ -41,6 +41,9 @@
<mat-error *ngIf="alarmFormGroup.get('alarmType').hasError('required')"> <mat-error *ngIf="alarmFormGroup.get('alarmType').hasError('required')">
{{ 'device-profile.alarm-type-required' | translate }} {{ 'device-profile.alarm-type-required' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="alarmFormGroup.get('alarmType').hasError('unique')">
{{ 'device-profile.alarm-type-unique' | translate }}
</mat-error>
<mat-hint *ngIf="!disabled" <mat-hint *ngIf="!disabled"
innerHTML="{{ 'device-profile.alarm-type-pattern-hint' | translate }}"></mat-hint> innerHTML="{{ 'device-profile.alarm-type-pattern-hint' | translate }}"></mat-hint>
</mat-form-field> </mat-form-field>

View File

@ -133,6 +133,19 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
} }
public validate(c: FormControl) { public validate(c: FormControl) {
if (c.parent) {
const alarmType = c.value.alarmType;
const profileAlarmsType = [];
c.parent.getRawValue().forEach((alarm: DeviceProfileAlarm) => {
profileAlarmsType.push(alarm.alarmType);
}
);
if (profileAlarmsType.filter(profileAlarmType => profileAlarmType === alarmType).length > 1) {
this.alarmFormGroup.get('alarmType').setErrors({
unique: true
});
}
}
return (this.alarmFormGroup.valid) ? null : { return (this.alarmFormGroup.valid) ? null : {
alarm: { alarm: {
valid: false, valid: false,

View File

@ -38,7 +38,7 @@ import {
} from '@shared/models/device.models'; } from '@shared/models/device.models';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { RuleChainId } from '@shared/models/id/rule-chain-id'; import { RuleChainId } from '@shared/models/id/rule-chain-id';
import {ServiceType} from "@shared/models/queue.models"; import { ServiceType } from '@shared/models/queue.models';
@Component({ @Component({
selector: 'tb-device-profile', selector: 'tb-device-profile',
@ -176,10 +176,10 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
transportConfiguration: entity.profileData?.transportConfiguration, transportConfiguration: entity.profileData?.transportConfiguration,
alarms: entity.profileData?.alarms, alarms: entity.profileData?.alarms,
provisionConfiguration: deviceProvisionConfiguration provisionConfiguration: deviceProvisionConfiguration
}}); }}, {emitEvent: false});
this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}); this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}, {emitEvent: false});
this.entityForm.patchValue({defaultQueueName: entity.defaultQueueName}); this.entityForm.patchValue({defaultQueueName: entity.defaultQueueName}, {emitEvent: false});
this.entityForm.patchValue({description: entity.description}); this.entityForm.patchValue({description: entity.description}, {emitEvent: false});
} }
prepareFormValue(formValue: any): any { prepareFormValue(formValue: any): any {

View File

@ -913,6 +913,7 @@
"edit-alarm-rule": "Edit alarm rule", "edit-alarm-rule": "Edit alarm rule",
"alarm-type": "Alarm type", "alarm-type": "Alarm type",
"alarm-type-required": "Alarm type is required.", "alarm-type-required": "Alarm type is required.",
"alarm-type-unique": "Alarm type must be unique within the device profile alarm rules.",
"alarm-type-pattern-hint": "Alarm type pattern, use <code>${metaKeyName}</code> to substitute variables from metadata", "alarm-type-pattern-hint": "Alarm type pattern, use <code>${metaKeyName}</code> to substitute variables from metadata",
"create-alarm-pattern": "Create <b>{{alarmType}}</b> alarm", "create-alarm-pattern": "Create <b>{{alarmType}}</b> alarm",
"create-alarm-rules": "Create alarm rules", "create-alarm-rules": "Create alarm rules",