Commit 0ea4723e2e3f97481d7c8df540ebafe72c22c62b

Authored by Andrew Shvayka
Committed by GitHub
2 parents 706fd01d be094fc6

Merge pull request #3572 from ShvaykaD/feature/device-profile-telemetry-proto-schema

[3.2] Custom proto schemas for MQTT telemetry & attributes upload.
Showing 19 changed files with 467 additions and 229 deletions
@@ -219,8 +219,8 @@ @@ -219,8 +219,8 @@
219 "defaultPageSize": 10, 219 "defaultPageSize": 10,
220 "defaultSortOrder": "-createdTime", 220 "defaultSortOrder": "-createdTime",
221 "enableSelectColumnDisplay": false, 221 "enableSelectColumnDisplay": false,
222 - "enableStatusFilter": true,  
223 - "alarmsTitle": "Alarms" 222 + "alarmsTitle": "Alarms",
  223 + "enableFilter": true
224 }, 224 },
225 "title": "New Alarms table", 225 "title": "New Alarms table",
226 "dropShadow": true, 226 "dropShadow": true,
@@ -234,6 +234,9 @@ @@ -234,6 +234,9 @@
234 "showLegend": false, 234 "showLegend": false,
235 "alarmSource": { 235 "alarmSource": {
236 "type": "entity", 236 "type": "entity",
  237 + "name": "alarms",
  238 + "entityAliasId": "68a058e1-fdda-8482-715b-3ae4a488568e",
  239 + "filterId": null,
237 "dataKeys": [ 240 "dataKeys": [
238 { 241 {
239 "name": "createdTime", 242 "name": "createdTime",
@@ -275,9 +278,7 @@ @@ -275,9 +278,7 @@
275 "settings": {}, 278 "settings": {},
276 "_hash": 0.7977920750136249 279 "_hash": 0.7977920750136249
277 } 280 }
278 - ],  
279 - "entityAliasId": "ce27a9d0-93bf-b7a4-054d-d0369a8cf813",  
280 - "name": "alarms" 281 + ]
281 }, 282 },
282 "alarmSearchStatus": "ANY", 283 "alarmSearchStatus": "ANY",
283 "alarmsPollingInterval": 5, 284 "alarmsPollingInterval": 5,
@@ -1031,7 +1032,8 @@ @@ -1031,7 +1032,8 @@
1031 "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;", 1032 "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;",
1032 "useLabelFunction": true, 1033 "useLabelFunction": true,
1033 "provider": "openstreet-map", 1034 "provider": "openstreet-map",
1034 - "draggableMarker": true 1035 + "draggableMarker": true,
  1036 + "editablePolygon": true
1035 }, 1037 },
1036 "title": "New Markers Placement - OpenStreetMap", 1038 "title": "New Markers Placement - OpenStreetMap",
1037 "dropShadow": true, 1039 "dropShadow": true,
@@ -1062,61 +1064,6 @@ @@ -1062,61 +1064,6 @@
1062 "displayTimewindow": true 1064 "displayTimewindow": true
1063 }, 1065 },
1064 "id": "0a430429-9078-9ae6-2b67-e4a15a2bf8bf" 1066 "id": "0a430429-9078-9ae6-2b67-e4a15a2bf8bf"
1065 - },  
1066 - "f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3": {  
1067 - "isSystemType": true,  
1068 - "bundleAlias": "input_widgets",  
1069 - "typeAlias": "update_double_timeseries",  
1070 - "type": "latest",  
1071 - "title": "New widget",  
1072 - "sizeX": 7.5,  
1073 - "sizeY": 3,  
1074 - "config": {  
1075 - "datasources": [  
1076 - {  
1077 - "type": "entity",  
1078 - "name": null,  
1079 - "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547",  
1080 - "dataKeys": [  
1081 - {  
1082 - "name": "temperature",  
1083 - "type": "timeseries",  
1084 - "label": "temperature",  
1085 - "color": "#2196f3",  
1086 - "settings": {},  
1087 - "_hash": 0.4164505192982848  
1088 - }  
1089 - ]  
1090 - }  
1091 - ],  
1092 - "timewindow": {  
1093 - "realtime": {  
1094 - "timewindowMs": 60000  
1095 - }  
1096 - },  
1097 - "showTitle": true,  
1098 - "backgroundColor": "#fff",  
1099 - "color": "rgba(0, 0, 0, 0.87)",  
1100 - "padding": "8px",  
1101 - "settings": {  
1102 - "showResultMessage": true,  
1103 - "showLabel": true  
1104 - },  
1105 - "title": "New Update double timeseries",  
1106 - "dropShadow": true,  
1107 - "enableFullscreen": false,  
1108 - "widgetStyle": {},  
1109 - "titleStyle": {  
1110 - "fontSize": "16px",  
1111 - "fontWeight": 400  
1112 - },  
1113 - "useDashboardTimewindow": true,  
1114 - "showLegend": false,  
1115 - "actions": {}  
1116 - },  
1117 - "row": 0,  
1118 - "col": 0,  
1119 - "id": "f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3"  
1120 } 1067 }
1121 }, 1068 },
1122 "states": { 1069 "states": {
@@ -1215,12 +1162,6 @@ @@ -1215,12 +1162,6 @@
1215 "sizeY": 6, 1162 "sizeY": 6,
1216 "row": 6, 1163 "row": 6,
1217 "col": 0 1164 "col": 0
1218 - },  
1219 - "f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3": {  
1220 - "sizeX": 7.5,  
1221 - "sizeY": 3,  
1222 - "row": 12,  
1223 - "col": 0  
1224 } 1165 }
1225 }, 1166 },
1226 "gridSettings": { 1167 "gridSettings": {
@@ -1257,16 +1198,6 @@ @@ -1257,16 +1198,6 @@
1257 "stateEntityParamName": null, 1198 "stateEntityParamName": null,
1258 "defaultStateEntity": null 1199 "defaultStateEntity": null
1259 } 1200 }
1260 - },  
1261 - "ce27a9d0-93bf-b7a4-054d-d0369a8cf813": {  
1262 - "id": "ce27a9d0-93bf-b7a4-054d-d0369a8cf813",  
1263 - "alias": "Thermostat-alarm",  
1264 - "filter": {  
1265 - "type": "entityName",  
1266 - "resolveMultiple": false,  
1267 - "entityType": "ASSET",  
1268 - "entityNameFilter": "Thermostat Alarms"  
1269 - }  
1270 } 1201 }
1271 }, 1202 },
1272 "timewindow": { 1203 "timewindow": {
@@ -1301,7 +1232,8 @@ @@ -1301,7 +1232,8 @@
1301 "showDashboardTimewindow": true, 1232 "showDashboardTimewindow": true,
1302 "showDashboardExport": true, 1233 "showDashboardExport": true,
1303 "toolbarAlwaysOpen": true 1234 "toolbarAlwaysOpen": true
1304 - } 1235 + },
  1236 + "filters": {}
