@ -16,6 +16,7 @@
package org.thingsboard.rule.engine.profile ;
import com.fasterxml.jackson.databind.JsonNode ;
import com.fasterxml.jackson.databind.node.ObjectNode ;
import lombok.Data ;
import lombok.extern.slf4j.Slf4j ;
import org.thingsboard.rule.engine.action.TbAlarmResult ;
@ -29,6 +30,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm ;
import org.thingsboard.server.common.data.id.EntityId ;
import org.thingsboard.server.common.data.query.EntityKeyType ;
import org.thingsboard.server.common.data.query.KeyFilter ;
import org.thingsboard.server.common.msg.TbMsg ;
import org.thingsboard.server.common.msg.TbMsgMetaData ;
import org.thingsboard.server.common.msg.queue.ServiceQueue ;
@ -53,6 +55,7 @@ class AlarmState {
private volatile boolean initialFetchDone ;
private volatile TbMsgMetaData lastMsgMetaData ;
private volatile String lastMsgQueueName ;
private volatile DataSnapshot dataSnapshot ;
AlarmState ( ProfileState deviceProfile , EntityId originator , DeviceProfileAlarm alarmDefinition , PersistedAlarmState alarmState ) {
this . deviceProfile = deviceProfile ;
@ -74,7 +77,7 @@ class AlarmState {
public < T > boolean createOrClearAlarms ( TbContext ctx , T data , SnapshotUpdate update , BiFunction < AlarmRuleState , T , AlarmEvalResult > evalFunction ) {
boolean stateUpdate = false ;
AlarmSeverity resultSeverity = null ;
AlarmRuleState resultState = null ;
log . debug ( "[{}] processing update: {}" , alarmDefinition . getId ( ) , data ) ;
for ( AlarmRuleState state : createRulesSortedBySeverityDesc ) {
if ( ! validateUpdate ( update , state ) ) {
@ -84,15 +87,15 @@ class AlarmState {
AlarmEvalResult evalResult = evalFunction . apply ( state , data ) ;
stateUpdate | = state . checkUpdate ( ) ;
if ( AlarmEvalResult . TRUE . equals ( evalResult ) ) {
resultSeverity = state . getSeverity ( ) ;
resultState = state ;
break ;
} else if ( AlarmEvalResult . FALSE . equals ( evalResult ) ) {
state . clear ( ) ;
stateUpdate | = state . checkUpdate ( ) ;
}
}
if ( resultSeverity ! = null ) {
TbAlarmResult result = calculateAlarmResult ( ctx , resultSeverity ) ;
if ( resultState ! = null ) {
TbAlarmResult result = calculateAlarmResult ( ctx , resultState ) ;
if ( result ! = null ) {
pushMsg ( ctx , result ) ;
}
@ -187,7 +190,8 @@ class AlarmState {
}
}
private TbAlarmResult calculateAlarmResult ( TbContext ctx , AlarmSeverity severity ) {
private < T > TbAlarmResult calculateAlarmResult ( TbContext ctx , AlarmRuleState ruleState ) {
AlarmSeverity severity = ruleState . getSeverity ( ) ;
if ( currentAlarm ! = null ) {
// TODO: In some extremely rare cases, we might miss the event of alarm clear (If one use in-mem queue and restarted the server) or (if one manipulated the rule chain).
// Maybe we should fetch alarm every time?
@ -213,7 +217,7 @@ class AlarmState {
currentAlarm . setSeverity ( severity ) ;
currentAlarm . setStartTs ( System . currentTimeMillis ( ) ) ;
currentAlarm . setEndTs ( currentAlarm . getStartTs ( ) ) ;
currentAlarm . setDetails ( JacksonUtil . OBJECT_MAPPER . createObjectNode ( ) ) ;
currentAlarm . setDetails ( createDetails ( ruleState ) ) ;
currentAlarm . setOriginator ( originator ) ;
currentAlarm . setTenantId ( ctx . getTenantId ( ) ) ;
currentAlarm . setPropagate ( alarmDefinition . isPropagate ( ) ) ;
@ -226,6 +230,44 @@ class AlarmState {
}
}
private < T > JsonNode createDetails ( AlarmRuleState ruleState ) {
ObjectNode details = JacksonUtil . OBJECT_MAPPER . createObjectNode ( ) ;
String alarmDetails = ruleState . getAlarmRule ( ) . getAlarmDetails ( ) ;
if ( alarmDetails ! = null ) {
for ( KeyFilter keyFilter : ruleState . getAlarmRule ( ) . getCondition ( ) . getCondition ( ) ) {
EntityKeyValue entityKeyValue = dataSnapshot . getValue ( keyFilter . getKey ( ) ) ;
alarmDetails = alarmDetails . replaceAll ( String . format ( "\\$\\{%s}" , keyFilter . getKey ( ) . getKey ( ) ) , getValueAsString ( entityKeyValue ) ) ;
}
details . put ( "data" , alarmDetails ) ;
}
return details ;
}
private static String getValueAsString ( EntityKeyValue entityKeyValue ) {
Object result = null ;
switch ( entityKeyValue . getDataType ( ) ) {
case STRING :
result = entityKeyValue . getStrValue ( ) ;
break ;
case JSON :
result = entityKeyValue . getJsonValue ( ) ;
break ;
case LONG :
result = entityKeyValue . getLngValue ( ) ;
break ;
case DOUBLE :
result = entityKeyValue . getDblValue ( ) ;
break ;
case BOOLEAN :
result = entityKeyValue . getBoolValue ( ) ;
break ;
}
return String . valueOf ( result ) ;
}
public boolean processAlarmClear ( TbContext ctx , Alarm alarmNf ) {
boolean updated = false ;
if ( currentAlarm ! = null & & currentAlarm . getId ( ) . equals ( alarmNf . getId ( ) ) ) {