Scheduler for Device Profile Alarms
This commit is contained in:
		
							parent
							
								
									dafc0bebc1
								
							
						
					
					
						commit
						c9f3af73fd
					
				@ -15,6 +15,7 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
package org.thingsboard.server.common.data.device.profile;
 | 
					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.JsonIgnoreProperties;
 | 
				
			||||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
 | 
					import com.fasterxml.jackson.annotation.JsonSubTypes;
 | 
				
			||||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
 | 
					import com.fasterxml.jackson.annotation.JsonTypeInfo;
 | 
				
			||||||
@ -30,6 +31,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
 | 
				
			|||||||
        @JsonSubTypes.Type(value = RepeatingAlarmConditionSpec.class, name = "REPEATING")})
 | 
					        @JsonSubTypes.Type(value = RepeatingAlarmConditionSpec.class, name = "REPEATING")})
 | 
				
			||||||
public interface AlarmConditionSpec {
 | 
					public interface AlarmConditionSpec {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @JsonIgnore
 | 
				
			||||||
    AlarmConditionSpecType getType();
 | 
					    AlarmConditionSpecType getType();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@ import java.util.List;
 | 
				
			|||||||
public class CustomTimeScheduleItem {
 | 
					public class CustomTimeScheduleItem {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private boolean enabled;
 | 
					    private boolean enabled;
 | 
				
			||||||
    private Integer dayOfWeek;
 | 
					    private int dayOfWeek;
 | 
				
			||||||
    private long startsOn;
 | 
					    private long startsOn;
 | 
				
			||||||
    private long endsOn;
 | 
					    private long endsOn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -29,6 +29,6 @@ public class DurationAlarmConditionSpec implements AlarmConditionSpec {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public AlarmConditionSpecType getType() {
 | 
					    public AlarmConditionSpecType getType() {
 | 
				
			||||||
        return AlarmConditionSpecType.SIMPLE;
 | 
					        return AlarmConditionSpecType.DURATION;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -28,6 +28,6 @@ public class RepeatingAlarmConditionSpec implements AlarmConditionSpec {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public AlarmConditionSpecType getType() {
 | 
					    public AlarmConditionSpecType getType() {
 | 
				
			||||||
        return AlarmConditionSpecType.SIMPLE;
 | 
					        return AlarmConditionSpecType.REPEATING;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -18,12 +18,13 @@ package org.thingsboard.server.common.data.device.profile;
 | 
				
			|||||||
import lombok.Data;
 | 
					import lombok.Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Set;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Data
 | 
					@Data
 | 
				
			||||||
public class SpecificTimeSchedule implements AlarmSchedule {
 | 
					public class SpecificTimeSchedule implements AlarmSchedule {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private String timezone;
 | 
					    private String timezone;
 | 
				
			||||||
    private List<Integer> daysOfWeek;
 | 
					    private Set<Integer> daysOfWeek;
 | 
				
			||||||
    private long startsOn;
 | 
					    private long startsOn;
 | 
				
			||||||
    private long endsOn;
 | 
					    private long endsOn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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.AlarmCondition;
 | 
				
			||||||
import org.thingsboard.server.common.data.device.profile.AlarmConditionSpec;
 | 
					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.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.DurationAlarmConditionSpec;
 | 
				
			||||||
import org.thingsboard.server.common.data.device.profile.RepeatingAlarmConditionSpec;
 | 
					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.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.BooleanFilterPredicate;
 | 
				
			||||||
import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
 | 
					import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
 | 
				
			||||||
import org.thingsboard.server.common.data.query.KeyFilter;
 | 
					import org.thingsboard.server.common.data.query.KeyFilter;
 | 
				
			||||||
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
 | 
					import org.thingsboard.server.common.data.query.KeyFilterPredicate;
 | 
				
			||||||
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
 | 
					import org.thingsboard.server.common.data.query.NumericFilterPredicate;
 | 
				
			||||||
import org.thingsboard.server.common.data.query.StringFilterPredicate;
 | 
					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
 | 
					@Data
 | 
				
			||||||
public class AlarmRuleState {
 | 
					public class AlarmRuleState {
 | 
				
			||||||
@ -85,21 +94,72 @@ public class AlarmRuleState {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public boolean eval(DeviceDataSnapshot data) {
 | 
					    public boolean eval(DeviceDataSnapshot data) {
 | 
				
			||||||
 | 
					        boolean active = isActive(data.getTs());
 | 
				
			||||||
        switch (spec.getType()) {
 | 
					        switch (spec.getType()) {
 | 
				
			||||||
            case SIMPLE:
 | 
					            case SIMPLE:
 | 
				
			||||||
                return eval(alarmRule.getCondition(), data);
 | 
					                return active && eval(alarmRule.getCondition(), data);
 | 
				
			||||||
            case DURATION:
 | 
					            case DURATION:
 | 
				
			||||||
                return evalDuration(data);
 | 
					                return evalDuration(data, active);
 | 
				
			||||||
            case REPEATING:
 | 
					            case REPEATING:
 | 
				
			||||||
                return evalRepeating(data);
 | 
					                return evalRepeating(data, active);
 | 
				
			||||||
            default:
 | 
					            default:
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private boolean evalRepeating(DeviceDataSnapshot data) {
 | 
					    private boolean isActive(long eventTs) {
 | 
				
			||||||
        boolean eval = eval(alarmRule.getCondition(), data);
 | 
					        if (eventTs == 0L) {
 | 
				
			||||||
        if (eval) {
 | 
					            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);
 | 
					            state.setEventCount(state.getEventCount() + 1);
 | 
				
			||||||
            updateFlag = true;
 | 
					            updateFlag = true;
 | 
				
			||||||
            return state.getEventCount() > requiredRepeats;
 | 
					            return state.getEventCount() > requiredRepeats;
 | 
				
			||||||
@ -112,9 +172,8 @@ public class AlarmRuleState {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private boolean evalDuration(DeviceDataSnapshot data) {
 | 
					    private boolean evalDuration(DeviceDataSnapshot data, boolean active) {
 | 
				
			||||||
        boolean eval = eval(alarmRule.getCondition(), data);
 | 
					        if (active && eval(alarmRule.getCondition(), data)) {
 | 
				
			||||||
        if (eval) {
 | 
					 | 
				
			||||||
            if (state.getLastEventTs() > 0) {
 | 
					            if (state.getLastEventTs() > 0) {
 | 
				
			||||||
                if (data.getTs() > state.getLastEventTs()) {
 | 
					                if (data.getTs() > state.getLastEventTs()) {
 | 
				
			||||||
                    state.setDuration(state.getDuration() + (data.getTs() - state.getLastEventTs()));
 | 
					                    state.setDuration(state.getDuration() + (data.getTs() - state.getLastEventTs()));
 | 
				
			||||||
@ -145,7 +204,13 @@ public class AlarmRuleState {
 | 
				
			|||||||
            case DURATION:
 | 
					            case DURATION:
 | 
				
			||||||
                if (requiredDurationInMs > 0 && state.getLastEventTs() > 0 && ts > state.getLastEventTs()) {
 | 
					                if (requiredDurationInMs > 0 && state.getLastEventTs() > 0 && ts > state.getLastEventTs()) {
 | 
				
			||||||
                    long duration = state.getDuration() + (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:
 | 
					            default:
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user