1305 }, 1237 },
1306 "name": "Thermostats" 1238 "name": "Thermostats"
1307 } 1239 }
1 { 1 {
2 "ruleChain": { 2 "ruleChain": {
3 - "additionalInfo": null, 3 + "additionalInfo": {
  4 + "description": ""
  5 + },
4 "name": "Thermostat Alarms", 6 "name": "Thermostat Alarms",
5 "firstRuleNodeId": null, 7 "firstRuleNodeId": null,
6 "root": false, 8 "root": false,
@@ -8,131 +10,126 @@ @@ -8,131 +10,126 @@
8 "configuration": null 10 "configuration": null
9 }, 11 },
10 "metadata": { 12 "metadata": {
11 - "firstNodeIndex": 5, 13 + "firstNodeIndex": 6,
12 "nodes": [ 14 "nodes": [
13 { 15 {
14 "additionalInfo": { 16 "additionalInfo": {
15 - "layoutX": 929,  
16 - "layoutY": 67 17 + "layoutX": 822,
  18 + "layoutY": 294
17 }, 19 },
18 - "type": "org.thingsboard.rule.engine.action.TbCreateAlarmNode",  
19 - "name": "Create Temp Alarm", 20 + "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
  21 + "name": "Save Timeseries",
20 "debugMode": false, 22 "debugMode": false,
21 "configuration": { 23 "configuration": {
22 - "alarmType": "High Temperature",  
23 - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\ndetails.triggerValue = msg.temperature;\nreturn details;",  
24 - "severity": "MAJOR",  
25 - "propagate": true,  
26 - "useMessageAlarmData": false,  
27 - "relationTypes": [  
28 - "ToAlarmPropagationAsset"  
29 - ] 24 + "defaultTTL": 0
30 } 25 }
31 }, 26 },
32 { 27 {
33 "additionalInfo": { 28 "additionalInfo": {
34 - "layoutX": 930,  
35 - "layoutY": 201 29 + "description": null,
  30 + "layoutX": 824,
  31 + "layoutY": 221
36 }, 32 },
37 - "type": "org.thingsboard.rule.engine.action.TbClearAlarmNode",  
38 - "name": "Clear Temp Alarm", 33 + "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
  34 + "name": "Save Client Attributes",
39 "debugMode": false, 35 "debugMode": false,
40 "configuration": { 36 "configuration": {
41 - "alarmType": "High Temperature",  
42 - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\nreturn details;" 37 + "scope": "SERVER_SCOPE",
  38 + "notifyDevice": null
43 } 39 }
44 }, 40 },
45 { 41 {
46 "additionalInfo": { 42 "additionalInfo": {
47 - "layoutX": 930,  
48 - "layoutY": 131 43 + "layoutX": 494,
  44 + "layoutY": 309
49 }, 45 },
50 - "type": "org.thingsboard.rule.engine.action.TbCreateAlarmNode",  
51 - "name": "Create Humidity Alarm", 46 + "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
  47 + "name": "Message Type Switch",
52 "debugMode": false, 48 "debugMode": false,
53 "configuration": { 49 "configuration": {
54 - "alarmType": "Low Humidity",  
55 - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\ndetails.triggerValue = msg.humidity;\nreturn details;",  
56 - "severity": "MINOR",  
57 - "propagate": true,  
58 - "useMessageAlarmData": false,  
59 - "relationTypes": [  
60 - "ToAlarmPropagationAsset"  
61 - ] 50 + "version": 0
62 } 51 }
63 }, 52 },
64 { 53 {
65 "additionalInfo": { 54 "additionalInfo": {
66 - "layoutX": 929,  
67 - "layoutY": 275 55 + "layoutX": 824,
  56 + "layoutY": 383
68 }, 57 },
69 - "type": "org.thingsboard.rule.engine.action.TbClearAlarmNode",  
70 - "name": "Clear Humidity Alarm", 58 + "type": "org.thingsboard.rule.engine.action.TbLogNode",
  59 + "name": "Log RPC from Device",
71 "debugMode": false, 60 "debugMode": false,
72 "configuration": { 61 "configuration": {
73 - "alarmType": "Low Humidity",  
74 - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\nreturn details;" 62 + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
75 } 63 }
76 }, 64 },
77 { 65 {
78 "additionalInfo": { 66 "additionalInfo": {
79 - "layoutX": 586,  
80 - "layoutY": 148 67 + "layoutX": 823,
  68 + "layoutY": 444
81 }, 69 },
82 - "type": "org.thingsboard.rule.engine.filter.TbJsSwitchNode",  
83 - "name": "Check Alarms", 70 + "type": "org.thingsboard.rule.engine.action.TbLogNode",
  71 + "name": "Log Other",
84 "debugMode": false, 72 "debugMode": false,
85 "configuration": { 73 "configuration": {
86 - "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;" 74 + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
87 } 75 }
88 }, 76 },
89 { 77 {
90 "additionalInfo": { 78 "additionalInfo": {
91 - "layoutX": 321,  
92 - "layoutY": 149 79 + "layoutX": 822,
  80 + "layoutY": 507
93 }, 81 },
94 - "type": "org.thingsboard.rule.engine.metadata.TbGetAttributesNode",  
95 - "name": "Fetch Configuration", 82 + "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
  83 + "name": "RPC Call Request",
96 "debugMode": false, 84 "debugMode": false,
97 "configuration": { 85 "configuration": {
98 - "clientAttributeNames": [],  
99 - "sharedAttributeNames": [],  
100 - "serverAttributeNames": [  
101 - "alarmTemperature",  
102 - "thresholdTemperature",  
103 - "alarmHumidity",  
104 - "thresholdHumidity"  
105 - ],  
106 - "latestTsKeyNames": [],  
107 - "tellFailureIfAbsent": false,  
108 - "getLatestValueWithTs": false 86 + "timeoutInSeconds": 60
  87 + }
  88 + },
  89 + {
  90 + "additionalInfo": {
  91 + "description": "",
  92 + "layoutX": 209,
  93 + "layoutY": 307
  94 + },
  95 + "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
  96 + "name": "Device Profile Node",
  97 + "debugMode": false,
  98 + "configuration": {
  99 + "persistAlarmRulesState": false,
  100 + "fetchAlarmRulesStateOnStart": false
109 } 101 }
110 } 102 }
111 ], 103 ],
112 "connections": [ 104 "connections": [
113 { 105 {
114 - "fromIndex": 4,  
115 - "toIndex": 0,  
116 - "type": "NewTempAlarm" 106 + "fromIndex": 2,
  107 + "toIndex": 4,
  108 + "type": "Other"
117 }, 109 },
118 { 110 {
119 - "fromIndex": 4, 111 + "fromIndex": 2,
120 "toIndex": 1, 112 "toIndex": 1,
121 - "type": "ClearTempAlarm" 113 + "type": "Post attributes"
122 }, 114 },
123 { 115 {
124 - "fromIndex": 4,  
125 - "toIndex": 2,  
126 - "type": "NewHumidityAlarm" 116 + "fromIndex": 2,
  117 + "toIndex": 0,
  118 + "type": "Post telemetry"
127 }, 119 },
128 { 120 {
129 - "fromIndex": 4, 121 + "fromIndex": 2,
130 "toIndex": 3, 122 "toIndex": 3,
131 - "type": "ClearHumidityAlarm" 123 + "type": "RPC Request from Device"
132 }, 124 },
133 { 125 {
134 - "fromIndex": 5,  
135 - "toIndex": 4, 126 + "fromIndex": 2,
  127 + "toIndex": 5,
  128 + "type": "RPC Request to Device"
  129 + },
  130 + {
  131 + "fromIndex": 6,
  132 + "toIndex": 2,
136 "type": "Success" 133 "type": "Success"
137 } 134 }
138 ], 135 ],
@@ -28,12 +28,21 @@ import org.thingsboard.server.common.data.Customer; @@ -28,12 +28,21 @@ import org.thingsboard.server.common.data.Customer;
28 import org.thingsboard.server.common.data.DataConstants; 28 import org.thingsboard.server.common.data.DataConstants;
29 import org.thingsboard.server.common.data.Device; 29 import org.thingsboard.server.common.data.Device;
30 import org.thingsboard.server.common.data.DeviceProfile; 30 import org.thingsboard.server.common.data.DeviceProfile;
  31 +import org.thingsboard.server.common.data.DeviceProfileProvisionType;
  32 +import org.thingsboard.server.common.data.DeviceProfileType;
  33 +import org.thingsboard.server.common.data.DeviceTransportType;
31 import org.thingsboard.server.common.data.Tenant; 34 import org.thingsboard.server.common.data.Tenant;
32 import org.thingsboard.server.common.data.TenantProfile; 35 import org.thingsboard.server.common.data.TenantProfile;
33 -import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;  
34 -import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;  
35 import org.thingsboard.server.common.data.User; 36 import org.thingsboard.server.common.data.User;
36 -import org.thingsboard.server.common.data.asset.Asset; 37 +import org.thingsboard.server.common.data.alarm.AlarmSeverity;
  38 +import org.thingsboard.server.common.data.device.profile.AlarmCondition;
  39 +import org.thingsboard.server.common.data.device.profile.AlarmRule;
  40 +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
  41 +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
  42 +import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
  43 +import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
  44 +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
  45 +import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec;
37 import org.thingsboard.server.common.data.id.CustomerId; 46 import org.thingsboard.server.common.data.id.CustomerId;
38 import org.thingsboard.server.common.data.id.DeviceId; 47 import org.thingsboard.server.common.data.id.DeviceId;
39 import org.thingsboard.server.common.data.id.DeviceProfileId; 48 import org.thingsboard.server.common.data.id.DeviceProfileId;
@@ -42,19 +51,29 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; @@ -42,19 +51,29 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
42 import org.thingsboard.server.common.data.kv.BooleanDataEntry; 51 import org.thingsboard.server.common.data.kv.BooleanDataEntry;
43 import org.thingsboard.server.common.data.kv.DoubleDataEntry; 52 import org.thingsboard.server.common.data.kv.DoubleDataEntry;
44 import org.thingsboard.server.common.data.kv.LongDataEntry; 53 import org.thingsboard.server.common.data.kv.LongDataEntry;
45 -import org.thingsboard.server.common.data.relation.EntityRelation; 54 +import org.thingsboard.server.common.data.page.PageLink;
  55 +import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
  56 +import org.thingsboard.server.common.data.query.DynamicValue;
  57 +import org.thingsboard.server.common.data.query.DynamicValueSourceType;
  58 +import org.thingsboard.server.common.data.query.EntityKey;
  59 +import org.thingsboard.server.common.data.query.EntityKeyType;
  60 +import org.thingsboard.server.common.data.query.EntityKeyValueType;
  61 +import org.thingsboard.server.common.data.query.FilterPredicateValue;
  62 +import org.thingsboard.server.common.data.query.KeyFilter;
  63 +import org.thingsboard.server.common.data.query.NumericFilterPredicate;
46 import org.thingsboard.server.common.data.security.Authority; 64 import org.thingsboard.server.common.data.security.Authority;
47 import org.thingsboard.server.common.data.security.DeviceCredentials; 65 import org.thingsboard.server.common.data.security.DeviceCredentials;
48 import org.thingsboard.server.common.data.security.UserCredentials; 66 import org.thingsboard.server.common.data.security.UserCredentials;
  67 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
  68 +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
49 import org.thingsboard.server.common.data.widget.WidgetsBundle; 69 import org.thingsboard.server.common.data.widget.WidgetsBundle;
50 -import org.thingsboard.server.dao.asset.AssetService;  
51 import org.thingsboard.server.dao.attributes.AttributesService; 70 import org.thingsboard.server.dao.attributes.AttributesService;
52 import org.thingsboard.server.dao.customer.CustomerService; 71 import org.thingsboard.server.dao.customer.CustomerService;
53 import org.thingsboard.server.dao.device.DeviceCredentialsService; 72 import org.thingsboard.server.dao.device.DeviceCredentialsService;
54 import org.thingsboard.server.dao.device.DeviceProfileService; 73 import org.thingsboard.server.dao.device.DeviceProfileService;
55 import org.thingsboard.server.dao.device.DeviceService; 74 import org.thingsboard.server.dao.device.DeviceService;
56 import org.thingsboard.server.dao.exception.DataValidationException; 75 import org.thingsboard.server.dao.exception.DataValidationException;
57 -import org.thingsboard.server.dao.relation.RelationService; 76 +import org.thingsboard.server.dao.rule.RuleChainService;
58 import org.thingsboard.server.dao.settings.AdminSettingsService; 77 import org.thingsboard.server.dao.settings.AdminSettingsService;
59 import org.thingsboard.server.dao.tenant.TenantProfileService; 78 import org.thingsboard.server.dao.tenant.TenantProfileService;
60 import org.thingsboard.server.dao.tenant.TenantService; 79 import org.thingsboard.server.dao.tenant.TenantService;
@@ -62,6 +81,8 @@ import org.thingsboard.server.dao.user.UserService; @@ -62,6 +81,8 @@ import org.thingsboard.server.dao.user.UserService;
62 import org.thingsboard.server.dao.widget.WidgetsBundleService; 81 import org.thingsboard.server.dao.widget.WidgetsBundleService;
63 82
64 import java.util.Arrays; 83 import java.util.Arrays;
  84 +import java.util.Collections;
  85 +import java.util.LinkedHashMap;
65 86
66 @Service 87 @Service
67 @Profile("install") 88 @Profile("install")
@@ -97,12 +118,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -97,12 +118,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
97 private CustomerService customerService; 118 private CustomerService customerService;
98 119
99 @Autowired 120 @Autowired
100 - private RelationService relationService;  
101 -  
102 - @Autowired  
103 - private AssetService assetService;  
104 -  
105 - @Autowired  
106 private DeviceService deviceService; 121 private DeviceService deviceService;
107 122
108 @Autowired 123 @Autowired
@@ -114,6 +129,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -114,6 +129,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
114 @Autowired 129 @Autowired
115 private DeviceCredentialsService deviceCredentialsService; 130 private DeviceCredentialsService deviceCredentialsService;
116 131
  132 + @Autowired
  133 + private RuleChainService ruleChainService;
  134 +
117 @Bean 135 @Bean
118 protected BCryptPasswordEncoder passwordEncoder() { 136 protected BCryptPasswordEncoder passwordEncoder() {
119 return new BCryptPasswordEncoder(); 137 return new BCryptPasswordEncoder();
@@ -245,19 +263,133 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -245,19 +263,133 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
245 createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " + 263 createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " +
246 "Raspberry Pi GPIO control sample application"); 264 "Raspberry Pi GPIO control sample application");
247 265
248 - Asset thermostatAlarms = new Asset();  
249 - thermostatAlarms.setTenantId(demoTenant.getId());  
250 - thermostatAlarms.setName("Thermostat Alarms");  
251 - thermostatAlarms.setType("AlarmPropagationAsset");  
252 - thermostatAlarms = assetService.saveAsset(thermostatAlarms);  
253 -  
254 - DeviceProfile thermostatDeviceProfile = this.deviceProfileService.findOrCreateDeviceProfile(demoTenant.getId(), "thermostat");  
255 -  
256 - DeviceId t1Id = createDevice(demoTenant.getId(), null, thermostatDeviceProfile.getId(), "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();  
257 - DeviceId t2Id = createDevice(demoTenant.getId(), null, thermostatDeviceProfile.getId(), "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();  
258 -  
259 - relationService.saveRelation(thermostatAlarms.getTenantId(), new EntityRelation(thermostatAlarms.getId(), t1Id, "ToAlarmPropagationAsset"));  
260 - relationService.saveRelation(thermostatAlarms.getTenantId(), new EntityRelation(thermostatAlarms.getId(), t2Id, "ToAlarmPropagationAsset")); 266 + DeviceProfile thermostatDeviceProfile = new DeviceProfile();
  267 + thermostatDeviceProfile.setTenantId(demoTenant.getId());
  268 + thermostatDeviceProfile.setDefault(false);
  269 + thermostatDeviceProfile.setName("thermostat");
  270 + thermostatDeviceProfile.setType(DeviceProfileType.DEFAULT);
  271 + thermostatDeviceProfile.setTransportType(DeviceTransportType.DEFAULT);
  272 + thermostatDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED);
  273 + thermostatDeviceProfile.setDescription("Thermostat device profile");
  274 + thermostatDeviceProfile.setDefaultRuleChainId(ruleChainService.findTenantRuleChains(
  275 + demoTenant.getId(), new PageLink(1, 0, "Thermostat Alarms")).getData().get(0).getId());
  276 +
  277 + DeviceProfileData deviceProfileData = new DeviceProfileData();
  278 + DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
  279 + DefaultDeviceProfileTransportConfiguration transportConfiguration = new DefaultDeviceProfileTransportConfiguration();
  280 + DisabledDeviceProfileProvisionConfiguration provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(null);
  281 + deviceProfileData.setConfiguration(configuration);
  282 + deviceProfileData.setTransportConfiguration(transportConfiguration);
  283 + deviceProfileData.setProvisionConfiguration(provisionConfiguration);
  284 + thermostatDeviceProfile.setProfileData(deviceProfileData);
  285 +
  286 + DeviceProfileAlarm highTemperature = new DeviceProfileAlarm();
  287 + highTemperature.setId("highTemperatureAlarmID");
  288 + highTemperature.setAlarmType("High Temperature");
  289 + AlarmRule temperatureRule = new AlarmRule();
  290 + AlarmCondition temperatureCondition = new AlarmCondition();
  291 + temperatureCondition.setSpec(new SimpleAlarmConditionSpec());
  292 +
  293 + KeyFilter alarmTemperatureAttributeFilter = new KeyFilter();
  294 + alarmTemperatureAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "alarmTemperature"));
  295 + alarmTemperatureAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);
  296 + BooleanFilterPredicate alarmTemperatureAttributePredicate = new BooleanFilterPredicate();
  297 + alarmTemperatureAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
  298 + alarmTemperatureAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));
  299 + alarmTemperatureAttributeFilter.setPredicate(alarmTemperatureAttributePredicate);
  300 +
  301 + KeyFilter temperatureTimeseriesFilter = new KeyFilter();
  302 + temperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"));
  303 + temperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
  304 + NumericFilterPredicate temperatureTimeseriesFilterPredicate = new NumericFilterPredicate();
  305 + temperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
  306 + FilterPredicateValue<Double> temperatureTimeseriesPredicateValue =
  307 + new FilterPredicateValue<>(25.0, null,
  308 + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdTemperature"));
  309 + temperatureTimeseriesFilterPredicate.setValue(temperatureTimeseriesPredicateValue);
  310 + temperatureTimeseriesFilter.setPredicate(temperatureTimeseriesFilterPredicate);
  311 + temperatureCondition.setCondition(Arrays.asList(alarmTemperatureAttributeFilter, temperatureTimeseriesFilter));
  312 + temperatureRule.setAlarmDetails("Current temperature = ${temperature}");
  313 + temperatureRule.setCondition(temperatureCondition);
  314 + highTemperature.setCreateRules(new LinkedHashMap<>(Collections.singletonMap(AlarmSeverity.MAJOR, temperatureRule)));
  315 +
  316 + AlarmRule clearTemperatureRule = new AlarmRule();
  317 + AlarmCondition clearTemperatureCondition = new AlarmCondition();
  318 + clearTemperatureCondition.setSpec(new SimpleAlarmConditionSpec());
  319 +
  320 + KeyFilter clearTemperatureTimeseriesFilter = new KeyFilter();
  321 + clearTemperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"));
  322 + clearTemperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
  323 + NumericFilterPredicate clearTemperatureTimeseriesFilterPredicate = new NumericFilterPredicate();
  324 + clearTemperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL);
  325 + FilterPredicateValue<Double> clearTemperatureTimeseriesPredicateValue =
  326 + new FilterPredicateValue<>(25.0, null,
  327 + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdTemperature"));
  328 +
  329 + clearTemperatureTimeseriesFilterPredicate.setValue(clearTemperatureTimeseriesPredicateValue);
  330 + clearTemperatureTimeseriesFilter.setPredicate(clearTemperatureTimeseriesFilterPredicate);
  331 + clearTemperatureCondition.setCondition(Collections.singletonList(clearTemperatureTimeseriesFilter));
  332 + clearTemperatureRule.setCondition(clearTemperatureCondition);
  333 + clearTemperatureRule.setAlarmDetails("Current temperature = ${temperature}");
  334 + highTemperature.setClearRule(clearTemperatureRule);
  335 +
  336 + DeviceProfileAlarm lowHumidity = new DeviceProfileAlarm();
  337 + lowHumidity.setId("lowHumidityAlarmID");
  338 + lowHumidity.setAlarmType("Low Humidity");
  339 + AlarmRule humidityRule = new AlarmRule();
  340 + AlarmCondition humidityCondition = new AlarmCondition();
  341 + humidityCondition.setSpec(new SimpleAlarmConditionSpec());
  342 +
  343 + KeyFilter alarmHumidityAttributeFilter = new KeyFilter();
  344 + alarmHumidityAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "alarmHumidity"));
  345 + alarmHumidityAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);
  346 + BooleanFilterPredicate alarmHumidityAttributePredicate = new BooleanFilterPredicate();
  347 + alarmHumidityAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
  348 + alarmHumidityAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));
  349 + alarmHumidityAttributeFilter.setPredicate(alarmHumidityAttributePredicate);
  350 +
  351 + KeyFilter humidityTimeseriesFilter = new KeyFilter();
  352 + humidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity"));
  353 + humidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
  354 + NumericFilterPredicate humidityTimeseriesFilterPredicate = new NumericFilterPredicate();
  355 + humidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS);
  356 + FilterPredicateValue<Double> humidityTimeseriesPredicateValue =
  357 + new FilterPredicateValue<>(60.0, null,
  358 + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdHumidity"));
  359 + humidityTimeseriesFilterPredicate.setValue(humidityTimeseriesPredicateValue);
  360 + humidityTimeseriesFilter.setPredicate(humidityTimeseriesFilterPredicate);
  361 + humidityCondition.setCondition(Arrays.asList(alarmHumidityAttributeFilter, humidityTimeseriesFilter));
  362 +
  363 + humidityRule.setCondition(humidityCondition);
  364 + humidityRule.setAlarmDetails("Current humidity = ${humidity}");
  365 + lowHumidity.setCreateRules(new LinkedHashMap<>(Collections.singletonMap(AlarmSeverity.MINOR, humidityRule)));
  366 +
  367 + AlarmRule clearHumidityRule = new AlarmRule();
  368 + AlarmCondition clearHumidityCondition = new AlarmCondition();
  369 + clearHumidityCondition.setSpec(new SimpleAlarmConditionSpec());
  370 +
  371 + KeyFilter clearHumidityTimeseriesFilter = new KeyFilter();
  372 + clearHumidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity"));
  373 + clearHumidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
  374 + NumericFilterPredicate clearHumidityTimeseriesFilterPredicate = new NumericFilterPredicate();
  375 + clearHumidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL);
  376 + FilterPredicateValue<Double> clearHumidityTimeseriesPredicateValue =
  377 + new FilterPredicateValue<>(60.0, null,
  378 + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdHumidity"));
  379 +
  380 + clearHumidityTimeseriesFilterPredicate.setValue(clearHumidityTimeseriesPredicateValue);
  381 + clearHumidityTimeseriesFilter.setPredicate(clearHumidityTimeseriesFilterPredicate);
  382 + clearHumidityCondition.setCondition(Collections.singletonList(clearHumidityTimeseriesFilter));
  383 + clearHumidityRule.setCondition(clearHumidityCondition);
  384 + clearHumidityRule.setAlarmDetails("Current humidity = ${humidity}");
  385 + lowHumidity.setClearRule(clearHumidityRule);
  386 +
  387 + deviceProfileData.setAlarms(Arrays.asList(highTemperature, lowHumidity));
  388 +
  389 + DeviceProfile savedThermostatDeviceProfile = deviceProfileService.saveDeviceProfile(thermostatDeviceProfile);
  390 +
  391 + DeviceId t1Id = createDevice(demoTenant.getId(), null, savedThermostatDeviceProfile.getId(), "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
  392 + DeviceId t2Id = createDevice(demoTenant.getId(), null, savedThermostatDeviceProfile.getId(), "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
261 393
262 attributesService.save(demoTenant.getId(), t1Id, DataConstants.SERVER_SCOPE, 394 attributesService.save(demoTenant.getId(), t1Id, DataConstants.SERVER_SCOPE,
263 Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.3948)), 395 Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.3948)),
@@ -62,19 +62,28 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest @@ -62,19 +62,28 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest
62 62
63 private static final AtomicInteger atomicInteger = new AtomicInteger(2); 63 private static final AtomicInteger atomicInteger = new AtomicInteger(2);
64 64
65 - protected static final String DEVICE_TELEMETRY_PROTO_SCHEMA = "syntax =\"proto3\";\n" + 65 + public static final String DEVICE_TELEMETRY_PROTO_SCHEMA = "syntax =\"proto3\";\n" +
66 "\n" + 66 "\n" +
67 "package test;\n" + 67 "package test;\n" +
68 - " \n" + 68 + "\n" +
69 "message PostTelemetry {\n" + 69 "message PostTelemetry {\n" +
70 " string key1 = 1;\n" + 70 " string key1 = 1;\n" +
71 " bool key2 = 2;\n" + 71 " bool key2 = 2;\n" +
72 " double key3 = 3;\n" + 72 " double key3 = 3;\n" +
73 " int32 key4 = 4;\n" + 73 " int32 key4 = 4;\n" +
74 - " string key5 = 5;\n" + 74 + " JsonObject key5 = 5;\n" +
  75 + "\n" +
  76 + " message JsonObject {\n" +
  77 + " int32 someNumber = 6;\n" +
  78 + " repeated int32 someArray = 7;\n" +
  79 + " NestedJsonObject someNestedObject = 8;\n" +
  80 + " message NestedJsonObject {\n" +
  81 + " string key = 9;\n" +
  82 + " }\n" +
  83 + " }\n" +
75 "}"; 84 "}";
76 85
77 - protected static final String DEVICE_ATTRIBUTES_PROTO_SCHEMA = "syntax =\"proto3\";\n" + 86 + public static final String DEVICE_ATTRIBUTES_PROTO_SCHEMA = "syntax =\"proto3\";\n" +
78 "\n" + 87 "\n" +
79 "package test;\n" + 88 "package test;\n" +
80 "\n" + 89 "\n" +
@@ -83,7 +92,16 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest @@ -83,7 +92,16 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest
83 " bool key2 = 2;\n" + 92 " bool key2 = 2;\n" +
84 " double key3 = 3;\n" + 93 " double key3 = 3;\n" +
85 " int32 key4 = 4;\n" + 94 " int32 key4 = 4;\n" +
86 - " string key5 = 5;\n" + 95 + " JsonObject key5 = 5;\n" +
  96 + "\n" +
  97 + " message JsonObject {\n" +
  98 + " int32 someNumber = 6;\n" +
  99 + " repeated int32 someArray = 7;\n" +
  100 + " NestedJsonObject someNestedObject = 8;\n" +
  101 + " message NestedJsonObject {\n" +
  102 + " string key = 9;\n" +
  103 + " }\n" +
  104 + " }\n" +
87 "}"; 105 "}";
88 106
89 protected Tenant savedTenant; 107 protected Tenant savedTenant;
@@ -26,13 +26,13 @@ import java.util.Arrays; @@ -26,13 +26,13 @@ import java.util.Arrays;
26 26
27 @RunWith(ClasspathSuite.class) 27 @RunWith(ClasspathSuite.class)
28 @ClasspathSuite.ClassnameFilters({ 28 @ClasspathSuite.ClassnameFilters({
29 -// "org.thingsboard.server.mqtt.rpc.sql.*Test",  
30 -// "org.thingsboard.server.mqtt.telemetry.timeseries.sql.*Test",  
31 -// "org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test",  
32 -// "org.thingsboard.server.mqtt.attributes.updates.sql.*Test", 29 + "org.thingsboard.server.mqtt.rpc.sql.*Test",
  30 + "org.thingsboard.server.mqtt.telemetry.timeseries.sql.*Test",
  31 + "org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test",
  32 + "org.thingsboard.server.mqtt.attributes.updates.sql.*Test",
33 "org.thingsboard.server.mqtt.attributes.request.sql.*Test", 33 "org.thingsboard.server.mqtt.attributes.request.sql.*Test",
34 -// "org.thingsboard.server.mqtt.claim.sql.*Test",  
35 -// "org.thingsboard.server.mqtt.provision.sql.*Test" 34 + "org.thingsboard.server.mqtt.claim.sql.*Test",
  35 + "org.thingsboard.server.mqtt.provision.sql.*Test"
36 }) 36 })
37 public class MqttSqlTestSuite { 37 public class MqttSqlTestSuite {
38 38
@@ -122,7 +122,7 @@ public abstract class AbstractMqttAttributesRequestIntegrationTest extends Abstr @@ -122,7 +122,7 @@ public abstract class AbstractMqttAttributesRequestIntegrationTest extends Abstr
122 client.publish(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX + "1", mqttMessage); 122 client.publish(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX + "1", mqttMessage);
123 latch.await(3, TimeUnit.SECONDS); 123 latch.await(3, TimeUnit.SECONDS);
124 assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); 124 assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
125 - String expectedRequestPayload = "{\"client\":{\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}},\"attribute4\":73,\"attribute1\":\"value1\",\"attribute3\":42.0,\"attribute2\":true},\"shared\":{\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}},\"attribute4\":73,\"attribute1\":\"value1\",\"attribute3\":42.0,\"attribute2\":true}}"; 125 + String expectedRequestPayload = "{\"client\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}},\"shared\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
126 assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8))); 126 assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
127 } 127 }
128 128
@@ -62,7 +62,16 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends @@ -62,7 +62,16 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends
62 " bool attribute2 = 2;\n" + 62 " bool attribute2 = 2;\n" +
63 " double attribute3 = 3;\n" + 63 " double attribute3 = 3;\n" +
64 " int32 attribute4 = 4;\n" + 64 " int32 attribute4 = 4;\n" +
65 - " string attribute5 = 5;\n" + 65 + " JsonObject attribute5 = 5;\n" +
  66 + "\n" +
  67 + " message JsonObject {\n" +
  68 + " int32 someNumber = 6;\n" +
  69 + " repeated int32 someArray = 7;\n" +
  70 + " NestedJsonObject someNestedObject = 8;\n" +
  71 + " message NestedJsonObject {\n" +
  72 + " string key = 9;\n" +
  73 + " }\n" +
  74 + " }\n" +
