Commit fc0d47a89cf54ac479815c6cedff4a63fca7d52b

Authored by 芯火源
1 parent 89167b44

feat: 场景联动显示告警详情

... ... @@ -175,4 +175,17 @@ public interface FastIotConstants {
175 175 /**RPC单项双向*/
176 176 public static String ONEWAY = "oneway";
177 177 }
  178 + class Alarm{
  179 +
  180 + /**遥测指标标识符*/
  181 + public static String KEY = "key";
  182 + /**遥测指标值*/
  183 + public static String VALUE = "value";
  184 + /**触发器逻辑关系*/
  185 + public static String PREDICATE = "logic";
  186 + /**触发器*/
  187 + public static String TRIGGER = "trigger";
  188 + /**执行条件*/
  189 + public static String CONDITION = "condition";
  190 + }
178 191 }
... ...
... ... @@ -89,24 +89,27 @@ class ReactState {
89 89 *
90 90 * @param ctx
91 91 * @param msg 设备推送的遥测数据
  92 + * @param prefixId 运算规则ID,例如:触发器、执行条件
92 93 * @param deviceId 遥测数据的来源设备的TB设备ID
93 94 * @throws ExecutionException
94 95 * @throws InterruptedException
95 96 */
96   - public void process(TbContext ctx, TbMsg msg, String prefixId,String deviceId)
  97 + public void process(TbContext ctx, TbMsg msg, String prefixId, String deviceId)
97 98 throws ExecutionException, InterruptedException {
98 99
99   - StringBuilder detail = new StringBuilder();
  100 + /** 场景联动告警详情 */
  101 + ObjectNode detail = JacksonUtil.newObjectNode();
100 102 if (actions == null) {
101 103 ctx.tellSuccess(msg);
102 104 }
103 105
  106 + /** 1、单个触发器内只要有1个设备满足条件即为true 2、多个触发器只要有1个触发器满足条件即为true */
104 107 AtomicBoolean triggerMatched = new AtomicBoolean(true);
105 108 Optional.ofNullable(triggers)
106 109 .ifPresent(
107   - t -> {
  110 + all -> {
108 111 triggerMatched.set(false);
109   - t.forEach(
  112 + all.forEach(
110 113 trigger -> {
111 114 ScopeEnum entityType = trigger.getEntityType();
112 115 List<String> trifggerDevices = trigger.getEntityId();
... ... @@ -119,24 +122,30 @@ class ReactState {
119 122 triggerMatched.set(
120 123 trifggerDevices.stream()
121 124 .anyMatch(
122   - id -> {
  125 + devId -> {
123 126 TriggerState triggerState =
124   - getOrCreateTriggerState(trigger, tkProjectId, id);
  127 + getOrCreateTriggerState(trigger, tkProjectId, devId);
125 128 if (triggerState == null) {
126 129 return false;
127 130 }
128 131 try {
129 132 boolean fresh = false;
130   - if(trigger.getId().equals(prefixId)&&msg.getOriginator().getId().toString().equals(id)){
131   - fresh=true;
  133 + if (trigger.getId().equals(prefixId)
  134 + && msg.getOriginator().getId().toString().equals(devId)) {
  135 + fresh = true;
132 136 }
133   - boolean result = triggerState.process(ctx, msg,fresh);
134   - log.error(String.format("触发器【%s】刷新【%s】结果【%s】触发器设备【%s】数据设备【%s】数据内容【%s】",trigger.getId(),fresh,result,id,msg.getOriginator(),msg.getData()));
135   - if (result) {
136   - detail.append(
137   - triggerState.getAlarmDetails() == null
138   - ? ""
139   - : triggerState.getAlarmDetails());
  137 + ObjectNode result = triggerState.process(ctx, msg, fresh);
  138 + log.error(
  139 + String.format(
  140 + "触发器【%s】刷新【%s】结果【%s】触发器设备【%s】数据设备【%s】数据内容【%s】",
  141 + trigger.getId(),
  142 + fresh,
  143 + result,
  144 + devId,
  145 + msg.getOriginator(),
  146 + msg.getData()));
  147 + if (!result.isEmpty()) {
  148 + detail.set(FastIotConstants.Alarm.TRIGGER,result);
140 149 return true;
141 150 } else if (currentAlarms.containsKey(deviceId)) {
142 151 // 清除设备告警
... ... @@ -155,51 +164,65 @@ class ReactState {
155 164 });
156 165 });
157 166
158   - /** 执行条件的所有设备都满足才为true */
  167 + /** 1、单个执行条件内全部设备满足条件才为true 2、多个执行条件全部执行条件满足条件才为true */
159 168 AtomicBoolean conditionMatched = new AtomicBoolean(true);
160 169 Optional.ofNullable(conditions)
161   - .ifPresent(
162   - t -> {
163   - t.forEach(
164   - condition -> {
165   - ScopeEnum entityType = condition.getEntityType();
166   - List<String> conditionDevices = condition.getEntityId();
167   - String tkProjectId = condition.getDeviceProfileId();
168   - if (ScopeEnum.ALL.equals(entityType)) {
169   - conditionDevices =
170   - ytDeviceService.findTbDeviceIdsByDeviceProfileId(
171   - tkProjectId, condition.getTenantId());
172   - }
173   - conditionMatched.set(
174   - !conditionDevices.stream()
175   - .anyMatch(
176   - id -> {
177   - TriggerState conditionState =
178   - getOrCreateConditionState(condition, tkProjectId, id);
179   - try {
180   - boolean fresh = false;
181   - if(msg.getOriginator().getId().toString().equals(id)){
182   - fresh=true;
183   - }
184   - boolean result = conditionState.process(ctx, msg,fresh);
185   - log.warn(String.format("执行器【%s】刷新【%s】结果【%s】执行器设备【%s】数据设备【%s】数据内容【%s】",condition.getId(),fresh,result,id,msg.getOriginator(),msg.getData()));
186   - return !result;
187   - } catch (ExecutionException e) {
188   - throw new RuntimeException(e);
189   - } catch (InterruptedException e) {
190   - throw new RuntimeException(e);
191   - }
192   - }));
193   - });
194   - });
  170 + .ifPresent(
  171 + all -> {
  172 + conditionMatched.set(
  173 + all.stream()
  174 + .allMatch(
  175 + condition -> {
  176 + ScopeEnum entityType = condition.getEntityType();
  177 + List<String> conditionDevices = condition.getEntityId();
  178 + String tkProjectId = condition.getDeviceProfileId();
  179 + if (ScopeEnum.ALL.equals(entityType)) {
  180 + conditionDevices =
  181 + ytDeviceService.findTbDeviceIdsByDeviceProfileId(
  182 + tkProjectId, condition.getTenantId());
  183 + }
  184 +
  185 + return conditionDevices.stream()
  186 + .allMatch(
  187 + devId -> {
  188 + TriggerState conditionState =
  189 + getOrCreateConditionState(condition, tkProjectId, devId);
  190 + try {
  191 + boolean fresh = false;
  192 + if (msg.getOriginator().getId().toString().equals(devId)) {
  193 + fresh = true;
  194 + }
  195 + ObjectNode result = conditionState.process(ctx, msg, fresh);
  196 + log.warn(
  197 + String.format(
  198 + "执行器【%s】刷新【%s】结果【%s】执行器设备【%s】数据设备【%s】数据内容【%s】",
  199 + condition.getId(),
  200 + fresh,
  201 + result,
  202 + devId,
  203 + msg.getOriginator(),
  204 + msg.getData()));
  205 + if (!result.isEmpty()) {
  206 + detail.set(FastIotConstants.Alarm.CONDITION,result);
  207 + return true;
  208 + }
  209 + } catch (ExecutionException e) {
  210 + throw new RuntimeException(e);
  211 + } catch (InterruptedException e) {
  212 + throw new RuntimeException(e);
  213 + }
  214 + return false;
  215 + });
  216 + }));
  217 + });
195 218
196 219 if (triggerMatched.get() && conditionMatched.get()) {
197   - log.error(String.format("设备【%s】的消息内容【%s】触发动作",deviceId,msg.getData()));
  220 + log.error(String.format("设备【%s】的消息内容【%s】触发动作", deviceId, msg.getData()));
198 221 for (TkDoActionEntity item : actions) {
199 222 if (ActionTypeEnum.MSG_NOTIFY.equals(item.getOutTarget())) {
200   - noticeMsg(ctx, msg, item, deviceId, detail.toString(), msg.getTs());
  223 + noticeMsg(ctx, msg, item, deviceId, detail, msg.getTs());
201 224 } else {
202   - pushMsg(ctx, msg, item, detail.toString());
  225 + pushMsg(ctx, msg, item);
203 226 }
204 227 }
205 228 } else {
... ... @@ -226,7 +249,7 @@ class ReactState {
226 249 || (trigger.getEntityType().equals(ScopeEnum.ALL)
227 250 && trigger.getDeviceProfileId().equals(profileId))) {
228 251 TriggerState state = createTriggerState(deviceId, trigger.getTriggerCondition());
229   - log.error(String.format("新建设备【%s】的触发器",deviceId));
  252 + log.error(String.format("新建设备【%s】的触发器", deviceId));
230 253 triggerState.put(cacheKey, state);
231 254 return state;
232 255 }
... ... @@ -290,12 +313,12 @@ class ReactState {
290 313 for (AlarmConditionFilter filter : rule.getCondition().getCondition()) {
291 314 filterKeys.add(filter.getKey());
292 315 }
293   - TriggerState state = new TriggerState(deviceId, rule, filterKeys, rule.getAlarmDetails(), null);
  316 + TriggerState state = new TriggerState(deviceId, rule, filterKeys, null);
294 317
295 318 return state;
296 319 }
297 320
298   - private void pushMsg(TbContext ctx, TbMsg msg, TkDoActionEntity action, String detail) {
  321 + private void pushMsg(TbContext ctx, TbMsg msg, TkDoActionEntity action) {
299 322 switch (action.getOutTarget()) {
300 323 case DEVICE_OUT:
301 324 List<String> rpcDevices = action.getDeviceId();
... ... @@ -354,7 +377,7 @@ class ReactState {
354 377 TbMsg msg,
355 378 TkDoActionEntity action,
356 379 String deviceId,
357   - String detailStr,
  380 + ObjectNode detail,
358 381 long startTs) {
359 382
360 383 DeviceId entityId = new DeviceId(UUID.fromString(deviceId));
... ... @@ -370,12 +393,7 @@ class ReactState {
370 393 }
371 394 currentAlarm.setStartTs(startTs);
372 395 currentAlarm.setEndTs(currentAlarm.getStartTs());
373   - ObjectNode detailData = JacksonUtil.newObjectNode();
374   - if (StringUtils.isNotEmpty(detailStr)) {
375   - detailData.put("msg", detailStr);
376   - }
377   - detailData.put("data", JacksonUtil.toJsonNode(msg.getData()));
378   - currentAlarm.setDetails(detailData);
  396 + currentAlarm.setDetails(detail);
379 397 currentAlarm.setOriginator(entityId);
380 398 currentAlarm.setTenantId(ctx.getTenantId());
381 399 currentAlarm.setPropagate(false);
... ... @@ -393,7 +411,7 @@ class ReactState {
393 411
394 412 AlarmInfoDTO formData = new AlarmInfoDTO();
395 413 formData.setDeviceName(msg.getMetaData().getData().get("deviceName"));
396   - formData.setDetails(JacksonUtil.toString(detailData));
  414 + formData.setDetails(JacksonUtil.toString(detail));
397 415 formData.setType(currentAlarm.getType());
398 416 formData.setCreateTs(currentAlarm.getCreatedTime());
399 417 formData.setStartTs(currentAlarm.getStartTs());
... ... @@ -408,16 +426,19 @@ class ReactState {
408 426 private void clearAlarm(TbContext ctx, TbMsg msg, String deviceId, String key)
409 427 throws ExecutionException, InterruptedException {
410 428 TriggerState clearState = getOrCreateClearState(deviceId, key);
411   - if (clearState != null && clearState.process(ctx, msg,true)) {
412   - ctx.getAlarmService()
413   - .clearAlarmForResult(
414   - ctx.getTenantId(),
415   - currentAlarms.get(deviceId).getId(),
416   - null,
417   - System.currentTimeMillis());
418   - ytDeviceService.freshAlarmStatus(new DeviceId(UUID.fromString(deviceId)), 0);
419   - alarmMsg(ctx, msg, currentAlarms.get(deviceId), "Alarm Cleared");
420   - currentAlarms.remove(deviceId);
  429 + if(clearState !=null){
  430 + ObjectNode clearResult = clearState.process(ctx, msg, true);
  431 + if(!clearResult.isEmpty()){
  432 + ctx.getAlarmService()
  433 + .clearAlarmForResult(
  434 + ctx.getTenantId(),
  435 + currentAlarms.get(deviceId).getId(),
  436 + clearResult,
  437 + System.currentTimeMillis());
  438 + ytDeviceService.freshAlarmStatus(new DeviceId(UUID.fromString(deviceId)), 0);
  439 + alarmMsg(ctx, msg, currentAlarms.get(deviceId), "Alarm Cleared");
  440 + currentAlarms.remove(deviceId);
  441 + }
421 442 }
422 443 }
423 444
... ...
... ... @@ -13,6 +13,7 @@
13 13 */
14 14 package org.thingsboard.rule.engine.yunteng.scene;
15 15
  16 +import com.fasterxml.jackson.databind.node.ObjectNode;
16 17 import com.google.gson.JsonParser;
17 18 import java.util.*;
18 19 import java.util.concurrent.ExecutionException;
... ... @@ -32,6 +33,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
32 33 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
33 34 import org.thingsboard.server.common.data.kv.KvEntry;
34 35 import org.thingsboard.server.common.data.kv.TsKvEntry;
  36 +import org.thingsboard.server.common.data.yunteng.utils.JacksonUtil;
35 37 import org.thingsboard.server.common.msg.TbMsg;
36 38 import org.thingsboard.server.common.msg.TbMsgMetaData;
37 39 import org.thingsboard.server.common.msg.session.SessionMsgType;
... ... @@ -50,18 +52,14 @@ class TriggerState {
50 52 private final DynamicPredicateValueCtx dynamicPredicateValueCtx;
51 53 private DataSnapshot latestValues;
52 54
  55 + /**触发器的遥测指标*/
53 56 private final Set<AlarmConditionFilterKey> entityKeys;
54   - private final String alarmDetails;
55 57
56   - @Getter
57   - /** 触发条件执行结果 */
58   - private boolean ruleMatched = false;
59 58
60 59 TriggerState(
61 60 String originator,
62 61 AlarmRule rule,
63 62 Set<AlarmConditionFilterKey> filterKeys,
64   - String alarmDetails,
65 63 DynamicPredicateValueCtx dynamicPredicateValueCtx) {
66 64
67 65 this.originator = originator;
... ... @@ -70,12 +68,12 @@ class TriggerState {
70 68 new TriggerRuleState(
71 69 rule.getCondition(), filterKeys, new PersistedAlarmRuleState(), rule.getSchedule());
72 70 this.entityKeys = filterKeys;
73   - this.alarmDetails = alarmDetails;
74 71 }
75 72
76   - public boolean process(TbContext ctx, TbMsg msg,boolean needFresh) throws ExecutionException, InterruptedException {
  73 + public ObjectNode process(TbContext ctx, TbMsg msg, boolean needFresh)
  74 + throws ExecutionException, InterruptedException {
77 75 if (!needFresh) {
78   - return ruleMatched;
  76 + return ruleState.getDetailInform();
79 77 }
80 78 if (latestValues == null) {
81 79 latestValues = fetchLatestValues(ctx, originator);
... ... @@ -89,35 +87,33 @@ class TriggerState {
89 87 }
90 88
91 89 if (update != null && update.hasUpdate()) {
92   - ruleMatched = createOrClearAlarms(ctx, msg, latestValues, update, TriggerRuleState::eval);
93   - return ruleMatched;
  90 + return createOrClearAlarms(ctx, msg, latestValues, update, TriggerRuleState::eval);
94 91 }
95   - return false;
  92 + return JacksonUtil.newObjectNode();
96 93 }
97 94
98   - public boolean process(TbContext ctx, long ts) throws ExecutionException, InterruptedException {
  95 + public ObjectNode process(TbContext ctx, long ts) throws ExecutionException, InterruptedException {
99 96 return createOrClearAlarms(
100 97 ctx, null, ts, null, (alarmState, tsParam) -> alarmState.eval(tsParam, latestValues));
101 98 }
102 99
103   - public <T> boolean createOrClearAlarms(
  100 + public <T> ObjectNode createOrClearAlarms(
104 101 TbContext ctx,
105 102 TbMsg msg,
106 103 T data,
107 104 SnapshotUpdate update,
108 105 BiFunction<TriggerRuleState, T, AlarmEvalResult> evalFunction) {
109   - boolean stateUpdate = false;
110 106 if (!validateUpdate(update, ruleState)) {
111   - return false;
  107 + return JacksonUtil.newObjectNode();
112 108 }
  109 +// ruleState.getCondition().
113 110 AlarmEvalResult evalResult = evalFunction.apply(ruleState, data);
114 111 if (AlarmEvalResult.TRUE.equals(evalResult)) {
115   - stateUpdate = true;
116   - clearAlarmState(stateUpdate, ruleState);
  112 + clearAlarmState(true, ruleState);
117 113 } else if (AlarmEvalResult.FALSE.equals(evalResult)) {
118   - clearAlarmState(stateUpdate, ruleState);
  114 + clearAlarmState(false, ruleState);
119 115 }
120   - return stateUpdate;
  116 + return ruleState.getDetailInform();
121 117 }
122 118
123 119 public boolean clearAlarmState(boolean stateUpdate, TriggerRuleState state) {
... ... @@ -128,6 +124,12 @@ class TriggerState {
128 124 return stateUpdate;
129 125 }
130 126
  127 + /**
  128 + * 快照中有更新的遥测指标是否包含在触发器中
  129 + * @param update 遥测数据的快照
  130 + * @param state 触发器状态
  131 + * @return
  132 + */
131 133 public boolean validateUpdate(SnapshotUpdate update, TriggerRuleState state) {
132 134 if (update != null) {
133 135 // Check that the update type and that keys match.
... ...
... ... @@ -3,7 +3,9 @@
3 3 */
4 4 package org.thingsboard.rule.engine.yunteng.utils;
5 5
  6 +import com.fasterxml.jackson.databind.node.ObjectNode;
6 7 import lombok.Data;
  8 +import lombok.Getter;
7 9 import lombok.extern.slf4j.Slf4j;
8 10 import org.thingsboard.rule.engine.profile.AlarmEvalResult;
9 11 import org.thingsboard.rule.engine.profile.DataSnapshot;
... ... @@ -12,6 +14,8 @@ import org.thingsboard.rule.engine.profile.EntityKeyValue;
12 14 import org.thingsboard.rule.engine.profile.state.PersistedAlarmRuleState;
13 15 import org.thingsboard.server.common.data.device.profile.*;
14 16 import org.thingsboard.server.common.data.query.*;
  17 +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
  18 +import org.thingsboard.server.common.data.yunteng.utils.JacksonUtil;
15 19 import org.thingsboard.server.common.msg.tools.SchedulerUtils;
16 20
17 21 import java.time.Instant;
... ... @@ -33,6 +37,8 @@ public class TriggerRuleState {
33 37 private final DynamicPredicateValueCtx dynamicPredicateValueCtx =null;
34 38
35 39
  40 + /**告警触发详情*/
  41 + private ObjectNode detailInform = null;
36 42
37 43
38 44
... ... @@ -46,6 +52,7 @@ public class TriggerRuleState {
46 52 this.state = new PersistedAlarmRuleState(0L, 0L, 0L);
47 53 }
48 54 this.spec = getSpec(condition);
  55 + this.detailInform = JacksonUtil.newObjectNode();
49 56 }
50 57
51 58
... ... @@ -279,7 +286,11 @@ public class TriggerRuleState {
279 286 if (value == null) {
280 287 return false;
281 288 }
  289 + detailInform.put(FastIotConstants.Alarm.KEY,filter.getKey().getKey());
282 290 eval = eval && eval(data, value, filter.getPredicate(), filter);
  291 + if(!eval){
  292 + detailInform.removeAll();
  293 + }
283 294 }
284 295 return eval;
285 296 }
... ... @@ -320,6 +331,8 @@ public class TriggerRuleState {
320 331 }
321 332
322 333 private boolean evalComplexPredicate(DataSnapshot data, EntityKeyValue ekv, ComplexFilterPredicate predicate, AlarmConditionFilter filter) {
  334 + detailInform.put(FastIotConstants.Alarm.VALUE,ekv.getDataType().toString());
  335 + detailInform.put(FastIotConstants.Alarm.PREDICATE,predicate.getOperation().toString());
323 336 switch (predicate.getOperation()) {
324 337 case OR:
325 338 for (KeyFilterPredicate kfp : predicate.getPredicates()) {
... ... @@ -349,6 +362,8 @@ public class TriggerRuleState {
349 362 if (predicateValue == null) {
350 363 return false;
351 364 }
  365 + detailInform.put(FastIotConstants.Alarm.VALUE,predicateValue);
  366 + detailInform.put(FastIotConstants.Alarm.PREDICATE,predicate.getOperation().toString());
352 367 switch (predicate.getOperation()) {
353 368 case EQUAL:
354 369 return val.equals(predicateValue);
... ... @@ -368,6 +383,8 @@ public class TriggerRuleState {
368 383 if (predicateValue == null) {
369 384 return false;
370 385 }
  386 + detailInform.put(FastIotConstants.Alarm.VALUE,predicateValue);
  387 + detailInform.put(FastIotConstants.Alarm.PREDICATE,predicate.getOperation().toString());
371 388 switch (predicate.getOperation()) {
372 389 case NOT_EQUAL:
373 390 return !val.equals(predicateValue);
... ... @@ -399,6 +416,8 @@ public class TriggerRuleState {
399 416 val = val.toLowerCase();
400 417 predicateValue = predicateValue.toLowerCase();
401 418 }
  419 + detailInform.put(FastIotConstants.Alarm.VALUE,predicateValue);
  420 + detailInform.put(FastIotConstants.Alarm.PREDICATE,predicate.getOperation().toString());
402 421 switch (predicate.getOperation()) {
403 422 case CONTAINS:
404 423 return val.contains(predicateValue);
... ...