Browse Source

Add repeating alarm condition support for Alarm rules CF

pull/14107/head
VIacheslavKlimov 1 year ago
parent
commit
1a66f3973e
  1. 24
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java
  2. 17
      application/src/main/java/org/thingsboard/server/service/cf/AlarmCalculatedFieldResult.java
  3. 15
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java
  4. 9
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java
  5. 4
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java
  6. 4
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldState.java
  7. 4
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java
  8. 64
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java
  9. 18
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmRuleState.java
  10. 19
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingCalculatedFieldState.java
  11. 140
      application/src/test/java/org/thingsboard/server/cf/AlarmRulesTest.java
  12. 25
      application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingCalculatedFieldStateTest.java
  13. 7
      application/src/test/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldStateTest.java
  14. 11
      application/src/test/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldStateTest.java
  15. 2
      application/src/test/java/org/thingsboard/server/utils/CalculatedFieldUtilsTest.java
  16. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/DurationAlarmCondition.java
  17. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/RepeatingAlarmCondition.java
  18. 20
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmResult.java

24
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java

@ -122,7 +122,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
}
public void process(EntityInitCalculatedFieldMsg msg) throws CalculatedFieldException {
log.debug("[{}] Processing entity init CF msg.", msg.getCtx().getCfId());
log.debug("[{}] Processing entity init CF msg: {}", msg.getCtx().getCfId(), msg);
var ctx = msg.getCtx();
CalculatedFieldState state;
if (msg.getStateAction() == StateAction.RECREATE) {
@ -142,11 +142,12 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
state.init(ctx);
}
if (state.isSizeOk()) {
processStateIfReady(ctx, Collections.singletonList(ctx.getCfId()), state, null, null, msg.getCallback());
processStateIfReady(ctx, Collections.emptyMap(), Collections.singletonList(ctx.getCfId()), state, null, null, msg.getCallback());
} else {
throw new RuntimeException(ctx.getSizeExceedsLimitMessage());
}
} catch (Exception e) {
log.debug("[{}][{}] Failed to initialize CF state", entityId, ctx.getCfId(), e);
if (e instanceof CalculatedFieldException cfe) {
throw cfe;
}
@ -176,7 +177,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
}
public void process(EntityCalculatedFieldTelemetryMsg msg) throws CalculatedFieldException {
log.debug("[{}] Processing CF telemetry msg.", msg.getEntityId());
log.trace("[{}] Processing CF telemetry msg: {}", msg.getEntityId(), msg);
var proto = msg.getProto();
var numberOfCallbacks = CALLBACKS_PER_CF * (msg.getEntityIdFields().size() + msg.getProfileIdFields().size());
MultipleTbCallback callback = new MultipleTbCallback(numberOfCallbacks, msg.getCallback());
@ -191,7 +192,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
}
public void process(EntityCalculatedFieldLinkedTelemetryMsg msg) throws CalculatedFieldException {
log.debug("[{}] Processing CF link telemetry msg.", msg.getEntityId());
log.trace("[{}] Processing CF link telemetry msg: {}", msg.getEntityId(), msg);
var proto = msg.getProto();
var ctx = msg.getCtx();
var callback = new MultipleTbCallback(CALLBACKS_PER_CF, msg.getCallback());
@ -213,6 +214,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
}
}
} catch (Exception e) {
log.debug("[{}][{}] Failed to process linked CF telemetry msg: {}", entityId, ctx.getCfId(), msg, e);
throw CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).cause(e).build();
}
}
@ -235,6 +237,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
}
}
} catch (Exception e) {
log.debug("[{}][{}] Failed to process CF telemetry msg: {}", entityId, ctx.getCfId(), proto, e);
if (e instanceof CalculatedFieldException cfe) {
throw cfe;
}
@ -305,10 +308,11 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
}
}
if (state.isSizeOk()) {
if (state.update(ctx, newArgValues) || justRestored) {
Map<String, ArgumentEntry> updatedArgs = state.update(newArgValues, ctx);
if (!updatedArgs.isEmpty() || justRestored) {
cfIdList = new ArrayList<>(cfIdList);
cfIdList.add(ctx.getCfId());
processStateIfReady(ctx, cfIdList, state, tbMsgId, tbMsgType, callback);
processStateIfReady(ctx, updatedArgs, cfIdList, state, tbMsgId, tbMsgType, callback);
} else {
callback.onSuccess(CALLBACKS_PER_CF);
}
@ -327,7 +331,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
state.init(ctx);
Map<String, ArgumentEntry> arguments = fetchArguments(ctx);
state.update(ctx, arguments);
state.update(arguments, ctx);
state.checkStateSize(new CalculatedFieldEntityCtxId(tenantId, ctx.getCfId(), entityId), ctx.getMaxStateSize());
states.put(ctx.getCfId(), state);
@ -343,12 +347,14 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
return argumentsFuture.get(1, TimeUnit.MINUTES);
}
private void processStateIfReady(CalculatedFieldCtx ctx, List<CalculatedFieldId> cfIdList, CalculatedFieldState state, UUID tbMsgId, TbMsgType tbMsgType, TbCallback callback) throws CalculatedFieldException {
private void processStateIfReady(CalculatedFieldCtx ctx, Map<String, ArgumentEntry> updatedArgs, List<CalculatedFieldId> cfIdList, CalculatedFieldState state, UUID tbMsgId, TbMsgType tbMsgType, TbCallback callback) throws CalculatedFieldException {
log.trace("[{}][{}] Processing state if ready. Current args: {}, updated args: {}", entityId, ctx.getCfId(), state.getArguments(), updatedArgs);
CalculatedFieldEntityCtxId ctxId = new CalculatedFieldEntityCtxId(tenantId, ctx.getCfId(), entityId);
boolean stateSizeChecked = false;
try {
if (ctx.isInitialized() && state.isReady()) {
CalculatedFieldResult calculationResult = state.performCalculation(ctx).get(systemContext.getCfCalculationResultTimeout(), TimeUnit.SECONDS);
log.trace("[{}][{}] Performing calculation. Updated args: {}", entityId, ctx.getCfId(), updatedArgs);
CalculatedFieldResult calculationResult = state.performCalculation(updatedArgs, ctx).get(systemContext.getCfCalculationResultTimeout(), TimeUnit.SECONDS);
state.checkStateSize(ctxId, ctx.getMaxStateSize());
stateSizeChecked = true;
if (state.isSizeOk()) {

17
application/src/main/java/org/thingsboard/server/service/cf/AlarmCalculatedFieldResult.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.cf;
import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.action.TbAlarmResult;
import org.thingsboard.server.common.data.DataConstants;
@ -25,16 +26,15 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.service.cf.ctx.state.alarm.AlarmRuleState;
import java.util.List;
@Data
@Builder
@RequiredArgsConstructor
public class AlarmCalculatedFieldResult implements CalculatedFieldResult {
private final TbAlarmResult alarmResult;
private final AlarmRuleState alarmRuleState;
@Override
public TbMsg toTbMsg(EntityId entityId, List<CalculatedFieldId> cfIds) {
@ -49,14 +49,11 @@ public class AlarmCalculatedFieldResult implements CalculatedFieldResult {
} else {
metaData.putValue(DataConstants.IS_CLEARED_ALARM, Boolean.TRUE.toString());
}
switch (alarmRuleState.getCondition().getType()) {
case REPEATING -> {
metaData.putValue(DataConstants.ALARM_CONDITION_REPEATS, String.valueOf(alarmRuleState.getEventCount()));
}
case DURATION -> {
// TODO: schedule instead of duration
metaData.putValue(DataConstants.ALARM_CONDITION_DURATION, String.valueOf(alarmRuleState.getDuration()));
}
if (alarmResult.getConditionRepeats() != null) {
metaData.putValue(DataConstants.ALARM_CONDITION_REPEATS, String.valueOf(alarmResult.getConditionRepeats()));
}
if (alarmResult.getConditionDuration() != null) {
metaData.putValue(DataConstants.ALARM_CONDITION_DURATION, String.valueOf(alarmResult.getConditionDuration()));
}
return TbMsg.newMsg()

15
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
import org.thingsboard.server.utils.CalculatedFieldUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -44,8 +45,8 @@ public abstract class BaseCalculatedFieldState implements CalculatedFieldState {
}
@Override
public boolean update(CalculatedFieldCtx ctx, Map<String, ArgumentEntry> argumentValues) {
boolean stateUpdated = false;
public Map<String, ArgumentEntry> update(Map<String, ArgumentEntry> argumentValues, CalculatedFieldCtx ctx) {
Map<String, ArgumentEntry> updatedArguments = null;
for (Map.Entry<String, ArgumentEntry> entry : argumentValues.entrySet()) {
String key = entry.getKey();
@ -65,13 +66,19 @@ public abstract class BaseCalculatedFieldState implements CalculatedFieldState {
}
if (entryUpdated) {
stateUpdated = true;
if (updatedArguments == null) {
updatedArguments = new HashMap<>(argumentValues.size());
}
updatedArguments.put(key, newEntry);
updateLastUpdateTimestamp(newEntry);
}
}
return stateUpdated;
if (updatedArguments == null) {
updatedArguments = Collections.emptyMap();
}
return updatedArguments;
}
@Override

9
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java

@ -455,4 +455,13 @@ public class CalculatedFieldCtx {
return "Failed to init CF state. State size exceeds limit of " + (maxStateSize / 1024) + "Kb!";
}
@Override
public String toString() {
return "CalculatedFieldCtx{" +
"cfId=" + cfId +
", cfType=" + cfType +
", entityId=" + entityId +
'}';
}
}

4
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java

@ -56,11 +56,11 @@ public interface CalculatedFieldState {
void init(CalculatedFieldCtx ctx);
boolean update(CalculatedFieldCtx ctx, Map<String, ArgumentEntry> arguments);
Map<String, ArgumentEntry> update(Map<String, ArgumentEntry> arguments, CalculatedFieldCtx ctx);
void reset(CalculatedFieldCtx ctx);
ListenableFuture<CalculatedFieldResult> performCalculation(CalculatedFieldCtx ctx);
ListenableFuture<CalculatedFieldResult> performCalculation(Map<String, ArgumentEntry> updatedArgs, CalculatedFieldCtx ctx);
@JsonIgnore
boolean isReady();

4
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldState.java

@ -27,6 +27,8 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.service.cf.CalculatedFieldResult;
import org.thingsboard.server.service.cf.TelemetryCalculatedFieldResult;
import java.util.Map;
@Slf4j
@EqualsAndHashCode(callSuper = true)
public class ScriptCalculatedFieldState extends BaseCalculatedFieldState {
@ -41,7 +43,7 @@ public class ScriptCalculatedFieldState extends BaseCalculatedFieldState {
}
@Override
public ListenableFuture<CalculatedFieldResult> performCalculation(CalculatedFieldCtx ctx) {
public ListenableFuture<CalculatedFieldResult> performCalculation(Map<String, ArgumentEntry> updatedArgs, CalculatedFieldCtx ctx) {
ListenableFuture<Object> resultFuture = ctx.evaluateTbelExpression(ctx.getExpression(), this);
Output output = ctx.getOutput();
return Futures.transform(resultFuture,

4
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java

@ -28,6 +28,8 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.service.cf.CalculatedFieldResult;
import org.thingsboard.server.service.cf.TelemetryCalculatedFieldResult;
import java.util.Map;
@EqualsAndHashCode(callSuper = true)
public class SimpleCalculatedFieldState extends BaseCalculatedFieldState {
@ -48,7 +50,7 @@ public class SimpleCalculatedFieldState extends BaseCalculatedFieldState {
}
@Override
public ListenableFuture<CalculatedFieldResult> performCalculation(CalculatedFieldCtx ctx) {
public ListenableFuture<CalculatedFieldResult> performCalculation(Map<String, ArgumentEntry> updatedArgs, CalculatedFieldCtx ctx) {
double expressionResult = ctx.evaluateSimpleExpression(ctx.getExpression(), this);
Output output = ctx.getOutput();

64
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java

@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.alarm.rule.AlarmRule;
import org.thingsboard.server.common.data.alarm.rule.condition.AlarmConditionType;
import org.thingsboard.server.common.data.alarm.rule.condition.expression.AlarmConditionExpression;
import org.thingsboard.server.common.data.alarm.rule.condition.expression.TbelAlarmConditionExpression;
import org.thingsboard.server.common.data.audit.ActionType;
@ -104,23 +105,36 @@ public class AlarmCalculatedFieldState extends BaseCalculatedFieldState {
log.debug("Initialized create rule states {} and clear rule state {} for {}", createRuleStates, clearRuleState, ctx.getCalculatedField());
}
@Override
public Map<String, ArgumentEntry> update(Map<String, ArgumentEntry> argumentValues, CalculatedFieldCtx ctx) {
return super.update(argumentValues, ctx);
}
@Override
public void reset(CalculatedFieldCtx ctx) {
super.reset(ctx);
}
@Override
public ListenableFuture<CalculatedFieldResult> performCalculation(CalculatedFieldCtx ctx) {
public ListenableFuture<CalculatedFieldResult> performCalculation(Map<String, ArgumentEntry> updatedArgs, CalculatedFieldCtx ctx) {
if (updatedArgs.isEmpty()) {
// FIXME: do we evaluate alarm rule (and increment event count) after arguments or expression change (state reinit)???
return Futures.immediateFuture(new AlarmCalculatedFieldResult(null));
}
initCurrentAlarm(ctx);
AlarmCalculatedFieldResult result = createOrClearAlarms(state -> state.eval(ctx), ctx);
return Futures.immediateFuture(result);
TbAlarmResult result = createOrClearAlarms(state -> state.eval(ctx), ctx);
return Futures.immediateFuture(AlarmCalculatedFieldResult.builder()
.alarmResult(result)
.build());
}
// TODO: harvesting
public ListenableFuture<CalculatedFieldResult> performCalculation(long ts, CalculatedFieldCtx ctx) {
public ListenableFuture<CalculatedFieldResult> performCalculation(Map<String, ArgumentEntry> updatedArgs, long ts, CalculatedFieldCtx ctx) {
initCurrentAlarm(ctx);
AlarmCalculatedFieldResult result = createOrClearAlarms(ruleState -> ruleState.eval(ts), ctx);
return Futures.immediateFuture(result);
TbAlarmResult result = createOrClearAlarms(ruleState -> ruleState.eval(ts), ctx);
return Futures.immediateFuture(AlarmCalculatedFieldResult.builder()
.alarmResult(result)
.build());
}
@SneakyThrows
@ -160,28 +174,33 @@ public class AlarmCalculatedFieldState extends BaseCalculatedFieldState {
createRuleStates.values().forEach(AlarmRuleState::clear);
}
public AlarmCalculatedFieldResult createOrClearAlarms(Function<AlarmRuleState, AlarmEvalResult> evalFunction, CalculatedFieldCtx ctx) {
private TbAlarmResult createOrClearAlarms(Function<AlarmRuleState, AlarmEvalResult> evalFunction,
CalculatedFieldCtx ctx) {
TbAlarmResult result = null;
AlarmRuleState resultState = null;
AlarmRuleState.StateInfo resultStateInfo = null;
for (AlarmRuleState state : createRuleStates.values()) {
AlarmEvalResult evalResult = evalFunction.apply(state);
log.debug("Evaluated create rule {} with args {}. Result: {}", state, arguments, evalResult);
if (AlarmEvalResult.TRUE.equals(evalResult)) {
if (evalResult == AlarmEvalResult.TRUE) {
resultState = state;
break;
} else if (AlarmEvalResult.FALSE.equals(evalResult)) {
} else if (evalResult == AlarmEvalResult.FALSE) {
clearAlarmState(state);
}
}
if (resultState != null) {
result = calculateAlarmResult(resultState, ctx);
resultStateInfo = resultState.getStateInfo();
log.debug("Alarm result for state {}: {}", resultState, result);
clearAlarmState(clearRuleState);
} else if (currentAlarm != null && clearRuleState != null) {
AlarmEvalResult evalResult = evalFunction.apply(clearRuleState);
log.debug("Evaluated clear rule {} with args {}. Result: {}", clearRuleState, arguments, evalResult);
if (AlarmEvalResult.TRUE.equals(evalResult)) {
if (evalResult == AlarmEvalResult.TRUE) {
resultStateInfo = clearRuleState.getStateInfo();
clearAlarmState(clearRuleState);
for (AlarmRuleState state : createRuleStates.values()) {
clearAlarmState(state);
@ -190,18 +209,23 @@ public class AlarmCalculatedFieldState extends BaseCalculatedFieldState {
ctx.getTenantId(), currentAlarm.getId(), System.currentTimeMillis(), createDetails(clearRuleState), true
);
if (clearResult.isCleared()) {
result = new TbAlarmResult(false, false, true, clearResult.getAlarm());
result = TbAlarmResult.builder()
.isCleared(true)
.alarm(clearResult.getAlarm())
.build();
addStateInfo(result, clearRuleState);
resultState = clearRuleState;
}
currentAlarm = null;
} else if (AlarmEvalResult.FALSE.equals(evalResult)) {
} else if (evalResult == AlarmEvalResult.FALSE) {
clearAlarmState(clearRuleState);
}
}
return AlarmCalculatedFieldResult.builder()
.alarmResult(result)
.alarmRuleState(resultState)
.build();
if (result != null && resultState != null) {
result.setConditionRepeats(resultStateInfo.eventCount());
result.setConditionDuration(resultStateInfo.duration());
}
return result;
}
private void clearAlarmState(AlarmRuleState state) {
@ -265,6 +289,14 @@ public class AlarmCalculatedFieldState extends BaseCalculatedFieldState {
}
}
private void addStateInfo(TbAlarmResult alarmResult, AlarmRuleState ruleState) {
if (ruleState.getCondition().getType() == AlarmConditionType.REPEATING) {
alarmResult.setConditionRepeats(ruleState.getEventCount());
} else if (ruleState.getCondition().getType() == AlarmConditionType.DURATION) {
alarmResult.setConditionDuration(ruleState.getDuration());
}
}
private JsonNode createDetails(AlarmRuleState ruleState) {
JsonNode alarmDetails;
String alarmDetailsStr = ruleState.getAlarmRule().getAlarmDetails();

18
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmRuleState.java

@ -22,6 +22,7 @@ import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.rule.AlarmRule;
import org.thingsboard.server.common.data.alarm.rule.condition.AlarmCondition;
import org.thingsboard.server.common.data.alarm.rule.condition.AlarmConditionType;
import org.thingsboard.server.common.data.alarm.rule.condition.AlarmConditionValue;
import org.thingsboard.server.common.data.alarm.rule.condition.DurationAlarmCondition;
import org.thingsboard.server.common.data.alarm.rule.condition.RepeatingAlarmCondition;
@ -194,6 +195,8 @@ public class AlarmRuleState {
}
private long getRequiredDurationInMs() {
// fixme timeUnit??
return getValue(((DurationAlarmCondition) condition).getValue(), KvUtil::getLongValue);
}
@ -219,6 +222,16 @@ public class AlarmRuleState {
this.condition = alarmRule.getCondition();
}
public StateInfo getStateInfo() {
if (condition.getType() == AlarmConditionType.REPEATING) {
return new StateInfo(eventCount, null);
} else if (condition.getType() == AlarmConditionType.DURATION) {
return new StateInfo(null, duration);
} else {
return StateInfo.EMPTY;
}
}
@Override
public String toString() {
return "AlarmRuleState{" +
@ -230,4 +243,9 @@ public class AlarmRuleState {
'}';
}
public record StateInfo(Long eventCount, Long duration) {
static final StateInfo EMPTY = new StateInfo(null, null);
}
}

19
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingCalculatedFieldState.java

@ -41,6 +41,8 @@ import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
import org.thingsboard.server.service.cf.ctx.state.SingleValueArgumentEntry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@ -68,8 +70,8 @@ public class GeofencingCalculatedFieldState extends BaseCalculatedFieldState {
}
@Override
public boolean update(CalculatedFieldCtx ctx, Map<String, ArgumentEntry> argumentValues) {
boolean stateUpdated = false;
public Map<String, ArgumentEntry> update(Map<String, ArgumentEntry> argumentValues, CalculatedFieldCtx ctx) {
Map<String, ArgumentEntry> updatedArguments = null;
for (var entry : argumentValues.entrySet()) {
String key = entry.getKey();
@ -103,14 +105,21 @@ public class GeofencingCalculatedFieldState extends BaseCalculatedFieldState {
entryUpdated = existingEntry.updateEntry(newEntry);
}
if (entryUpdated) {
stateUpdated = true;
if (updatedArguments == null) {
updatedArguments = new HashMap<>(argumentValues.size());
}
updatedArguments.put(key, newEntry);
}
}
return stateUpdated;
if (updatedArguments == null) {
updatedArguments = Collections.emptyMap();
}
return updatedArguments;
}
@Override
public ListenableFuture<CalculatedFieldResult> performCalculation(CalculatedFieldCtx ctx) {
public ListenableFuture<CalculatedFieldResult> performCalculation(Map<String, ArgumentEntry> updatedArgs, CalculatedFieldCtx ctx) {
double latitude = (double) arguments.get(ENTITY_ID_LATITUDE_ARGUMENT_KEY).getValue();
double longitude = (double) arguments.get(ENTITY_ID_LONGITUDE_ARGUMENT_KEY).getValue();
Coordinates entityCoordinates = new Coordinates(latitude, longitude);

140
application/src/test/java/org/thingsboard/server/cf/AlarmRulesTest.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.cf;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Test;
@ -23,11 +24,15 @@ import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.action.TbAlarmResult;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.rule.AlarmRule;
import org.thingsboard.server.common.data.alarm.rule.condition.AlarmConditionValue;
import org.thingsboard.server.common.data.alarm.rule.condition.DurationAlarmCondition;
import org.thingsboard.server.common.data.alarm.rule.condition.RepeatingAlarmCondition;
import org.thingsboard.server.common.data.alarm.rule.condition.SimpleAlarmCondition;
import org.thingsboard.server.common.data.alarm.rule.condition.expression.TbelAlarmConditionExpression;
import org.thingsboard.server.common.data.cf.CalculatedField;
@ -56,6 +61,7 @@ import java.util.function.Consumer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
@Slf4j
@DaoSqlTest
public class AlarmRulesTest extends AbstractControllerTest {
@ -79,15 +85,17 @@ public class AlarmRulesTest extends AbstractControllerTest {
public void testCreateAndSeverityUpdateAndClear() throws Exception {
Argument temperatureArgument = new Argument();
temperatureArgument.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null));
temperatureArgument.setDefaultValue("0");
Map<String, Argument> arguments = Map.of(
"temperature", temperatureArgument
);
Map<AlarmSeverity, String> createRules = Map.of(
AlarmSeverity.MAJOR, "return temperature >= 50;",
AlarmSeverity.CRITICAL, "return temperature >= 100;"
Map<AlarmSeverity, Condition> createRules = Map.of(
AlarmSeverity.MAJOR, new Condition("return temperature >= 50;", null, null),
AlarmSeverity.CRITICAL, new Condition("return temperature >= 100;", null, null)
);
String clearRule = "return temperature <= 25;";
Condition clearRule = new Condition("return temperature <= 25;", null, null);
CalculatedField calculatedField = createAlarmCf(deviceId, "High Temperature Alarm",
arguments, createRules, clearRule);
@ -120,6 +128,71 @@ public class AlarmRulesTest extends AbstractControllerTest {
});
}
/*
* todo: state restore (event count)
* */
@Test
public void testCreateAlarmForRepeatingConditionOnTs() throws Exception {
Argument temperatureArgument = new Argument();
temperatureArgument.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null));
temperatureArgument.setDefaultValue("0");
Map<String, Argument> arguments = Map.of(
"temperature", temperatureArgument
);
int eventsCountMajor = 5;
int eventsCountCritical = 10;
Map<AlarmSeverity, Condition> createRules = Map.of(
AlarmSeverity.MAJOR, new Condition("return temperature >= 50;", eventsCountMajor, null),
AlarmSeverity.CRITICAL, new Condition("return temperature >= 50;", eventsCountCritical, null)
);
CalculatedField calculatedField = createAlarmCf(deviceId, "High Temperature Alarm",
arguments, createRules, null);
for (int i = 0; i < 4; i++) {
postTelemetry(deviceId, "{\"temperature\":50}");
Thread.sleep(10);
}
assertThat(getLatestAlarmResult(calculatedField.getId())).isNull();
postTelemetry(deviceId, "{\"temperature\":50}");
checkAlarmResult(calculatedField, alarmResult -> {
assertThat(alarmResult.isCreated()).isTrue();
assertThat(alarmResult.getAlarm().getSeverity()).isEqualTo(AlarmSeverity.MAJOR);
assertThat(alarmResult.getAlarm().getStatus()).isEqualTo(AlarmStatus.ACTIVE_UNACK);
assertThat(alarmResult.getConditionRepeats()).isEqualTo(5);
});
}
@Test
public void testCreateAlarmForRepeatingConditionOnAttribute() {
Argument temperatureArgument = new Argument();
temperatureArgument.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.ATTRIBUTE, AttributeScope.SHARED_SCOPE));
Map<String, Argument> arguments = Map.of(
"temperature", temperatureArgument
);
Map<AlarmSeverity, String> createRules = Map.of(
AlarmSeverity.MAJOR, "return temperature >= 50;",
AlarmSeverity.CRITICAL, "return temperature >= 100;"
);
String clearRule = "return temperature <= 25;";
// CalculatedField calculatedField = createAlarmCf(deviceId, "High Temperature Alarm",
// arguments, createRules, clearRule);
}
@Test
public void testCreateAlarmForDurationCondition() {
Argument temperatureArgument = new Argument();
temperatureArgument.setRefEntityKey(new ReferencedEntityKey("powerConsumption", ArgumentType.TS_LATEST, null));
Map<String, Argument> arguments = Map.of(
"powerConsumption", temperatureArgument
);
// CalculatedField calculatedField = createAlarmCf(deviceId, "High power consumption during 5 seconds",
// arguments, createRules, nu);
}
private void checkAlarmResult(CalculatedField calculatedField, Consumer<TbAlarmResult> assertion) {
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
TbAlarmResult alarmResult = getLatestAlarmResult(calculatedField.getId());
@ -152,34 +225,61 @@ public class AlarmRulesTest extends AbstractControllerTest {
private CalculatedField createAlarmCf(EntityId entityId,
String alarmType,
Map<String, Argument> arguments,
Map<AlarmSeverity, String> createConditions,
String clearCondition) {
Map<AlarmSeverity, Condition> createConditions,
Condition clearCondition) {
Map<AlarmSeverity, AlarmRule> createRules = new HashMap<>();
createConditions.forEach((severity, condition) -> {
createRules.put(severity, toAlarmRule(condition));
});
AlarmRule clearRule = clearCondition != null ? toAlarmRule(clearCondition) : null;
CalculatedField calculatedField = createAlarmCf(entityId, alarmType, arguments, createRules, clearRule);
CalculatedFieldDebugEvent debugEvent = await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> getDebugEvents(calculatedField.getId(), 1), events -> !events.isEmpty()).get(0);
latestEventId = debugEvent.getId();
return calculatedField;
}
private CalculatedField createAlarmCf(EntityId entityId,
String alarmType,
Map<String, Argument> arguments,
Map<AlarmSeverity, AlarmRule> createRules,
AlarmRule clearRule) {
CalculatedField calculatedField = new CalculatedField();
calculatedField.setEntityId(entityId);
calculatedField.setName(alarmType);
calculatedField.setType(CalculatedFieldType.ALARM);
AlarmCalculatedFieldConfiguration configuration = new AlarmCalculatedFieldConfiguration();
configuration.setArguments(arguments);
configuration.setCreateRules(new HashMap<>());
createConditions.forEach((severity, expression) -> {
configuration.getCreateRules().put(severity, toAlarmRule(expression));
});
configuration.setClearRule(toAlarmRule(clearCondition));
configuration.setCreateRules(createRules);
configuration.setClearRule(clearRule);
calculatedField.setConfiguration(configuration);
calculatedField.setDebugSettings(DebugSettings.all());
return saveCalculatedField(calculatedField);
}
private AlarmRule toAlarmRule(String conditionExpression) {
if (conditionExpression == null) {
return null;
}
private AlarmRule toAlarmRule(Condition condition) {
AlarmRule rule = new AlarmRule();
SimpleAlarmCondition condition = new SimpleAlarmCondition();
TbelAlarmConditionExpression expression = new TbelAlarmConditionExpression();
expression.setExpression(conditionExpression);
condition.setExpression(expression);
rule.setCondition(condition);
expression.setExpression(condition.expression());
if (condition.eventsCount() != null) {
RepeatingAlarmCondition alarmCondition = new RepeatingAlarmCondition();
alarmCondition.setExpression(expression);
AlarmConditionValue<Integer> count = new AlarmConditionValue<>();
count.setStaticValue(condition.eventsCount());
alarmCondition.setCount(count);
rule.setCondition(alarmCondition);
} else if (condition.durationMs() != null) {
DurationAlarmCondition alarmCondition = new DurationAlarmCondition();
alarmCondition.setExpression(expression);
AlarmConditionValue<Long> duration = new AlarmConditionValue<>();
duration.setStaticValue(condition.durationMs());
alarmCondition.setValue(duration);
rule.setCondition(alarmCondition);
} else {
SimpleAlarmCondition alarmCondition = new SimpleAlarmCondition();
alarmCondition.setExpression(expression);
rule.setCondition(alarmCondition);
}
return rule;
}
@ -188,4 +288,6 @@ public class AlarmRulesTest extends AbstractControllerTest {
.map(e -> (CalculatedFieldDebugEvent) e).toList();
}
private record Condition(String expression, Integer eventsCount, Long durationMs) {}
}

25
application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingCalculatedFieldStateTest.java

@ -49,6 +49,7 @@ import org.thingsboard.server.service.cf.TelemetryCalculatedFieldResult;
import org.thingsboard.server.service.cf.ctx.state.geofencing.GeofencingArgumentEntry;
import org.thingsboard.server.service.cf.ctx.state.geofencing.GeofencingCalculatedFieldState;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -118,7 +119,7 @@ public class GeofencingCalculatedFieldStateTest {
));
Map<String, ArgumentEntry> newArgs = Map.of("allowedZones", geofencingAllowedZoneArgEntry);
boolean stateUpdated = state.update(ctx, newArgs);
boolean stateUpdated = !state.update(newArgs, ctx).isEmpty();
assertThat(stateUpdated).isTrue();
assertThat(state.getArguments()).containsExactlyInAnyOrderEntriesOf(
@ -132,21 +133,21 @@ public class GeofencingCalculatedFieldStateTest {
@Test
void testUpdateStateWithInvalidArgumentTypeForLatitudeArgument() {
assertThatThrownBy(() -> state.update(ctx, Map.of(ENTITY_ID_LATITUDE_ARGUMENT_KEY, geofencingAllowedZoneArgEntry)))
assertThatThrownBy(() -> state.update(Map.of(ENTITY_ID_LATITUDE_ARGUMENT_KEY, geofencingAllowedZoneArgEntry), ctx))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Unsupported argument entry type for latitude argument: GEOFENCING. Only SINGLE_VALUE type is allowed.");
}
@Test
void testUpdateStateWithInvalidArgumentTypeForLongitudeArgument() {
assertThatThrownBy(() -> state.update(ctx, Map.of(ENTITY_ID_LONGITUDE_ARGUMENT_KEY, geofencingAllowedZoneArgEntry)))
assertThatThrownBy(() -> state.update(Map.of(ENTITY_ID_LONGITUDE_ARGUMENT_KEY, geofencingAllowedZoneArgEntry), ctx))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Unsupported argument entry type for longitude argument: GEOFENCING. Only SINGLE_VALUE type is allowed.");
}
@Test
void testUpdateStateWithInvalidArgumentTypeForGeofencingArgument() {
assertThatThrownBy(() -> state.update(ctx, Map.of("someArgumentName", latitudeArgEntry)))
assertThatThrownBy(() -> state.update(Map.of("someArgumentName", latitudeArgEntry), ctx))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Unsupported argument entry type for someArgumentName argument: SINGLE_VALUE. Only GEOFENCING type is allowed.");
}
@ -157,7 +158,7 @@ public class GeofencingCalculatedFieldStateTest {
SingleValueArgumentEntry newArgEntry = new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 50.4760), 190L);
Map<String, ArgumentEntry> newArgs = Map.of("latitude", newArgEntry);
boolean stateUpdated = state.update(ctx, newArgs);
boolean stateUpdated = !state.update(newArgs, ctx).isEmpty();
assertThat(stateUpdated).isTrue();
assertThat(state.getArguments()).isEqualTo(newArgs);
@ -169,7 +170,7 @@ public class GeofencingCalculatedFieldStateTest {
Map<String, ArgumentEntry> newArgs = Map.of("allowedZones", geofencingAllowedZoneArgEntry);
boolean stateUpdated = state.update(ctx, newArgs);
boolean stateUpdated = !state.update(newArgs, ctx).isEmpty();
assertThat(stateUpdated).isFalse();
assertThat(state.getArguments()).isEqualTo(newArgs);
@ -179,7 +180,7 @@ public class GeofencingCalculatedFieldStateTest {
void testUpdateStateWhenUpdateExistingSingleValueArgumentEntryWithValueOfAnotherType() {
state.arguments = new HashMap<>(Map.of(ENTITY_ID_LATITUDE_ARGUMENT_KEY, latitudeArgEntry));
assertThatThrownBy(() -> state.update(ctx, Map.of(ENTITY_ID_LATITUDE_ARGUMENT_KEY, geofencingAllowedZoneArgEntry)))
assertThatThrownBy(() -> state.update(Map.of(ENTITY_ID_LATITUDE_ARGUMENT_KEY, geofencingAllowedZoneArgEntry), ctx))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Unsupported argument entry type for single value argument entry: GEOFENCING");
}
@ -189,7 +190,7 @@ public class GeofencingCalculatedFieldStateTest {
void testUpdateStateWhenUpdateExistingGeofencingValueArgumentEntryWithValueOfAnotherType() {
state.arguments = new HashMap<>(Map.of("allowedZones", geofencingAllowedZoneArgEntry));
assertThatThrownBy(() -> state.update(ctx, Map.of("allowedZones", latitudeArgEntry)))
assertThatThrownBy(() -> state.update(Map.of("allowedZones", latitudeArgEntry), ctx))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Unsupported argument entry type for geofencing argument entry: SINGLE_VALUE");
}
@ -255,7 +256,7 @@ public class GeofencingCalculatedFieldStateTest {
SingleValueArgumentEntry newLongitude = new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", 30.5110), 166L);
// move the device to new coordinates → leaves allowed, enters restricted
state.update(ctx, Map.of(ENTITY_ID_LATITUDE_ARGUMENT_KEY, newLatitude, ENTITY_ID_LONGITUDE_ARGUMENT_KEY, newLongitude));
state.update(Map.of(ENTITY_ID_LATITUDE_ARGUMENT_KEY, newLatitude, ENTITY_ID_LONGITUDE_ARGUMENT_KEY, newLongitude), ctx);
TelemetryCalculatedFieldResult result2 = performCalculation();
@ -327,7 +328,7 @@ public class GeofencingCalculatedFieldStateTest {
SingleValueArgumentEntry newLongitude = new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", 30.5110), 166L);
// move the device to new coordinates → leaves allowed, enters restricted
state.update(ctx, Map.of(ENTITY_ID_LATITUDE_ARGUMENT_KEY, newLatitude, ENTITY_ID_LONGITUDE_ARGUMENT_KEY, newLongitude));
state.update(Map.of(ENTITY_ID_LATITUDE_ARGUMENT_KEY, newLatitude, ENTITY_ID_LONGITUDE_ARGUMENT_KEY, newLongitude), ctx);
TelemetryCalculatedFieldResult result2 = performCalculation();
@ -399,7 +400,7 @@ public class GeofencingCalculatedFieldStateTest {
SingleValueArgumentEntry newLongitude = new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", 30.5110), 166L);
// move the device to new coordinates → leaves allowed, enters restricted
state.update(ctx, Map.of(ENTITY_ID_LATITUDE_ARGUMENT_KEY, newLatitude, ENTITY_ID_LONGITUDE_ARGUMENT_KEY, newLongitude));
state.update(Map.of(ENTITY_ID_LATITUDE_ARGUMENT_KEY, newLatitude, ENTITY_ID_LONGITUDE_ARGUMENT_KEY, newLongitude), ctx);
TelemetryCalculatedFieldResult result2 = performCalculation();
@ -486,7 +487,7 @@ public class GeofencingCalculatedFieldStateTest {
}
private TelemetryCalculatedFieldResult performCalculation() throws InterruptedException, ExecutionException {
return (TelemetryCalculatedFieldResult) state.performCalculation(ctx).get();
return (TelemetryCalculatedFieldResult) state.performCalculation(Collections.emptyMap(), ctx).get();
}
}

7
application/src/test/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldStateTest.java

@ -45,6 +45,7 @@ import org.thingsboard.server.common.stats.DefaultStatsFactory;
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import org.thingsboard.server.service.cf.TelemetryCalculatedFieldResult;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
@ -99,7 +100,7 @@ public class ScriptCalculatedFieldStateTest {
state.arguments = new HashMap<>(Map.of("assetHumidity", assetHumidityArgEntry));
Map<String, ArgumentEntry> newArgs = Map.of("deviceTemperature", deviceTemperatureArgEntry);
boolean stateUpdated = state.update(ctx, newArgs);
boolean stateUpdated = !state.update(newArgs, ctx).isEmpty();
assertThat(stateUpdated).isTrue();
assertThat(state.getArguments()).containsExactlyInAnyOrderEntriesOf(
@ -116,7 +117,7 @@ public class ScriptCalculatedFieldStateTest {
SingleValueArgumentEntry newArgEntry = new SingleValueArgumentEntry(ts, new LongDataEntry("assetHumidity", 41L), 349L);
Map<String, ArgumentEntry> newArgs = Map.of("assetHumidity", newArgEntry);
boolean stateUpdated = state.update(ctx, newArgs);
boolean stateUpdated = !state.update(newArgs, ctx).isEmpty();
assertThat(stateUpdated).isTrue();
assertThat(state.getArguments()).containsExactlyInAnyOrderEntriesOf(
@ -228,7 +229,7 @@ public class ScriptCalculatedFieldStateTest {
}
private TelemetryCalculatedFieldResult performCalculation() throws InterruptedException, ExecutionException {
return (TelemetryCalculatedFieldResult) state.performCalculation(ctx).get();
return (TelemetryCalculatedFieldResult) state.performCalculation(Collections.emptyMap(), ctx).get();
}
}

11
application/src/test/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldStateTest.java

@ -43,6 +43,7 @@ import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import org.thingsboard.server.service.cf.TelemetryCalculatedFieldResult;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@ -94,7 +95,7 @@ public class SimpleCalculatedFieldStateTest {
));
Map<String, ArgumentEntry> newArgs = Map.of("key3", key3ArgEntry);
boolean stateUpdated = state.update(ctx, newArgs);
boolean stateUpdated = !state.update(newArgs, ctx).isEmpty();
assertThat(stateUpdated).isTrue();
assertThat(state.getArguments()).containsExactlyInAnyOrderEntriesOf(
@ -112,7 +113,7 @@ public class SimpleCalculatedFieldStateTest {
SingleValueArgumentEntry newArgEntry = new SingleValueArgumentEntry(System.currentTimeMillis(), new LongDataEntry("key1", 18L), 190L);
Map<String, ArgumentEntry> newArgs = Map.of("key1", newArgEntry);
boolean stateUpdated = state.update(ctx, newArgs);
boolean stateUpdated = !state.update(newArgs, ctx).isEmpty();
assertThat(stateUpdated).isTrue();
assertThat(state.getArguments()).containsExactlyInAnyOrderEntriesOf(Map.of("key1", newArgEntry));
@ -126,7 +127,7 @@ public class SimpleCalculatedFieldStateTest {
));
Map<String, ArgumentEntry> newArgs = Map.of("key3", new TsRollingArgumentEntry(10, 30000L));
assertThatThrownBy(() -> state.update(ctx, newArgs))
assertThatThrownBy(() -> state.update(newArgs, ctx))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Rolling argument entry is not supported for simple calculated fields.");
}
@ -156,7 +157,7 @@ public class SimpleCalculatedFieldStateTest {
"key3", key3ArgEntry
));
assertThatThrownBy(() -> state.performCalculation(ctx))
assertThatThrownBy(() -> state.performCalculation(Collections.emptyMap(), ctx))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Argument 'key2' is not a number.");
}
@ -271,7 +272,7 @@ public class SimpleCalculatedFieldStateTest {
}
private TelemetryCalculatedFieldResult performCalculation() throws InterruptedException, ExecutionException {
return (TelemetryCalculatedFieldResult) state.performCalculation(ctx).get();
return (TelemetryCalculatedFieldResult) state.performCalculation(Collections.emptyMap(), ctx).get();
}
}

2
application/src/test/java/org/thingsboard/server/utils/CalculatedFieldUtilsTest.java

@ -85,7 +85,7 @@ class CalculatedFieldUtilsTest {
// Create cf state with the geofencing argument and add it to the state map
CalculatedFieldState state = new GeofencingCalculatedFieldState(DEVICE_ID);
state.update(mock(CalculatedFieldCtx.class), Map.of("geofencingArgumentTest", geofencingArgumentEntry));
state.update(Map.of("geofencingArgumentTest", geofencingArgumentEntry), mock(CalculatedFieldCtx.class));
// when
CalculatedFieldStateProto proto = toProto(stateId, state);

2
common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/DurationAlarmCondition.java

@ -17,11 +17,13 @@ package org.thingsboard.server.common.data.alarm.rule.condition;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.concurrent.TimeUnit;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DurationAlarmCondition extends AlarmCondition {
private TimeUnit unit;

2
common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/RepeatingAlarmCondition.java

@ -17,9 +17,11 @@ package org.thingsboard.server.common.data.alarm.rule.condition;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class RepeatingAlarmCondition extends AlarmCondition {
private AlarmConditionValue<Integer> count;

20
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmResult.java

@ -16,6 +16,7 @@
package org.thingsboard.rule.engine.action;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.alarm.Alarm;
@ -24,13 +25,18 @@ import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TbAlarmResult {
boolean isCreated;
boolean isUpdated;
boolean isSeverityUpdated;
boolean isCleared;
Alarm alarm;
Long conditionRepeats;
Long conditionDuration;
public TbAlarmResult(boolean isCreated, boolean isUpdated, boolean isCleared, Alarm alarm) {
this.isCreated = isCreated;
this.isUpdated = isUpdated;
@ -40,11 +46,13 @@ public class TbAlarmResult {
public static TbAlarmResult fromAlarmResult(AlarmApiCallResult result) {
boolean isSeverityChanged = result.isSeverityChanged();
return new TbAlarmResult(
result.isCreated(),
result.isModified() && !isSeverityChanged,
isSeverityChanged,
result.isCleared(),
result.getAlarm());
return TbAlarmResult.builder()
.isCreated(result.isCreated())
.isUpdated(result.isModified() && !isSeverityChanged)
.isSeverityUpdated(isSeverityChanged)
.isCleared(result.isCleared())
.alarm(result.getAlarm())
.build();
}
}

Loading…
Cancel
Save