66 "}"; 75 "}";
67 76
68 @After 77 @After
@@ -93,6 +102,23 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends @@ -93,6 +102,23 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends
93 ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; 102 ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration;
94 ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(ATTRIBUTES_SCHEMA_STR); 103 ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(ATTRIBUTES_SCHEMA_STR);
95 DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); 104 DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA);
  105 +
  106 + DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject");
  107 + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType();
  108 + assertNotNull(nestedJsonObjectBuilderDescriptor);
  109 + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build();
  110 +
  111 + DynamicMessage.Builder jsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject");
  112 + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType();
  113 + assertNotNull(jsonObjectBuilderDescriptor);
  114 + DynamicMessage jsonObject = jsonObjectBuilder
  115 + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42)
  116 + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1)
  117 + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2)
  118 + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3)
  119 + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject)
  120 + .build();
  121 +
96 DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); 122 DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes");
97 Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); 123 Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType();
98 assertNotNull(postAttributesMsgDescriptor); 124 assertNotNull(postAttributesMsgDescriptor);
@@ -101,7 +127,7 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends @@ -101,7 +127,7 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends
101 .setField(postAttributesMsgDescriptor.findFieldByName("attribute2"), true) 127 .setField(postAttributesMsgDescriptor.findFieldByName("attribute2"), true)
102 .setField(postAttributesMsgDescriptor.findFieldByName("attribute3"), 42.0) 128 .setField(postAttributesMsgDescriptor.findFieldByName("attribute3"), 42.0)
103 .setField(postAttributesMsgDescriptor.findFieldByName("attribute4"), 73) 129 .setField(postAttributesMsgDescriptor.findFieldByName("attribute4"), 73)
104 - .setField(postAttributesMsgDescriptor.findFieldByName("attribute5"), "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}") 130 + .setField(postAttributesMsgDescriptor.findFieldByName("attribute5"), jsonObject)
105 .build(); 131 .build();
106 byte[] payload = postAttributesMsg.toByteArray(); 132 byte[] payload = postAttributesMsg.toByteArray();
107 client.publish(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, new MqttMessage(payload)); 133 client.publish(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, new MqttMessage(payload));
@@ -163,16 +163,10 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt @@ -163,16 +163,10 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
163 break; 163 break;
164 case "key5": 164 case "key5":
165 assertNotNull(value); 165 assertNotNull(value);
166 - LinkedHashMap valueMap;  
167 - if (value instanceof String) {  
168 - valueMap = mapper.readValue((String) value, LinkedHashMap.class);  
169 - } else {  
170 - valueMap = (LinkedHashMap) value;  
171 - }  
172 - assertEquals(3, valueMap.size());  
173 - assertEquals(42, valueMap.get("someNumber"));  
174 - assertEquals(Arrays.asList(1, 2, 3), valueMap.get("someArray"));  
175 - LinkedHashMap<String, String> someNestedObject = (LinkedHashMap) valueMap.get("someNestedObject"); 166 + assertEquals(3, ((LinkedHashMap) value).size());
  167 + assertEquals(42, ((LinkedHashMap) value).get("someNumber"));
  168 + assertEquals(Arrays.asList(1, 2, 3), ((LinkedHashMap) value).get("someArray"));
  169 + LinkedHashMap<String, String> someNestedObject = (LinkedHashMap) ((LinkedHashMap) value).get("someNestedObject");
