7 changed files with 520 additions and 240 deletions
@ -0,0 +1,252 @@ |
|||||
|
/** |
||||
|
* 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.rule.engine.profile; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
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.AlarmRule; |
||||
|
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; |
||||
|
|
||||
|
@Data |
||||
|
public class AlarmRuleState { |
||||
|
|
||||
|
private final AlarmSeverity severity; |
||||
|
private final AlarmRule alarmRule; |
||||
|
private final long requiredDurationInMs; |
||||
|
private long lastEventTs; |
||||
|
private long duration; |
||||
|
|
||||
|
public AlarmRuleState(AlarmSeverity severity, AlarmRule alarmRule) { |
||||
|
this.severity = severity; |
||||
|
this.alarmRule = alarmRule; |
||||
|
if (alarmRule.getCondition().getDurationValue() > 0) { |
||||
|
requiredDurationInMs = alarmRule.getCondition().getDurationUnit().toMillis(alarmRule.getCondition().getDurationValue()); |
||||
|
} else { |
||||
|
requiredDurationInMs = 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public boolean eval(DeviceDataSnapshot data) { |
||||
|
if (requiredDurationInMs > 0) { |
||||
|
boolean eval = eval(alarmRule.getCondition(), data); |
||||
|
if (eval) { |
||||
|
if (lastEventTs > 0) { |
||||
|
if (data.getTs() > lastEventTs) { |
||||
|
duration += data.getTs() - lastEventTs; |
||||
|
lastEventTs = data.getTs(); |
||||
|
} |
||||
|
} else { |
||||
|
lastEventTs = data.getTs(); |
||||
|
duration = 0; |
||||
|
} |
||||
|
return duration > requiredDurationInMs; |
||||
|
} else { |
||||
|
lastEventTs = 0; |
||||
|
duration = 0; |
||||
|
return false; |
||||
|
} |
||||
|
} else { |
||||
|
return eval(alarmRule.getCondition(), data); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public boolean eval(long ts) { |
||||
|
if (requiredDurationInMs > 0 && lastEventTs > 0 && ts > lastEventTs) { |
||||
|
duration += ts - lastEventTs; |
||||
|
return duration > requiredDurationInMs; |
||||
|
} else { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private boolean eval(AlarmCondition condition, DeviceDataSnapshot data) { |
||||
|
boolean eval = true; |
||||
|
for (KeyFilter keyFilter : condition.getCondition()) { |
||||
|
EntityKeyValue value = data.getValue(keyFilter.getKey()); |
||||
|
if (value == null) { |
||||
|
return false; |
||||
|
} |
||||
|
eval = eval && eval(value, keyFilter.getPredicate()); |
||||
|
} |
||||
|
//TODO: use condition duration;
|
||||
|
return eval; |
||||
|
} |
||||
|
|
||||
|
private boolean eval(EntityKeyValue value, KeyFilterPredicate predicate) { |
||||
|
switch (predicate.getType()) { |
||||
|
case STRING: |
||||
|
return evalStrPredicate(value, (StringFilterPredicate) predicate); |
||||
|
case NUMERIC: |
||||
|
return evalNumPredicate(value, (NumericFilterPredicate) predicate); |
||||
|
case COMPLEX: |
||||
|
return evalComplexPredicate(value, (ComplexFilterPredicate) predicate); |
||||
|
case BOOLEAN: |
||||
|
return evalBoolPredicate(value, (BooleanFilterPredicate) predicate); |
||||
|
default: |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private boolean evalComplexPredicate(EntityKeyValue ekv, ComplexFilterPredicate predicate) { |
||||
|
switch (predicate.getOperation()) { |
||||
|
case OR: |
||||
|
for (KeyFilterPredicate kfp : predicate.getPredicates()) { |
||||
|
if (eval(ekv, kfp)) { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
return false; |
||||
|
case AND: |
||||
|
for (KeyFilterPredicate kfp : predicate.getPredicates()) { |
||||
|
if (!eval(ekv, kfp)) { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
return true; |
||||
|
default: |
||||
|
throw new RuntimeException("Operation not supported: " + predicate.getOperation()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private boolean evalBoolPredicate(EntityKeyValue ekv, BooleanFilterPredicate predicate) { |
||||
|
Boolean value; |
||||
|
switch (ekv.getDataType()) { |
||||
|
case LONG: |
||||
|
value = ekv.getLngValue() > 0; |
||||
|
break; |
||||
|
case DOUBLE: |
||||
|
value = ekv.getDblValue() > 0; |
||||
|
break; |
||||
|
case BOOLEAN: |
||||
|
value = ekv.getBoolValue(); |
||||
|
break; |
||||
|
case STRING: |
||||
|
try { |
||||
|
value = Boolean.parseBoolean(ekv.getStrValue()); |
||||
|
break; |
||||
|
} catch (RuntimeException e) { |
||||
|
return false; |
||||
|
} |
||||
|
case JSON: |
||||
|
try { |
||||
|
value = Boolean.parseBoolean(ekv.getJsonValue()); |
||||
|
break; |
||||
|
} catch (RuntimeException e) { |
||||
|
return false; |
||||
|
} |
||||
|
default: |
||||
|
return false; |
||||
|
} |
||||
|
if (value == null) { |
||||
|
return false; |
||||
|
} |
||||
|
switch (predicate.getOperation()) { |
||||
|
case EQUAL: |
||||
|
return value.equals(predicate.getValue().getDefaultValue()); |
||||
|
case NOT_EQUAL: |
||||
|
return !value.equals(predicate.getValue().getDefaultValue()); |
||||
|
default: |
||||
|
throw new RuntimeException("Operation not supported: " + predicate.getOperation()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private boolean evalNumPredicate(EntityKeyValue ekv, NumericFilterPredicate predicate) { |
||||
|
Double value; |
||||
|
switch (ekv.getDataType()) { |
||||
|
case LONG: |
||||
|
value = ekv.getLngValue().doubleValue(); |
||||
|
break; |
||||
|
case DOUBLE: |
||||
|
value = ekv.getDblValue(); |
||||
|
break; |
||||
|
case BOOLEAN: |
||||
|
value = ekv.getBoolValue() ? 1.0 : 0.0; |
||||
|
break; |
||||
|
case STRING: |
||||
|
try { |
||||
|
value = Double.parseDouble(ekv.getStrValue()); |
||||
|
break; |
||||
|
} catch (RuntimeException e) { |
||||
|
return false; |
||||
|
} |
||||
|
case JSON: |
||||
|
try { |
||||
|
value = Double.parseDouble(ekv.getJsonValue()); |
||||
|
break; |
||||
|
} catch (RuntimeException e) { |
||||
|
return false; |
||||
|
} |
||||
|
default: |
||||
|
return false; |
||||
|
} |
||||
|
if (value == null) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
Double predicateValue = predicate.getValue().getDefaultValue(); |
||||
|
switch (predicate.getOperation()) { |
||||
|
case NOT_EQUAL: |
||||
|
return !value.equals(predicateValue); |
||||
|
case EQUAL: |
||||
|
return value.equals(predicateValue); |
||||
|
case GREATER: |
||||
|
return value > predicateValue; |
||||
|
case GREATER_OR_EQUAL: |
||||
|
return value >= predicateValue; |
||||
|
case LESS: |
||||
|
return value < predicateValue; |
||||
|
case LESS_OR_EQUAL: |
||||
|
return value <= predicateValue; |
||||
|
default: |
||||
|
throw new RuntimeException("Operation not supported: " + predicate.getOperation()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private boolean evalStrPredicate(EntityKeyValue ekv, StringFilterPredicate predicate) { |
||||
|
String val; |
||||
|
String predicateValue; |
||||
|
if (predicate.isIgnoreCase()) { |
||||
|
val = ekv.getStrValue().toLowerCase(); |
||||
|
predicateValue = predicate.getValue().getDefaultValue().toLowerCase(); |
||||
|
} else { |
||||
|
val = ekv.getStrValue(); |
||||
|
predicateValue = predicate.getValue().getDefaultValue(); |
||||
|
} |
||||
|
switch (predicate.getOperation()) { |
||||
|
case CONTAINS: |
||||
|
return val.contains(predicateValue); |
||||
|
case EQUAL: |
||||
|
return val.equals(predicateValue); |
||||
|
case STARTS_WITH: |
||||
|
return val.startsWith(predicateValue); |
||||
|
case ENDS_WITH: |
||||
|
return val.endsWith(predicateValue); |
||||
|
case NOT_EQUAL: |
||||
|
return !val.equals(predicateValue); |
||||
|
case NOT_CONTAINS: |
||||
|
return !val.contains(predicateValue); |
||||
|
default: |
||||
|
throw new RuntimeException("Operation not supported: " + predicate.getOperation()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue