Commit c9f3af73fd352666c5c47a66e12c3ee44c6f0fd8

Authored by Andrii Shvaika
1 parent dafc0beb

Scheduler for Device Profile Alarms

... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.common.data.device.profile;
17 17
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
18 19 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
19 20 import com.fasterxml.jackson.annotation.JsonSubTypes;
20 21 import com.fasterxml.jackson.annotation.JsonTypeInfo;
... ... @@ -30,6 +31,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
30 31 @JsonSubTypes.Type(value = RepeatingAlarmConditionSpec.class, name = "REPEATING")})
31 32 public interface AlarmConditionSpec {
32 33
  34 + @JsonIgnore
33 35 AlarmConditionSpecType getType();
34 36
35 37 }
... ...
... ... @@ -23,7 +23,7 @@ import java.util.List;
23 23 public class CustomTimeScheduleItem {
24 24
25 25 private boolean enabled;
26   - private Integer dayOfWeek;
  26 + private int dayOfWeek;
27 27 private long startsOn;
28 28 private long endsOn;
29 29
... ...
... ... @@ -29,6 +29,6 @@ public class DurationAlarmConditionSpec implements AlarmConditionSpec {
29 29
30 30 @Override
31 31 public AlarmConditionSpecType getType() {
32   - return AlarmConditionSpecType.SIMPLE;
  32 + return AlarmConditionSpecType.DURATION;
33 33 }
34 34 }
... ...
... ... @@ -28,6 +28,6 @@ public class RepeatingAlarmConditionSpec implements AlarmConditionSpec {
28 28
29 29 @Override
30 30 public AlarmConditionSpecType getType() {
31   - return AlarmConditionSpecType.SIMPLE;
  31 + return AlarmConditionSpecType.REPEATING;
32 32 }
33 33 }
... ...
... ... @@ -18,12 +18,13 @@ package org.thingsboard.server.common.data.device.profile;
18 18 import lombok.Data;
19 19
20 20 import java.util.List;
  21 +import java.util.Set;
21 22
22 23 @Data
23 24 public class SpecificTimeSchedule implements AlarmSchedule {
24 25
25 26 private String timezone;
26   - private List<Integer> daysOfWeek;
  27 + private Set<Integer> daysOfWeek;
27 28 private long startsOn;
28 29 private long endsOn;
29 30
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.msg.tools;
  17 +
  18 +import java.time.ZoneId;
  19 +import java.util.concurrent.ConcurrentHashMap;
  20 +import java.util.concurrent.ConcurrentMap;
  21 +
  22 +public class SchedulerUtils {
  23 +
  24 + private static final ConcurrentMap<String, ZoneId> tzMap = new ConcurrentHashMap<>();
  25 +
  26 + public static ZoneId getZoneId(String tz) {
  27 + return tzMap.computeIfAbsent(tz == null || tz.isEmpty() ? "UTC" : tz, ZoneId::of);
  28 + }
  29 +
  30 +}
... ...
... ... @@ -21,15 +21,24 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity;
21 21 import org.thingsboard.server.common.data.device.profile.AlarmCondition;
22 22 import org.thingsboard.server.common.data.device.profile.AlarmConditionSpec;
23 23 import org.thingsboard.server.common.data.device.profile.AlarmRule;
  24 +import org.thingsboard.server.common.data.device.profile.CustomTimeSchedule;
  25 +import org.thingsboard.server.common.data.device.profile.CustomTimeScheduleItem;
24 26 import org.thingsboard.server.common.data.device.profile.DurationAlarmConditionSpec;
25 27 import org.thingsboard.server.common.data.device.profile.RepeatingAlarmConditionSpec;
26 28 import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec;
  29 +import org.thingsboard.server.common.data.device.profile.SpecificTimeSchedule;
27 30 import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
28 31 import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
29 32 import org.thingsboard.server.common.data.query.KeyFilter;
30 33 import org.thingsboard.server.common.data.query.KeyFilterPredicate;
31 34 import org.thingsboard.server.common.data.query.NumericFilterPredicate;
32 35 import org.thingsboard.server.common.data.query.StringFilterPredicate;
  36 +import org.thingsboard.server.common.msg.tools.SchedulerUtils;
  37 +
  38 +import java.time.Instant;
  39 +import java.time.ZoneId;
  40 +import java.time.ZonedDateTime;
  41 +import java.util.Calendar;
33 42
34 43 @Data
35 44 public class AlarmRuleState {
... ... @@ -85,21 +94,72 @@ public class AlarmRuleState {
85 94 }
86 95
87 96 public boolean eval(DeviceDataSnapshot data) {
  97 + boolean active = isActive(data.getTs());
88 98 switch (spec.getType()) {
89 99 case SIMPLE:
90   - return eval(alarmRule.getCondition(), data);
  100 + return active && eval(alarmRule.getCondition(), data);
91 101 case DURATION:
92   - return evalDuration(data);
  102 + return evalDuration(data, active);
93 103 case REPEATING:
94   - return evalRepeating(data);
  104 + return evalRepeating(data, active);
95 105 default:
96 106 return false;
97 107 }
98 108 }
99 109
100   - private boolean evalRepeating(DeviceDataSnapshot data) {
101   - boolean eval = eval(alarmRule.getCondition(), data);
102   - if (eval) {
  110 + private boolean isActive(long eventTs) {
  111 + if (eventTs == 0L) {
  112 + eventTs = System.currentTimeMillis();
  113 + }
  114 + if (alarmRule.getSchedule() == null) {
  115 + return true;
  116 + }
  117 + switch (alarmRule.getSchedule().getType()) {
  118 + case ANY_TIME:
  119 + return true;
  120 + case SPECIFIC_TIME:
  121 + return isActiveSpecific((SpecificTimeSchedule) alarmRule.getSchedule(), eventTs);
  122 + case CUSTOM:
  123 + return isActiveCustom((CustomTimeSchedule) alarmRule.getSchedule(), eventTs);
  124 + default:
  125 + throw new RuntimeException("Unsupported schedule type: " + alarmRule.getSchedule().getType());
  126 + }
  127 + }
  128 +
  129 + private boolean isActiveSpecific(SpecificTimeSchedule schedule, long eventTs) {
  130 + ZoneId zoneId = SchedulerUtils.getZoneId(schedule.getTimezone());
  131 + ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(eventTs), zoneId);
  132 + if (schedule.getDaysOfWeek().size() != 7) {
  133 + int dayOfWeek = zdt.getDayOfWeek().getValue();
  134 + if (!schedule.getDaysOfWeek().contains(dayOfWeek)) {
  135 + return false;
  136 + }
  137 + }
  138 + long startOfDay = zdt.toLocalDate().atStartOfDay(zoneId).toInstant().toEpochMilli();
  139 + long msFromStartOfDay = eventTs - startOfDay;
  140 + return schedule.getStartsOn() <= msFromStartOfDay && schedule.getEndsOn() > msFromStartOfDay;
  141 + }
  142 +
  143 + private boolean isActiveCustom(CustomTimeSchedule schedule, long eventTs) {
  144 + ZoneId zoneId = SchedulerUtils.getZoneId(schedule.getTimezone());
  145 + ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(eventTs), zoneId);
  146 + int dayOfWeek = zdt.toLocalDate().getDayOfWeek().getValue();
  147 + for (CustomTimeScheduleItem item : schedule.getItems()) {
  148 + if (item.getDayOfWeek() == dayOfWeek) {
  149 + if (item.isEnabled()) {
  150 + long startOfDay = zdt.toLocalDate().atStartOfDay(zoneId).toInstant().toEpochMilli();
  151 + long msFromStartOfDay = eventTs - startOfDay;
  152 + return item.getStartsOn() <= msFromStartOfDay && item.getEndsOn() > msFromStartOfDay;
  153 + } else {
  154 + return false;
  155 + }
  156 + }
  157 + }
  158 + return false;
  159 + }
  160 +
  161 + private boolean evalRepeating(DeviceDataSnapshot data, boolean active) {
  162 + if (active && eval(alarmRule.getCondition(), data)) {
103 163 state.setEventCount(state.getEventCount() + 1);
104 164 updateFlag = true;
105 165 return state.getEventCount() > requiredRepeats;
... ... @@ -112,9 +172,8 @@ public class AlarmRuleState {
112 172 }
113 173 }
114 174
115   - private boolean evalDuration(DeviceDataSnapshot data) {
116   - boolean eval = eval(alarmRule.getCondition(), data);
117   - if (eval) {
  175 + private boolean evalDuration(DeviceDataSnapshot data, boolean active) {
  176 + if (active && eval(alarmRule.getCondition(), data)) {
118 177 if (state.getLastEventTs() > 0) {
119 178 if (data.getTs() > state.getLastEventTs()) {
120 179 state.setDuration(state.getDuration() + (data.getTs() - state.getLastEventTs()));
... ... @@ -145,7 +204,13 @@ public class AlarmRuleState {
145 204 case DURATION:
146 205 if (requiredDurationInMs > 0 && state.getLastEventTs() > 0 && ts > state.getLastEventTs()) {
147 206 long duration = state.getDuration() + (ts - state.getLastEventTs());
148   - return duration > requiredDurationInMs;
  207 + boolean result = duration > requiredDurationInMs && isActive(ts);
  208 + if (result) {
  209 + state.setLastEventTs(0L);
  210 + state.setDuration(0L);
  211 + updateFlag = true;
  212 + }
  213 + return result;
149 214 }
150 215 default:
151 216 return false;
... ...