176 assertEquals("value", someNestedObject.get("key")); 170 assertEquals("value", someNestedObject.get("key"));
177 break; 171 break;
178 } 172 }
@@ -58,6 +58,23 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac @@ -58,6 +58,23 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac
58 ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; 58 ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration;
59 ProtoFileElement transportProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_ATTRIBUTES_PROTO_SCHEMA); 59 ProtoFileElement transportProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_ATTRIBUTES_PROTO_SCHEMA);
60 DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchemaFile, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); 60 DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchemaFile, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA);
  61 +
  62 + DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject");
  63 + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType();
  64 + assertNotNull(nestedJsonObjectBuilderDescriptor);
  65 + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build();
  66 +
  67 + DynamicMessage.Builder jsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject");
  68 + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType();
  69 + assertNotNull(jsonObjectBuilderDescriptor);
  70 + DynamicMessage jsonObject = jsonObjectBuilder
  71 + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42)
  72 + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1)
  73 + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2)
  74 + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3)
  75 + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject)
  76 + .build();
  77 +
61 DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); 78 DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes");
62 Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); 79 Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType();
63 assertNotNull(postAttributesMsgDescriptor); 80 assertNotNull(postAttributesMsgDescriptor);
@@ -66,7 +83,7 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac @@ -66,7 +83,7 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac
66 .setField(postAttributesMsgDescriptor.findFieldByName("key2"), true) 83 .setField(postAttributesMsgDescriptor.findFieldByName("key2"), true)
67 .setField(postAttributesMsgDescriptor.findFieldByName("key3"), 3.0) 84 .setField(postAttributesMsgDescriptor.findFieldByName("key3"), 3.0)
68 .setField(postAttributesMsgDescriptor.findFieldByName("key4"), 4) 85 .setField(postAttributesMsgDescriptor.findFieldByName("key4"), 4)
69 - .setField(postAttributesMsgDescriptor.findFieldByName("key5"), "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}") 86 + .setField(postAttributesMsgDescriptor.findFieldByName("key5"), jsonObject)
70 .build(); 87 .build();
71 processAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, expectedKeys, postAttributesMsg.toByteArray()); 88 processAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, expectedKeys, postAttributesMsg.toByteArray());
72 } 89 }
@@ -62,6 +62,23 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac @@ -62,6 +62,23 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
62 ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; 62 ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration;
63 ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); 63 ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_TELEMETRY_PROTO_SCHEMA);
64 DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); 64 DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema");
  65 +
  66 + DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject");
  67 + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType();
  68 + assertNotNull(nestedJsonObjectBuilderDescriptor);
  69 + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build();
  70 +
  71 + DynamicMessage.Builder jsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject");
  72 + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType();
  73 + assertNotNull(jsonObjectBuilderDescriptor);
  74 + DynamicMessage jsonObject = jsonObjectBuilder
  75 + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42)
  76 + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1)
  77 + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2)
  78 + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3)
  79 + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject)
  80 + .build();
  81 +
