diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java index 0a175b4899..3e9502bfe9 100644 --- a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java @@ -155,7 +155,6 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM initState(state, ctx); } else { state.setCtx(ctx, actorCtx); - state.init(); } if (state.isSizeOk()) { processStateIfReady(state, Collections.emptyMap(), ctx, Collections.singletonList(ctx.getCfId()), null, null, msg.getCallback()); diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java index d29fab508b..1f6adf2ded 100644 --- a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java @@ -330,11 +330,11 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware StateAction stateAction; if (newCfCtx.getCfType() != oldCfCtx.getCfType()) { - stateAction = StateAction.RECREATE; + stateAction = StateAction.RECREATE; // completely recreate state, then calculate } else if (newCfCtx.hasStateChanges(oldCfCtx)) { - stateAction = StateAction.REINIT; + stateAction = StateAction.REINIT; // refetch arguments, call state.init, then calculate } else if (newCfCtx.hasContextOnlyChanges(oldCfCtx)) { - stateAction = StateAction.REPROCESS; + stateAction = StateAction.REPROCESS; // call state.setCtx, then calculate } else { callback.onSuccess(); return; diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java index db755d3e17..fe63a76a30 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java @@ -63,8 +63,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.concurrent.TimeUnit; import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; import java.util.stream.Stream; @Data @@ -479,11 +479,11 @@ public class CalculatedFieldCtx { return new CalculatedFieldEntityCtxId(tenantId, cfId, entityId); } - public boolean hasContextOnlyChanges(CalculatedFieldCtx other) { // has changes that do not require state reinit and will be picked up by the state on the fly + public boolean hasContextOnlyChanges(CalculatedFieldCtx other) { if (calculatedField.getConfiguration() instanceof ExpressionBasedCalculatedFieldConfiguration && !expression.equals(other.expression)) { return true; } - if (!output.equals(other.output)) { + if (!Objects.equals(output, other.output)) { return true; } if (cfType == CalculatedFieldType.ALARM && !calculatedField.getName().equals(other.getCalculatedField().getName())) { @@ -495,9 +495,8 @@ public class CalculatedFieldCtx { return false; } - public boolean hasStateChanges(CalculatedFieldCtx other) { // has changes that require state reinit (will trigger state.reset() and re-fetch arguments) - boolean hasChanges = !arguments.equals(other.arguments); - if (hasChanges) { + public boolean hasStateChanges(CalculatedFieldCtx other) { + if (!arguments.equals(other.arguments)) { return true; } if (cfType == CalculatedFieldType.ALARM) { @@ -505,14 +504,13 @@ public class CalculatedFieldCtx { var otherConfig = (AlarmCalculatedFieldConfiguration) other.getCalculatedField().getConfiguration(); if (!thisConfig.getCreateRules().equals(otherConfig.getCreateRules()) || !Objects.equals(thisConfig.getClearRule(), otherConfig.getClearRule())) { - hasChanges = true; + return true; } - // TODO: implement rules update logic! } if (hasGeofencingZoneGroupConfigurationChanges(other)) { - hasChanges = true; + return true; } - return hasChanges; + return false; } private boolean hasGeofencingZoneGroupConfigurationChanges(CalculatedFieldCtx other) { diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java index 838cb2779d..6f9f59584c 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java @@ -77,8 +77,8 @@ import static org.thingsboard.server.service.cf.ctx.state.alarm.AlarmEvalResult. @Slf4j public class AlarmCalculatedFieldState extends BaseCalculatedFieldState { - private String alarmType; private AlarmCalculatedFieldConfiguration configuration; + private String alarmType; @Getter private final Map createRuleStates = new TreeMap<>(Comparator.comparing(Enum::ordinal)); @@ -97,8 +97,13 @@ public class AlarmCalculatedFieldState extends BaseCalculatedFieldState { @Override public void setCtx(CalculatedFieldCtx ctx, TbActorRef actorCtx) { super.setCtx(ctx, actorCtx); - this.alarmType = ctx.getCalculatedField().getName(); this.configuration = getConfiguration(ctx); + this.alarmType = ctx.getCalculatedField().getName(); + + if (currentAlarm != null && !currentAlarm.getType().equals(alarmType)) { + currentAlarm = null; + initialFetchDone = false; + } } @Override @@ -170,10 +175,7 @@ public class AlarmCalculatedFieldState extends BaseCalculatedFieldState { @Override public void reset() { super.reset(); - createRuleStates.values().forEach(AlarmRuleState::clear); - if (clearRuleState != null) { - clearRuleState.clear(); - } + configuration = null; } @Override @@ -502,7 +504,7 @@ public class AlarmCalculatedFieldState extends BaseCalculatedFieldState { SingleValueArgumentEntry entry = getArgument(argument); value = mapper.apply(entry.getKvEntryValue()); if (value == null) { - throw new IllegalArgumentException("No value found for argument " + argument); + throw new IllegalArgumentException("No proper value found for argument " + argument); } } return value; diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmRuleState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmRuleState.java index 0638543685..97bf5c5453 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmRuleState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmRuleState.java @@ -15,10 +15,11 @@ */ package org.thingsboard.server.service.cf.ctx.state.alarm; +import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.Data; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.KvUtil; -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; @@ -28,6 +29,8 @@ import org.thingsboard.server.common.data.alarm.rule.condition.DurationAlarmCond import org.thingsboard.server.common.data.alarm.rule.condition.RepeatingAlarmCondition; import org.thingsboard.server.common.data.alarm.rule.condition.expression.AlarmConditionExpression; import org.thingsboard.server.common.data.alarm.rule.condition.schedule.AlarmSchedule; +import org.thingsboard.server.common.data.alarm.rule.condition.schedule.AlarmScheduleType; +import org.thingsboard.server.common.data.alarm.rule.condition.schedule.AnyTimeSchedule; import org.thingsboard.server.common.data.alarm.rule.condition.schedule.CustomTimeSchedule; import org.thingsboard.server.common.data.alarm.rule.condition.schedule.CustomTimeScheduleItem; import org.thingsboard.server.common.data.alarm.rule.condition.schedule.SpecificTimeSchedule; @@ -109,8 +112,12 @@ public class AlarmRuleState { eventCount++; } long requiredRepeats = getIntValue(((RepeatingAlarmCondition) condition).getCount()); - long leftRepeats = requiredRepeats - eventCount; - return leftRepeats <= 0 ? AlarmEvalResult.TRUE : AlarmEvalResult.notYetTrue(leftRepeats, 0); + if (requiredRepeats > 0) { + long leftRepeats = requiredRepeats - eventCount; + return leftRepeats <= 0 ? AlarmEvalResult.TRUE : AlarmEvalResult.notYetTrue(leftRepeats, 0); + } else { + return AlarmEvalResult.NOT_YET_TRUE; + } } else { return AlarmEvalResult.FALSE; } @@ -132,11 +139,15 @@ public class AlarmRuleState { } duration = lastEventTs - firstEventTs; long requiredDuration = getRequiredDurationInMs(); - long leftDuration = requiredDuration - duration; - if (leftDuration <= 0) { - return AlarmEvalResult.TRUE; + if (requiredDuration > 0) { + long leftDuration = requiredDuration - duration; + if (leftDuration <= 0) { + return AlarmEvalResult.TRUE; + } else { + return AlarmEvalResult.notYetTrue(0, leftDuration); + } } else { - return AlarmEvalResult.notYetTrue(0, leftDuration); + return AlarmEvalResult.NOT_YET_TRUE; } } else { return AlarmEvalResult.FALSE; @@ -148,13 +159,14 @@ public class AlarmRuleState { return true; } AlarmSchedule schedule = state.resolveValue(condition.getSchedule(), entry -> Optional.ofNullable(KvUtil.getStringValue(entry)) - .map(str -> JsonConverter.parse(str, AlarmSchedule.class)) - .orElse(null)); - return switch (schedule.getType()) { + .map(this::parseSchedule).orElse(null)); + boolean active = switch (schedule.getType()) { case ANY_TIME -> true; case SPECIFIC_TIME -> isActiveSpecific((SpecificTimeSchedule) schedule, eventTs); case CUSTOM -> isActiveCustom((CustomTimeSchedule) schedule, eventTs); }; + log.trace("Alarm rule active = {} for schedule {}", active, schedule); + return active; } private boolean isActiveSpecific(SpecificTimeSchedule schedule, long eventTs) { @@ -221,6 +233,28 @@ public class AlarmRuleState { return eventCount == 0L && firstEventTs == 0L && lastEventTs == 0L && durationCheckFuture == null; } + private AlarmSchedule parseSchedule(String str) { + ObjectNode json = (ObjectNode) JacksonUtil.toJsonNode(str); + if (json.isEmpty()) { + return new AnyTimeSchedule(); // only if valid json, fail otherwise + } + + if (!json.hasNonNull("type")) { + // deducting the schedule type + AlarmScheduleType type; + if (json.hasNonNull("daysOfWeek")) { + type = AlarmScheduleType.SPECIFIC_TIME; + } else if (json.hasNonNull("items")) { + type = AlarmScheduleType.CUSTOM; + } else { + throw new IllegalArgumentException("Failed to parse alarm schedule from '" + str + "'"); + } + json.put("type", type.name()); + } + + return JacksonUtil.treeToValue(json, AlarmSchedule.class); + } + private Integer getIntValue(AlarmConditionValue value) { return state.resolveValue(value, entry -> Optional.ofNullable(KvUtil.getLongValue(entry)).map(Long::intValue).orElse(null)); }