Scheduler for Device Profile Alarms

This commit is contained in:
Andrii Shvaika 2020-10-05 18:53:15 +03:00
parent dafc0bebc1
commit c9f3af73fd
7 changed files with 112 additions and 14 deletions

View File

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.common.data.device.profile;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@ -30,6 +31,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonSubTypes.Type(value = RepeatingAlarmConditionSpec.class, name = "REPEATING")})
public interface AlarmConditionSpec {
@JsonIgnore
AlarmConditionSpecType getType();
}

View File

@ -23,7 +23,7 @@ import java.util.List;
public class CustomTimeScheduleItem {
private boolean enabled;
private Integer dayOfWeek;
private int dayOfWeek;
private long startsOn;
private long endsOn;

View File

@ -29,6 +29,6 @@ public class DurationAlarmConditionSpec implements AlarmConditionSpec {
@Override
public AlarmConditionSpecType getType() {
return AlarmConditionSpecType.SIMPLE;
return AlarmConditionSpecType.DURATION;
}
}

View File

@ -28,6 +28,6 @@ public class RepeatingAlarmConditionSpec implements AlarmConditionSpec {
@Override
public AlarmConditionSpecType getType() {
return AlarmConditionSpecType.SIMPLE;
return AlarmConditionSpecType.REPEATING;
}
}

View File

@ -18,12 +18,13 @@ package org.thingsboard.server.common.data.device.profile;
import lombok.Data;
import java.util.List;
import java.util.Set;
@Data
public class SpecificTimeSchedule implements AlarmSchedule {
private String timezone;
private List<Integer> daysOfWeek;
private Set<Integer> daysOfWeek;
private long startsOn;
private long endsOn;

View File

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.msg.tools;
import java.time.ZoneId;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class SchedulerUtils {
private static final ConcurrentMap<String, ZoneId> tzMap = new ConcurrentHashMap<>();
public static ZoneId getZoneId(String tz) {
return tzMap.computeIfAbsent(tz == null || tz.isEmpty() ? "UTC" : tz, ZoneId::of);
}
}

View File

@ -21,15 +21,24 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.device.profile.AlarmCondition;
import org.thingsboard.server.common.data.device.profile.AlarmConditionSpec;
import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.device.profile.CustomTimeSchedule;
import org.thingsboard.server.common.data.device.profile.CustomTimeScheduleItem;
import org.thingsboard.server.common.data.device.profile.DurationAlarmConditionSpec;
import org.thingsboard.server.common.data.device.profile.RepeatingAlarmConditionSpec;
import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec;
import org.thingsboard.server.common.data.device.profile.SpecificTimeSchedule;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.StringFilterPredicate;
import org.thingsboard.server.common.msg.tools.SchedulerUtils;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
@Data
public class AlarmRuleState {
@ -85,21 +94,72 @@ public class AlarmRuleState {
}
public boolean eval(DeviceDataSnapshot data) {
boolean active = isActive(data.getTs());
switch (spec.getType()) {
case SIMPLE:
return eval(alarmRule.getCondition(), data);
return active && eval(alarmRule.getCondition(), data);
case DURATION:
return evalDuration(data);
return evalDuration(data, active);
case REPEATING:
return evalRepeating(data);
return evalRepeating(data, active);
default:
return false;
}
}
private boolean evalRepeating(DeviceDataSnapshot data) {
boolean eval = eval(alarmRule.getCondition(), data);
if (eval) {
private boolean isActive(long eventTs) {
if (eventTs == 0L) {
eventTs = System.currentTimeMillis();
}
if (alarmRule.getSchedule() == null) {
return true;
}
switch (alarmRule.getSchedule().getType()) {
case ANY_TIME:
return true;
case SPECIFIC_TIME:
return isActiveSpecific((SpecificTimeSchedule) alarmRule.getSchedule(), eventTs);
case CUSTOM:
return isActiveCustom((CustomTimeSchedule) alarmRule.getSchedule(), eventTs);
default:
throw new RuntimeException("Unsupported schedule type: " + alarmRule.getSchedule().getType());
}
}
private boolean isActiveSpecific(SpecificTimeSchedule schedule, long eventTs) {
ZoneId zoneId = SchedulerUtils.getZoneId(schedule.getTimezone());
ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(eventTs), zoneId);
if (schedule.getDaysOfWeek().size() != 7) {
int dayOfWeek = zdt.getDayOfWeek().getValue();
if (!schedule.getDaysOfWeek().contains(dayOfWeek)) {
return false;
}
}
long startOfDay = zdt.toLocalDate().atStartOfDay(zoneId).toInstant().toEpochMilli();
long msFromStartOfDay = eventTs - startOfDay;
return schedule.getStartsOn() <= msFromStartOfDay && schedule.getEndsOn() > msFromStartOfDay;
}
private boolean isActiveCustom(CustomTimeSchedule schedule, long eventTs) {
ZoneId zoneId = SchedulerUtils.getZoneId(schedule.getTimezone());
ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(eventTs), zoneId);
int dayOfWeek = zdt.toLocalDate().getDayOfWeek().getValue();
for (CustomTimeScheduleItem item : schedule.getItems()) {
if (item.getDayOfWeek() == dayOfWeek) {
if (item.isEnabled()) {
long startOfDay = zdt.toLocalDate().atStartOfDay(zoneId).toInstant().toEpochMilli();
long msFromStartOfDay = eventTs - startOfDay;
return item.getStartsOn() <= msFromStartOfDay && item.getEndsOn() > msFromStartOfDay;
} else {
return false;
}
}
}
return false;
}
private boolean evalRepeating(DeviceDataSnapshot data, boolean active) {
if (active && eval(alarmRule.getCondition(), data)) {
state.setEventCount(state.getEventCount() + 1);
updateFlag = true;
return state.getEventCount() > requiredRepeats;
@ -112,9 +172,8 @@ public class AlarmRuleState {
}
}
private boolean evalDuration(DeviceDataSnapshot data) {
boolean eval = eval(alarmRule.getCondition(), data);
if (eval) {
private boolean evalDuration(DeviceDataSnapshot data, boolean active) {
if (active && eval(alarmRule.getCondition(), data)) {
if (state.getLastEventTs() > 0) {
if (data.getTs() > state.getLastEventTs()) {
state.setDuration(state.getDuration() + (data.getTs() - state.getLastEventTs()));
@ -145,7 +204,13 @@ public class AlarmRuleState {
case DURATION:
if (requiredDurationInMs > 0 && state.getLastEventTs() > 0 && ts > state.getLastEventTs()) {
long duration = state.getDuration() + (ts - state.getLastEventTs());
return duration > requiredDurationInMs;
boolean result = duration > requiredDurationInMs && isActive(ts);
if (result) {
state.setLastEventTs(0L);
state.setDuration(0L);
updateFlag = true;
}
return result;
}
default:
return false;