65 DynamicMessage.Builder postTelemetryBuilder = telemetrySchema.newMessageBuilder("PostTelemetry"); 82 DynamicMessage.Builder postTelemetryBuilder = telemetrySchema.newMessageBuilder("PostTelemetry");
66 Descriptors.Descriptor postTelemetryMsgDescriptor = postTelemetryBuilder.getDescriptorForType(); 83 Descriptors.Descriptor postTelemetryMsgDescriptor = postTelemetryBuilder.getDescriptorForType();
67 assertNotNull(postTelemetryMsgDescriptor); 84 assertNotNull(postTelemetryMsgDescriptor);
@@ -70,7 +87,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac @@ -70,7 +87,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
70 .setField(postTelemetryMsgDescriptor.findFieldByName("key2"), true) 87 .setField(postTelemetryMsgDescriptor.findFieldByName("key2"), true)
71 .setField(postTelemetryMsgDescriptor.findFieldByName("key3"), 3.0) 88 .setField(postTelemetryMsgDescriptor.findFieldByName("key3"), 3.0)
72 .setField(postTelemetryMsgDescriptor.findFieldByName("key4"), 4) 89 .setField(postTelemetryMsgDescriptor.findFieldByName("key4"), 4)
73 - .setField(postTelemetryMsgDescriptor.findFieldByName("key5"), "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}") 90 + .setField(postTelemetryMsgDescriptor.findFieldByName("key5"), jsonObject)
74 .build(); 91 .build();
75 processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, postTelemetryMsg.toByteArray(), false); 92 processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, postTelemetryMsg.toByteArray(), false);
76 } 93 }
@@ -80,19 +97,27 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac @@ -80,19 +97,27 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
80 String schemaStr = "syntax =\"proto3\";\n" + 97 String schemaStr = "syntax =\"proto3\";\n" +
81 "\n" + 98 "\n" +
82 "package test;\n" + 99 "package test;\n" +
83 - " \n" +  
84 - "message PostTelemetry {\n" +  
85 - "\n" +  
86 - " message Values {\n" +  
87 - " string key1 = 1;\n" +  
88 - " bool key2 = 2;\n" +  
89 - " double key3 = 3;\n" +  
90 - " int32 key4 = 4;\n" +  
91 - " string key5 = 5;\n" +  
92 - " }\n" +  
93 "\n" + 100 "\n" +
  101 + "message PostTelemetry {\n" +
