Commit 0ea4723e2e3f97481d7c8df540ebafe72c22c62b
Committed by
GitHub
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 | 219 | "defaultPageSize": 10, |
220 | 220 | "defaultSortOrder": "-createdTime", |
221 | 221 | "enableSelectColumnDisplay": false, |
222 | - "enableStatusFilter": true, | |
223 | - "alarmsTitle": "Alarms" | |
222 | + "alarmsTitle": "Alarms", | |
223 | + "enableFilter": true | |
224 | 224 | }, |
225 | 225 | "title": "New Alarms table", |
226 | 226 | "dropShadow": true, |
... | ... | @@ -234,6 +234,9 @@ |
234 | 234 | "showLegend": false, |
235 | 235 | "alarmSource": { |
236 | 236 | "type": "entity", |
237 | + "name": "alarms", | |
238 | + "entityAliasId": "68a058e1-fdda-8482-715b-3ae4a488568e", | |
239 | + "filterId": null, | |
237 | 240 | "dataKeys": [ |
238 | 241 | { |
239 | 242 | "name": "createdTime", |
... | ... | @@ -275,9 +278,7 @@ |
275 | 278 | "settings": {}, |
276 | 279 | "_hash": 0.7977920750136249 |
277 | 280 | } |
278 | - ], | |
279 | - "entityAliasId": "ce27a9d0-93bf-b7a4-054d-d0369a8cf813", | |
280 | - "name": "alarms" | |
281 | + ] | |
281 | 282 | }, |
282 | 283 | "alarmSearchStatus": "ANY", |
283 | 284 | "alarmsPollingInterval": 5, |
... | ... | @@ -1031,7 +1032,8 @@ |
1031 | 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 | 1033 | "useLabelFunction": true, |
1033 | 1034 | "provider": "openstreet-map", |
1034 | - "draggableMarker": true | |
1035 | + "draggableMarker": true, | |
1036 | + "editablePolygon": true | |
1035 | 1037 | }, |
1036 | 1038 | "title": "New Markers Placement - OpenStreetMap", |
1037 | 1039 | "dropShadow": true, |
... | ... | @@ -1062,61 +1064,6 @@ |
1062 | 1064 | "displayTimewindow": true |
1063 | 1065 | }, |
1064 | 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 | 1069 | "states": { |
... | ... | @@ -1215,12 +1162,6 @@ |
1215 | 1162 | "sizeY": 6, |
1216 | 1163 | "row": 6, |
1217 | 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 | 1167 | "gridSettings": { |
... | ... | @@ -1257,16 +1198,6 @@ |
1257 | 1198 | "stateEntityParamName": null, |
1258 | 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 | 1203 | "timewindow": { |
... | ... | @@ -1301,7 +1232,8 @@ |
1301 | 1232 | "showDashboardTimewindow": true, |
1302 | 1233 | "showDashboardExport": true, |
1303 | 1234 | "toolbarAlwaysOpen": true |
1304 | - } | |
1235 | + }, | |
1236 | + "filters": {} | |
1305 | 1237 | }, |
1306 | 1238 | "name": "Thermostats" |
1307 | 1239 | } |
\ No newline at end of file | ... | ... |
1 | 1 | { |
2 | 2 | "ruleChain": { |
3 | - "additionalInfo": null, | |
3 | + "additionalInfo": { | |
4 | + "description": "" | |
5 | + }, | |
4 | 6 | "name": "Thermostat Alarms", |
5 | 7 | "firstRuleNodeId": null, |
6 | 8 | "root": false, |
... | ... | @@ -8,131 +10,126 @@ |
8 | 10 | "configuration": null |
9 | 11 | }, |
10 | 12 | "metadata": { |
11 | - "firstNodeIndex": 5, | |
13 | + "firstNodeIndex": 6, | |
12 | 14 | "nodes": [ |
13 | 15 | { |
14 | 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 | 22 | "debugMode": false, |
21 | 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 | 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 | 35 | "debugMode": false, |
40 | 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 | 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 | 48 | "debugMode": false, |
53 | 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 | 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 | 60 | "debugMode": false, |
72 | 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 | 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 | 72 | "debugMode": false, |
85 | 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 | 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 | 84 | "debugMode": false, |
97 | 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 | 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 | 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 | 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 | 133 | "type": "Success" |
137 | 134 | } |
138 | 135 | ], | ... | ... |
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
... | ... | @@ -28,12 +28,21 @@ import org.thingsboard.server.common.data.Customer; |
28 | 28 | import org.thingsboard.server.common.data.DataConstants; |
29 | 29 | import org.thingsboard.server.common.data.Device; |
30 | 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 | 34 | import org.thingsboard.server.common.data.Tenant; |
32 | 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 | 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 | 46 | import org.thingsboard.server.common.data.id.CustomerId; |
38 | 47 | import org.thingsboard.server.common.data.id.DeviceId; |
39 | 48 | import org.thingsboard.server.common.data.id.DeviceProfileId; |
... | ... | @@ -42,19 +51,29 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; |
42 | 51 | import org.thingsboard.server.common.data.kv.BooleanDataEntry; |
43 | 52 | import org.thingsboard.server.common.data.kv.DoubleDataEntry; |
44 | 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 | 64 | import org.thingsboard.server.common.data.security.Authority; |
47 | 65 | import org.thingsboard.server.common.data.security.DeviceCredentials; |
48 | 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 | 69 | import org.thingsboard.server.common.data.widget.WidgetsBundle; |
50 | -import org.thingsboard.server.dao.asset.AssetService; | |
51 | 70 | import org.thingsboard.server.dao.attributes.AttributesService; |
52 | 71 | import org.thingsboard.server.dao.customer.CustomerService; |
53 | 72 | import org.thingsboard.server.dao.device.DeviceCredentialsService; |
54 | 73 | import org.thingsboard.server.dao.device.DeviceProfileService; |
55 | 74 | import org.thingsboard.server.dao.device.DeviceService; |
56 | 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 | 77 | import org.thingsboard.server.dao.settings.AdminSettingsService; |
59 | 78 | import org.thingsboard.server.dao.tenant.TenantProfileService; |
60 | 79 | import org.thingsboard.server.dao.tenant.TenantService; |
... | ... | @@ -62,6 +81,8 @@ import org.thingsboard.server.dao.user.UserService; |
62 | 81 | import org.thingsboard.server.dao.widget.WidgetsBundleService; |
63 | 82 | |
64 | 83 | import java.util.Arrays; |
84 | +import java.util.Collections; | |
85 | +import java.util.LinkedHashMap; | |
65 | 86 | |
66 | 87 | @Service |
67 | 88 | @Profile("install") |
... | ... | @@ -97,12 +118,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { |
97 | 118 | private CustomerService customerService; |
98 | 119 | |
99 | 120 | @Autowired |
100 | - private RelationService relationService; | |
101 | - | |
102 | - @Autowired | |
103 | - private AssetService assetService; | |
104 | - | |
105 | - @Autowired | |
106 | 121 | private DeviceService deviceService; |
107 | 122 | |
108 | 123 | @Autowired |
... | ... | @@ -114,6 +129,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { |
114 | 129 | @Autowired |
115 | 130 | private DeviceCredentialsService deviceCredentialsService; |
116 | 131 | |
132 | + @Autowired | |
133 | + private RuleChainService ruleChainService; | |
134 | + | |
117 | 135 | @Bean |
118 | 136 | protected BCryptPasswordEncoder passwordEncoder() { |
119 | 137 | return new BCryptPasswordEncoder(); |
... | ... | @@ -245,19 +263,133 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { |
245 | 263 | createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " + |
246 | 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 | 394 | attributesService.save(demoTenant.getId(), t1Id, DataConstants.SERVER_SCOPE, |
263 | 395 | Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.3948)), | ... | ... |
... | ... | @@ -62,19 +62,28 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest |
62 | 62 | |
63 | 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 | 66 | "\n" + |
67 | 67 | "package test;\n" + |
68 | - " \n" + | |
68 | + "\n" + | |
69 | 69 | "message PostTelemetry {\n" + |
70 | 70 | " string key1 = 1;\n" + |
71 | 71 | " bool key2 = 2;\n" + |
72 | 72 | " double key3 = 3;\n" + |
73 | 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 | 87 | "\n" + |
79 | 88 | "package test;\n" + |
80 | 89 | "\n" + |
... | ... | @@ -83,7 +92,16 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest |
83 | 92 | " bool key2 = 2;\n" + |
84 | 93 | " double key3 = 3;\n" + |
85 | 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 | 107 | protected Tenant savedTenant; | ... | ... |
... | ... | @@ -26,13 +26,13 @@ import java.util.Arrays; |
26 | 26 | |
27 | 27 | @RunWith(ClasspathSuite.class) |
28 | 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 | 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 | 37 | public class MqttSqlTestSuite { |
38 | 38 | ... | ... |
... | ... | @@ -122,7 +122,7 @@ public abstract class AbstractMqttAttributesRequestIntegrationTest extends Abstr |
122 | 122 | client.publish(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX + "1", mqttMessage); |
123 | 123 | latch.await(3, TimeUnit.SECONDS); |
124 | 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 | 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 | 62 | " bool attribute2 = 2;\n" + |
63 | 63 | " double attribute3 = 3;\n" + |
64 | 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 | 77 | @After |
... | ... | @@ -93,6 +102,23 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends |
93 | 102 | ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; |
94 | 103 | ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(ATTRIBUTES_SCHEMA_STR); |
95 | 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 | 122 | DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); |
97 | 123 | Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); |
98 | 124 | assertNotNull(postAttributesMsgDescriptor); |
... | ... | @@ -101,7 +127,7 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends |
101 | 127 | .setField(postAttributesMsgDescriptor.findFieldByName("attribute2"), true) |
102 | 128 | .setField(postAttributesMsgDescriptor.findFieldByName("attribute3"), 42.0) |
103 | 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 | 131 | .build(); |
106 | 132 | byte[] payload = postAttributesMsg.toByteArray(); |
107 | 133 | client.publish(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, new MqttMessage(payload)); | ... | ... |
... | ... | @@ -163,16 +163,10 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt |
163 | 163 | break; |
164 | 164 | case "key5": |
165 | 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 | 170 | assertEquals("value", someNestedObject.get("key")); |
177 | 171 | break; |
178 | 172 | } | ... | ... |
... | ... | @@ -58,6 +58,23 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac |
58 | 58 | ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; |
59 | 59 | ProtoFileElement transportProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_ATTRIBUTES_PROTO_SCHEMA); |
60 | 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 | 78 | DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); |
62 | 79 | Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); |
63 | 80 | assertNotNull(postAttributesMsgDescriptor); |
... | ... | @@ -66,7 +83,7 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac |
66 | 83 | .setField(postAttributesMsgDescriptor.findFieldByName("key2"), true) |
67 | 84 | .setField(postAttributesMsgDescriptor.findFieldByName("key3"), 3.0) |
68 | 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 | 87 | .build(); |
71 | 88 | processAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, expectedKeys, postAttributesMsg.toByteArray()); |
72 | 89 | } | ... | ... |
... | ... | @@ -62,6 +62,23 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac |
62 | 62 | ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; |
63 | 63 | ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); |
64 | 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 | 82 | DynamicMessage.Builder postTelemetryBuilder = telemetrySchema.newMessageBuilder("PostTelemetry"); |
66 | 83 | Descriptors.Descriptor postTelemetryMsgDescriptor = postTelemetryBuilder.getDescriptorForType(); |
67 | 84 | assertNotNull(postTelemetryMsgDescriptor); |
... | ... | @@ -70,7 +87,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac |
70 | 87 | .setField(postTelemetryMsgDescriptor.findFieldByName("key2"), true) |
71 | 88 | .setField(postTelemetryMsgDescriptor.findFieldByName("key3"), 3.0) |
72 | 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 | 91 | .build(); |
75 | 92 | processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, postTelemetryMsg.toByteArray(), false); |
76 | 93 | } |
... | ... | @@ -80,19 +97,27 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac |
80 | 97 | String schemaStr = "syntax =\"proto3\";\n" + |
81 | 98 | "\n" + |
82 | 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 | 100 | "\n" + |
101 | + "message PostTelemetry {\n" + | |
94 | 102 | " int64 ts = 1;\n" + |
95 | 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 | 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 | 123 | List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); |
... | ... | @@ -105,6 +130,23 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac |
105 | 130 | ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(schemaStr); |
106 | 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 | 150 | DynamicMessage.Builder valuesBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.Values"); |
109 | 151 | Descriptors.Descriptor valuesDescriptor = valuesBuilder.getDescriptorForType(); |
110 | 152 | assertNotNull(valuesDescriptor); |
... | ... | @@ -114,7 +156,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac |
114 | 156 | .setField(valuesDescriptor.findFieldByName("key2"), true) |
115 | 157 | .setField(valuesDescriptor.findFieldByName("key3"), 3.0) |
116 | 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 | 160 | .build(); |
119 | 161 | |
120 | 162 | DynamicMessage.Builder postTelemetryBuilder = telemetrySchema.newMessageBuilder("PostTelemetry"); | ... | ... |
... | ... | @@ -128,19 +128,8 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC |
128 | 128 | List<MessageDefinition> messageDefinitions = new ArrayList<>(); |
129 | 129 | messageElementsList.forEach(messageElement -> { |
130 | 130 | MessageDefinition.Builder messageDefinitionBuilder = MessageDefinition.newBuilder(messageElement.getName()); |
131 | - List<FieldElement> messageElementFields = messageElement.getFields(); | |
132 | - List<OneOfElement> oneOfs = messageElement.getOneOfs(); | |
133 | 131 | |
134 | 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 | 133 | if (!nestedTypes.isEmpty()) { |
145 | 134 | List<EnumElement> nestedEnumTypes = getEnumElements(nestedTypes); |
146 | 135 | if (!nestedEnumTypes.isEmpty()) { |
... | ... | @@ -153,6 +142,17 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC |
153 | 142 | List<MessageDefinition> nestedMessageDefinitions = getMessageDefinitions(nestedMessageTypes); |
154 | 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 | 156 | messageDefinitions.add(messageDefinitionBuilder.build()); |
157 | 157 | }); |
158 | 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 | 33 | throw new IllegalArgumentException("Topic filter can't be empty!"); |
34 | 34 | } |
35 | 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 | 39 | String regex = filter |
38 | 40 | .replace("\\", "\\\\") |
39 | 41 | .replace("+", "[^/]+") | ... | ... |
... | ... | @@ -20,7 +20,6 @@ import org.junit.runner.RunWith; |
20 | 20 | import org.mockito.runners.MockitoJUnitRunner; |
21 | 21 | |
22 | 22 | import javax.script.ScriptException; |
23 | -import java.util.regex.Pattern; | |
24 | 23 | |
25 | 24 | import static org.junit.Assert.assertFalse; |
26 | 25 | import static org.junit.Assert.assertTrue; |
... | ... | @@ -31,6 +30,9 @@ public class MqttTopicFilterFactoryTest { |
31 | 30 | private static String TEST_STR_1 = "Sensor/Temperature/House/48"; |
32 | 31 | private static String TEST_STR_2 = "Sensor/Temperature"; |
33 | 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 | 37 | @Test |
36 | 38 | public void metadataCanBeUpdated() throws ScriptException { |
... | ... | @@ -51,6 +53,17 @@ public class MqttTopicFilterFactoryTest { |
51 | 53 | assertTrue(filter.filter(TEST_STR_1)); |
52 | 54 | assertTrue(filter.filter(TEST_STR_2)); |
53 | 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 | 33 | import org.springframework.cache.CacheManager; |
34 | 34 | import org.springframework.cache.annotation.Cacheable; |
35 | 35 | import org.springframework.stereotype.Service; |
36 | +import org.springframework.util.CollectionUtils; | |
36 | 37 | import org.thingsboard.server.common.data.Device; |
37 | 38 | import org.thingsboard.server.common.data.DeviceProfile; |
38 | 39 | import org.thingsboard.server.common.data.DeviceProfileInfo; |
... | ... | @@ -42,6 +43,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; |
42 | 43 | import org.thingsboard.server.common.data.Tenant; |
43 | 44 | import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; |
44 | 45 | import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; |
46 | +import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; | |
45 | 47 | import org.thingsboard.server.common.data.device.profile.DeviceProfileData; |
46 | 48 | import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; |
47 | 49 | import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; |
... | ... | @@ -60,8 +62,10 @@ import org.thingsboard.server.dao.tenant.TenantDao; |
60 | 62 | |
61 | 63 | import java.util.Arrays; |
62 | 64 | import java.util.Collections; |
65 | +import java.util.HashSet; | |
63 | 66 | import java.util.List; |
64 | 67 | import java.util.stream.Collectors; |
68 | +import java.util.Set; | |
65 | 69 | |
66 | 70 | import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE; |
67 | 71 | import static org.thingsboard.server.dao.service.Validator.validateId; |
... | ... | @@ -136,7 +140,7 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D |
136 | 140 | if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_profile_name_unq_key")) { |
137 | 141 | throw new DataValidationException("Device profile with such name already exists!"); |
138 | 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 | 144 | } else { |
141 | 145 | throw t; |
142 | 146 | } |
... | ... | @@ -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 | 372 | @Override |
... | ... | @@ -393,6 +413,7 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D |
393 | 413 | " for " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!"); |
394 | 414 | } |
395 | 415 | } |
416 | + | |
396 | 417 | private void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage) { |
397 | 418 | if (!isEmptySettings) { |
398 | 419 | throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + invalidSettingsMessage); | ... | ... |
... | ... | @@ -41,6 +41,9 @@ |
41 | 41 | <mat-error *ngIf="alarmFormGroup.get('alarmType').hasError('required')"> |
42 | 42 | {{ 'device-profile.alarm-type-required' | translate }} |
43 | 43 | </mat-error> |
44 | + <mat-error *ngIf="alarmFormGroup.get('alarmType').hasError('unique')"> | |
45 | + {{ 'device-profile.alarm-type-unique' | translate }} | |
46 | + </mat-error> | |
44 | 47 | <mat-hint *ngIf="!disabled" |
45 | 48 | innerHTML="{{ 'device-profile.alarm-type-pattern-hint' | translate }}"></mat-hint> |
46 | 49 | </mat-form-field> | ... | ... |
... | ... | @@ -133,6 +133,19 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit |
133 | 133 | } |
134 | 134 | |
135 | 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 | 149 | return (this.alarmFormGroup.valid) ? null : { |
137 | 150 | alarm: { |
138 | 151 | valid: false, | ... | ... |
... | ... | @@ -38,7 +38,7 @@ import { |
38 | 38 | } from '@shared/models/device.models'; |
39 | 39 | import { EntityType } from '@shared/models/entity-type.models'; |
40 | 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 | 43 | @Component({ |
44 | 44 | selector: 'tb-device-profile', |
... | ... | @@ -176,10 +176,10 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { |
176 | 176 | transportConfiguration: entity.profileData?.transportConfiguration, |
177 | 177 | alarms: entity.profileData?.alarms, |
178 | 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 | 185 | prepareFormValue(formValue: any): any { | ... | ... |
... | ... | @@ -913,6 +913,7 @@ |
913 | 913 | "edit-alarm-rule": "Edit alarm rule", |
914 | 914 | "alarm-type": "Alarm type", |
915 | 915 | "alarm-type-required": "Alarm type is required.", |
916 | + "alarm-type-unique": "Alarm type must be unique within the device profile alarm rules.", | |
916 | 917 | "alarm-type-pattern-hint": "Alarm type pattern, use <code>${metaKeyName}</code> to substitute variables from metadata", |
917 | 918 | "create-alarm-pattern": "Create <b>{{alarmType}}</b> alarm", |
918 | 919 | "create-alarm-rules": "Create alarm rules", | ... | ... |