94 " int64 ts = 1;\n" + 102 " int64 ts = 1;\n" +
95 " Values values = 2;\n" + 103 " Values values = 2;\n" +
  104 + " \n" +
  105 + " message Values {\n" +
  106 + " string key1 = 3;\n" +
  107 + " bool key2 = 4;\n" +
  108 + " double key3 = 5;\n" +
  109 + " int32 key4 = 6;\n" +
  110 + " JsonObject key5 = 7;\n" +
  111 + " }\n" +
  112 + " \n" +
  113 + " message JsonObject {\n" +
  114 + " int32 someNumber = 8;\n" +
  115 + " repeated int32 someArray = 9;\n" +
  116 + " NestedJsonObject someNestedObject = 10;\n" +
  117 + " message NestedJsonObject {\n" +
  118 + " string key = 11;\n" +
  119 + " }\n" +
  120 + " }\n" +
96 "}"; 121 "}";
97 super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, DeviceProfileProvisionType.DISABLED, null, null); 122 super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, DeviceProfileProvisionType.DISABLED, null, null);
98 List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); 123 List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
@@ -105,6 +130,23 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac @@ -105,6 +130,23 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
105 ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(schemaStr); 130 ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(schemaStr);
106 DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); 131 DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema");
107 132
  133 + DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject");
  134 + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType();
  135 + assertNotNull(nestedJsonObjectBuilderDescriptor);
  136 + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build();
  137 +
  138 + DynamicMessage.Builder jsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject");
  139 + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType();
  140 + assertNotNull(jsonObjectBuilderDescriptor);
  141 + DynamicMessage jsonObject = jsonObjectBuilder
  142 + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42)
  143 + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1)
  144 + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2)
  145 + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3)
  146 + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject)
  147 + .build();
  148 +
  149 +
108 DynamicMessage.Builder valuesBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.Values"); 150 DynamicMessage.Builder valuesBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.Values");
109 Descriptors.Descriptor valuesDescriptor = valuesBuilder.getDescriptorForType(); 151 Descriptors.Descriptor valuesDescriptor = valuesBuilder.getDescriptorForType();
110 assertNotNull(valuesDescriptor); 152 assertNotNull(valuesDescriptor);
@@ -114,7 +156,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac @@ -114,7 +156,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
114 .setField(valuesDescriptor.findFieldByName("key2"), true) 156 .setField(valuesDescriptor.findFieldByName("key2"), true)
115 .setField(valuesDescriptor.findFieldByName("key3"), 3.0) 157 .setField(valuesDescriptor.findFieldByName("key3"), 3.0)
116 .setField(valuesDescriptor.findFieldByName("key4"), 4) 158 .setField(valuesDescriptor.findFieldByName("key4"), 4)
117 - .setField(valuesDescriptor.findFieldByName("key5"), "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}") 159 + .setField(valuesDescriptor.findFieldByName("key5"), jsonObject)
118 .build(); 160 .build();
119 161
120 DynamicMessage.Builder postTelemetryBuilder = telemetrySchema.newMessageBuilder("PostTelemetry"); 162 DynamicMessage.Builder postTelemetryBuilder = telemetrySchema.newMessageBuilder("PostTelemetry");
@@ -128,19 +128,8 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC @@ -128,19 +128,8 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC
128 List<MessageDefinition> messageDefinitions = new ArrayList<>(); 128 List<MessageDefinition> messageDefinitions = new ArrayList<>();
129 messageElementsList.forEach(messageElement -> { 129 messageElementsList.forEach(messageElement -> {
130 MessageDefinition.Builder messageDefinitionBuilder = MessageDefinition.newBuilder(messageElement.getName()); 130 MessageDefinition.Builder messageDefinitionBuilder = MessageDefinition.newBuilder(messageElement.getName());
131 - List<FieldElement> messageElementFields = messageElement.getFields();  
132 - List<OneOfElement> oneOfs = messageElement.getOneOfs();  
133 131
134 List<TypeElement> nestedTypes = messageElement.getNestedTypes(); 132 List<TypeElement> nestedTypes = messageElement.getNestedTypes();
135 - if (!messageElementFields.isEmpty()) {  
136 - addMessageFieldsToTheMessageDefinition(messageElementFields, messageDefinitionBuilder);  
137 - }  
138 - if (!oneOfs.isEmpty()) {  
139 - for (OneOfElement oneOfelement : oneOfs) {  
140 - MessageDefinition.OneofBuilder oneofBuilder = messageDefinitionBuilder.addOneof(oneOfelement.getName());  
141 - addMessageFieldsToTheOneOfDefinition(oneOfelement.getFields(), oneofBuilder);  
142 - }  
143 - }  
144 if (!nestedTypes.isEmpty()) { 133 if (!nestedTypes.isEmpty()) {
145 List<EnumElement> nestedEnumTypes = getEnumElements(nestedTypes); 134 List<EnumElement> nestedEnumTypes = getEnumElements(nestedTypes);
146 if (!nestedEnumTypes.isEmpty()) { 135 if (!nestedEnumTypes.isEmpty()) {
@@ -153,6 +142,17 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC @@ -153,6 +142,17 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC
153 List<MessageDefinition> nestedMessageDefinitions = getMessageDefinitions(nestedMessageTypes); 142 List<MessageDefinition> nestedMessageDefinitions = getMessageDefinitions(nestedMessageTypes);
154 nestedMessageDefinitions.forEach(messageDefinitionBuilder::addMessageDefinition); 143 nestedMessageDefinitions.forEach(messageDefinitionBuilder::addMessageDefinition);
155 } 144 }
  145 + List<FieldElement> messageElementFields = messageElement.getFields();
  146 + List<OneOfElement> oneOfs = messageElement.getOneOfs();
  147 + if (!oneOfs.isEmpty()) {
  148 + for (OneOfElement oneOfelement : oneOfs) {
  149 + MessageDefinition.OneofBuilder oneofBuilder = messageDefinitionBuilder.addOneof(oneOfelement.getName());
  150 + addMessageFieldsToTheOneOfDefinition(oneOfelement.getFields(), oneofBuilder);
  151 + }
  152 + }
  153 + if (!messageElementFields.isEmpty()) {
  154 + addMessageFieldsToTheMessageDefinition(messageElementFields, messageDefinitionBuilder);
  155 + }
156 messageDefinitions.add(messageDefinitionBuilder.build()); 156 messageDefinitions.add(messageDefinitionBuilder.build());
157 }); 157 });
158 return messageDefinitions; 158 return messageDefinitions;
  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.transport.mqtt.util;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class AlwaysTrueTopicFilter implements MqttTopicFilter {
  22 +
  23 + @Override
  24 + public boolean filter(String topic) {
  25 + return true;
  26 + }
  27 +}
@@ -33,7 +33,9 @@ public class MqttTopicFilterFactory { @@ -33,7 +33,9 @@ public class MqttTopicFilterFactory {
33 throw new IllegalArgumentException("Topic filter can't be empty!"); 33 throw new IllegalArgumentException("Topic filter can't be empty!");
34 } 34 }
35 return filters.computeIfAbsent(topicFilter, filter -> { 35 return filters.computeIfAbsent(topicFilter, filter -> {
36 - if (filter.contains("+") || filter.contains("#")) { 36 + if (filter.equals("#")) {
  37 + return new AlwaysTrueTopicFilter();
  38 + } else if (filter.contains("+") || filter.contains("#")) {
37 String regex = filter 39 String regex = filter
38 .replace("\\", "\\\\") 40 .replace("\\", "\\\\")
39 .replace("+", "[^/]+") 41 .replace("+", "[^/]+")
@@ -20,7 +20,6 @@ import org.junit.runner.RunWith; @@ -20,7 +20,6 @@ import org.junit.runner.RunWith;
20 import org.mockito.runners.MockitoJUnitRunner; 20 import org.mockito.runners.MockitoJUnitRunner;
21 21
22 import javax.script.ScriptException; 22 import javax.script.ScriptException;
23 -import java.util.regex.Pattern;  
24 23
25 import static org.junit.Assert.assertFalse; 24 import static org.junit.Assert.assertFalse;
26 import static org.junit.Assert.assertTrue; 25 import static org.junit.Assert.assertTrue;
@@ -31,6 +30,9 @@ public class MqttTopicFilterFactoryTest { @@ -31,6 +30,9 @@ public class MqttTopicFilterFactoryTest {
31 private static String TEST_STR_1 = "Sensor/Temperature/House/48"; 30 private static String TEST_STR_1 = "Sensor/Temperature/House/48";
32 private static String TEST_STR_2 = "Sensor/Temperature"; 31 private static String TEST_STR_2 = "Sensor/Temperature";
33 private static String TEST_STR_3 = "Sensor/Temperature2/House/48"; 32 private static String TEST_STR_3 = "Sensor/Temperature2/House/48";
  33 + private static String TEST_STR_4 = "/Sensor/Temperature2/House/48";
  34 + private static String TEST_STR_5 = "Sensor/ Temperature";
  35 + private static String TEST_STR_6 = "/";
34 36
35 @Test 37 @Test
36 public void metadataCanBeUpdated() throws ScriptException { 38 public void metadataCanBeUpdated() throws ScriptException {
@@ -51,6 +53,17 @@ public class MqttTopicFilterFactoryTest { @@ -51,6 +53,17 @@ public class MqttTopicFilterFactoryTest {
51 assertTrue(filter.filter(TEST_STR_1)); 53 assertTrue(filter.filter(TEST_STR_1));
52 assertTrue(filter.filter(TEST_STR_2)); 54 assertTrue(filter.filter(TEST_STR_2));
53 assertFalse(filter.filter(TEST_STR_3)); 55 assertFalse(filter.filter(TEST_STR_3));
  56 +
  57 + filter = MqttTopicFilterFactory.toFilter("#");
  58 + assertTrue(filter.filter(TEST_STR_1));
  59 + assertTrue(filter.filter(TEST_STR_2));
  60 + assertTrue(filter.filter(TEST_STR_3));
  61 + assertTrue(filter.filter(TEST_STR_4));
  62 + assertTrue(filter.filter(TEST_STR_5));
  63 + assertTrue(filter.filter(TEST_STR_6));
  64 +
  65 + filter = MqttTopicFilterFactory.toFilter("Sensor/Temperature#");
  66 + assertFalse(filter.filter(TEST_STR_2));
54 } 67 }
55 68
56 } 69 }
@@ -33,6 +33,7 @@ import org.springframework.cache.Cache; @@ -33,6 +33,7 @@ import org.springframework.cache.Cache;
33 import org.springframework.cache.CacheManager; 33 import org.springframework.cache.CacheManager;
34 import org.springframework.cache.annotation.Cacheable; 34 import org.springframework.cache.annotation.Cacheable;
35 import org.springframework.stereotype.Service; 35 import org.springframework.stereotype.Service;
  36 +import org.springframework.util.CollectionUtils;
36 import org.thingsboard.server.common.data.Device; 37 import org.thingsboard.server.common.data.Device;
37 import org.thingsboard.server.common.data.DeviceProfile; 38 import org.thingsboard.server.common.data.DeviceProfile;
38 import org.thingsboard.server.common.data.DeviceProfileInfo; 39 import org.thingsboard.server.common.data.DeviceProfileInfo;
@@ -42,6 +43,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; @@ -42,6 +43,7 @@ import org.thingsboard.server.common.data.DeviceTransportType;
42 import org.thingsboard.server.common.data.Tenant; 43 import org.thingsboard.server.common.data.Tenant;
43 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; 44 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
44 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; 45 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
  46 +import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
45 import org.thingsboard.server.common.data.device.profile.DeviceProfileData; 47 import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
46 import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; 48 import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
47 import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; 49 import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
@@ -60,8 +62,10 @@ import org.thingsboard.server.dao.tenant.TenantDao; @@ -60,8 +62,10 @@ import org.thingsboard.server.dao.tenant.TenantDao;
60 62
61 import java.util.Arrays; 63 import java.util.Arrays;
62 import java.util.Collections; 64 import java.util.Collections;
  65 +import java.util.HashSet;
63 import java.util.List; 66 import java.util.List;
64 import java.util.stream.Collectors; 67 import java.util.stream.Collectors;
  68 +import java.util.Set;
65 69
66 import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE; 70 import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE;
67 import static org.thingsboard.server.dao.service.Validator.validateId; 71 import static org.thingsboard.server.dao.service.Validator.validateId;
@@ -136,7 +140,7 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D @@ -136,7 +140,7 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
136 if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_profile_name_unq_key")) { 140 if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_profile_name_unq_key")) {
137 throw new DataValidationException("Device profile with such name already exists!"); 141 throw new DataValidationException("Device profile with such name already exists!");
138 } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_provision_key_unq_key")) { 142 } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_provision_key_unq_key")) {
139 - throw new DataValidationException("Device profile with such provision device key already exists!"); 143 + throw new DataValidationException("Device profile with such provision device key already exists!");
140 } else { 144 } else {
141 throw t; 145 throw t;
142 } 146 }
@@ -347,6 +351,22 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D @@ -347,6 +351,22 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
347 } 351 }
348 } 352 }
349 } 353 }
  354 +
  355 + List<DeviceProfileAlarm> profileAlarms = deviceProfile.getProfileData().getAlarms();
  356 +
  357 + if (!CollectionUtils.isEmpty(profileAlarms)) {
  358 + Set<String> alarmTypes = new HashSet<>();
  359 + for (DeviceProfileAlarm alarm : profileAlarms) {
  360 + String alarmType = alarm.getAlarmType();
  361 + if (StringUtils.isEmpty(alarmType)) {
  362 + throw new DataValidationException("Alarm rule type should be specified!");
  363 + }
  364 + if (!alarmTypes.add(alarmType)) {
  365 + throw new DataValidationException(String.format("Can't create device profile with the same alarm rule types: \"%s\"!", alarmType));
  366 + }
  367 + }
  368 + }
  369 +
350 } 370 }
351 371
352 @Override 372 @Override
@@ -393,6 +413,7 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D @@ -393,6 +413,7 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
393 " for " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!"); 413 " for " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!");
394 } 414 }
395 } 415 }
  416 +
396 private void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage) { 417 private void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage) {
397 if (!isEmptySettings) { 418 if (!isEmptySettings) {
398 throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + invalidSettingsMessage); 419 throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + invalidSettingsMessage);
@@ -41,6 +41,9 @@ @@ -41,6 +41,9 @@
41 <mat-error *ngIf="alarmFormGroup.get('alarmType').hasError('required')"> 41 <mat-error *ngIf="alarmFormGroup.get('alarmType').hasError('required')">
42 {{ 'device-profile.alarm-type-required' | translate }} 42 {{ 'device-profile.alarm-type-required' | translate }}
43 </mat-error> 43 </mat-error>
  44 + <mat-error *ngIf="alarmFormGroup.get('alarmType').hasError('unique')">
  45 + {{ 'device-profile.alarm-type-unique' | translate }}
  46 + </mat-error>
44 <mat-hint *ngIf="!disabled" 47 <mat-hint *ngIf="!disabled"
45 innerHTML="{{ 'device-profile.alarm-type-pattern-hint' | translate }}"></mat-hint> 48 innerHTML="{{ 'device-profile.alarm-type-pattern-hint' | translate }}"></mat-hint>
46 </mat-form-field> 49 </mat-form-field>
@@ -133,6 +133,19 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit @@ -133,6 +133,19 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
133 } 133 }
134 134
135 public validate(c: FormControl) { 135 public validate(c: FormControl) {
  136 + if (c.parent) {
  137 + const alarmType = c.value.alarmType;
  138 + const profileAlarmsType = [];
  139 + c.parent.getRawValue().forEach((alarm: DeviceProfileAlarm) => {
  140 + profileAlarmsType.push(alarm.alarmType);
  141 + }
  142 + );
  143 + if (profileAlarmsType.filter(profileAlarmType => profileAlarmType === alarmType).length > 1) {
  144 + this.alarmFormGroup.get('alarmType').setErrors({
  145 + unique: true
  146 + });
  147 + }
  148 + }
136 return (this.alarmFormGroup.valid) ? null : { 149 return (this.alarmFormGroup.valid) ? null : {
137 alarm: { 150 alarm: {
138 valid: false, 151 valid: false,
@@ -38,7 +38,7 @@ import { @@ -38,7 +38,7 @@ import {
38 } from '@shared/models/device.models'; 38 } from '@shared/models/device.models';
39 import { EntityType } from '@shared/models/entity-type.models'; 39 import { EntityType } from '@shared/models/entity-type.models';
40 import { RuleChainId } from '@shared/models/id/rule-chain-id'; 40 import { RuleChainId } from '@shared/models/id/rule-chain-id';
41 -import {ServiceType} from "@shared/models/queue.models"; 41 +import { ServiceType } from '@shared/models/queue.models';
42 42
43 @Component({ 43 @Component({
44 selector: 'tb-device-profile', 44 selector: 'tb-device-profile',
@@ -176,10 +176,10 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { @@ -176,10 +176,10 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
176 transportConfiguration: entity.profileData?.transportConfiguration, 176 transportConfiguration: entity.profileData?.transportConfiguration,
177 alarms: entity.profileData?.alarms, 177 alarms: entity.profileData?.alarms,
178 provisionConfiguration: deviceProvisionConfiguration 178 provisionConfiguration: deviceProvisionConfiguration
179 - }});  
180 - this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null});  
181 - this.entityForm.patchValue({defaultQueueName: entity.defaultQueueName});  
182 - this.entityForm.patchValue({description: entity.description}); 179 + }}, {emitEvent: false});
  180 + this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}, {emitEvent: false});
  181 + this.entityForm.patchValue({defaultQueueName: entity.defaultQueueName}, {emitEvent: false});
  182 + this.entityForm.patchValue({description: entity.description}, {emitEvent: false});
183 } 183 }
184 184
185 prepareFormValue(formValue: any): any { 185 prepareFormValue(formValue: any): any {
@@ -913,6 +913,7 @@ @@ -913,6 +913,7 @@
913 "edit-alarm-rule": "Edit alarm rule", 913 "edit-alarm-rule": "Edit alarm rule",
914 "alarm-type": "Alarm type", 914 "alarm-type": "Alarm type",
915 "alarm-type-required": "Alarm type is required.", 915 "alarm-type-required": "Alarm type is required.",
  916 + "alarm-type-unique": "Alarm type must be unique within the device profile alarm rules.",
916 "alarm-type-pattern-hint": "Alarm type pattern, use <code>${metaKeyName}</code> to substitute variables from metadata", 917 "alarm-type-pattern-hint": "Alarm type pattern, use <code>${metaKeyName}</code> to substitute variables from metadata",
917 "create-alarm-pattern": "Create <b>{{alarmType}}</b> alarm", 918 "create-alarm-pattern": "Create <b>{{alarmType}}</b> alarm",
918 "create-alarm-rules": "Create alarm rules", 919 "create-alarm-rules": "Create alarm rules",