Constant filters for device profile

This commit is contained in:
Andrii Shvaika 2021-02-26 14:14:11 +02:00 committed by Andrew Shvayka
parent 7237497946
commit 3cd964327a
12 changed files with 417 additions and 184 deletions

View File

@ -36,6 +36,9 @@ import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; 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.AlarmConditionFilter;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
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.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
@ -290,16 +293,16 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
AlarmCondition temperatureCondition = new AlarmCondition(); AlarmCondition temperatureCondition = new AlarmCondition();
temperatureCondition.setSpec(new SimpleAlarmConditionSpec()); temperatureCondition.setSpec(new SimpleAlarmConditionSpec());
KeyFilter temperatureAlarmFlagAttributeFilter = new KeyFilter(); AlarmConditionFilter temperatureAlarmFlagAttributeFilter = new AlarmConditionFilter();
temperatureAlarmFlagAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperatureAlarmFlag")); temperatureAlarmFlagAttributeFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, "temperatureAlarmFlag"));
temperatureAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN); temperatureAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);
BooleanFilterPredicate temperatureAlarmFlagAttributePredicate = new BooleanFilterPredicate(); BooleanFilterPredicate temperatureAlarmFlagAttributePredicate = new BooleanFilterPredicate();
temperatureAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); temperatureAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
temperatureAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE)); temperatureAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));
temperatureAlarmFlagAttributeFilter.setPredicate(temperatureAlarmFlagAttributePredicate); temperatureAlarmFlagAttributeFilter.setPredicate(temperatureAlarmFlagAttributePredicate);
KeyFilter temperatureTimeseriesFilter = new KeyFilter(); AlarmConditionFilter temperatureTimeseriesFilter = new AlarmConditionFilter();
temperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); temperatureTimeseriesFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
temperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); temperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate temperatureTimeseriesFilterPredicate = new NumericFilterPredicate(); NumericFilterPredicate temperatureTimeseriesFilterPredicate = new NumericFilterPredicate();
temperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); temperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
@ -317,8 +320,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
AlarmCondition clearTemperatureCondition = new AlarmCondition(); AlarmCondition clearTemperatureCondition = new AlarmCondition();
clearTemperatureCondition.setSpec(new SimpleAlarmConditionSpec()); clearTemperatureCondition.setSpec(new SimpleAlarmConditionSpec());
KeyFilter clearTemperatureTimeseriesFilter = new KeyFilter(); AlarmConditionFilter clearTemperatureTimeseriesFilter = new AlarmConditionFilter();
clearTemperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); clearTemperatureTimeseriesFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
clearTemperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); clearTemperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate clearTemperatureTimeseriesFilterPredicate = new NumericFilterPredicate(); NumericFilterPredicate clearTemperatureTimeseriesFilterPredicate = new NumericFilterPredicate();
clearTemperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL); clearTemperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL);
@ -340,16 +343,16 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
AlarmCondition humidityCondition = new AlarmCondition(); AlarmCondition humidityCondition = new AlarmCondition();
humidityCondition.setSpec(new SimpleAlarmConditionSpec()); humidityCondition.setSpec(new SimpleAlarmConditionSpec());
KeyFilter humidityAlarmFlagAttributeFilter = new KeyFilter(); AlarmConditionFilter humidityAlarmFlagAttributeFilter = new AlarmConditionFilter();
humidityAlarmFlagAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "humidityAlarmFlag")); humidityAlarmFlagAttributeFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, "humidityAlarmFlag"));
humidityAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN); humidityAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);
BooleanFilterPredicate humidityAlarmFlagAttributePredicate = new BooleanFilterPredicate(); BooleanFilterPredicate humidityAlarmFlagAttributePredicate = new BooleanFilterPredicate();
humidityAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); humidityAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
humidityAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE)); humidityAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));
humidityAlarmFlagAttributeFilter.setPredicate(humidityAlarmFlagAttributePredicate); humidityAlarmFlagAttributeFilter.setPredicate(humidityAlarmFlagAttributePredicate);
KeyFilter humidityTimeseriesFilter = new KeyFilter(); AlarmConditionFilter humidityTimeseriesFilter = new AlarmConditionFilter();
humidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity")); humidityTimeseriesFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "humidity"));
humidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); humidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate humidityTimeseriesFilterPredicate = new NumericFilterPredicate(); NumericFilterPredicate humidityTimeseriesFilterPredicate = new NumericFilterPredicate();
humidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS); humidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS);
@ -368,8 +371,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
AlarmCondition clearHumidityCondition = new AlarmCondition(); AlarmCondition clearHumidityCondition = new AlarmCondition();
clearHumidityCondition.setSpec(new SimpleAlarmConditionSpec()); clearHumidityCondition.setSpec(new SimpleAlarmConditionSpec());
KeyFilter clearHumidityTimeseriesFilter = new KeyFilter(); AlarmConditionFilter clearHumidityTimeseriesFilter = new AlarmConditionFilter();
clearHumidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity")); clearHumidityTimeseriesFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "humidity"));
clearHumidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); clearHumidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate clearHumidityTimeseriesFilterPredicate = new NumericFilterPredicate(); NumericFilterPredicate clearHumidityTimeseriesFilterPredicate = new NumericFilterPredicate();
clearHumidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL); clearHumidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL);

View File

@ -26,7 +26,7 @@ import java.util.concurrent.TimeUnit;
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class AlarmCondition { public class AlarmCondition {
private List<KeyFilter> condition; private List<AlarmConditionFilter> condition;
private AlarmConditionSpec spec; private AlarmConditionSpec spec;
} }

View File

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2021 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.data.device.profile;
import lombok.Data;
import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
@Data
public class AlarmConditionFilter {
private AlarmConditionFilterKey key;
private EntityKeyValueType valueType;
private Object value;
private KeyFilterPredicate predicate;
}

View File

@ -0,0 +1,26 @@
/**
* Copyright © 2016-2021 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.data.device.profile;
import lombok.Data;
@Data
public class AlarmConditionFilterKey {
private final AlarmConditionKeyType type;
private final String key;
}

View File

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2021 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.data.device.profile;
public enum AlarmConditionKeyType {
ATTRIBUTE,
TIME_SERIES,
ENTITY_FIELD,
CONSTANT
}

View File

@ -16,9 +16,13 @@
package org.thingsboard.rule.engine.profile; package org.thingsboard.rule.engine.profile;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.profile.state.PersistedAlarmRuleState; import org.thingsboard.rule.engine.profile.state.PersistedAlarmRuleState;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; 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.AlarmConditionFilter;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
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.CustomTimeSchedule;
@ -45,6 +49,7 @@ import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
@Data @Data
@Slf4j
class AlarmRuleState { class AlarmRuleState {
private final AlarmSeverity severity; private final AlarmSeverity severity;
@ -52,12 +57,12 @@ class AlarmRuleState {
private final AlarmConditionSpec spec; private final AlarmConditionSpec spec;
private final long requiredDurationInMs; private final long requiredDurationInMs;
private final long requiredRepeats; private final long requiredRepeats;
private final Set<EntityKey> entityKeys; private final Set<AlarmConditionFilterKey> entityKeys;
private PersistedAlarmRuleState state; private PersistedAlarmRuleState state;
private boolean updateFlag; private boolean updateFlag;
private final DynamicPredicateValueCtx dynamicPredicateValueCtx; private final DynamicPredicateValueCtx dynamicPredicateValueCtx;
AlarmRuleState(AlarmSeverity severity, AlarmRule alarmRule, Set<EntityKey> entityKeys, PersistedAlarmRuleState state, DynamicPredicateValueCtx dynamicPredicateValueCtx) { AlarmRuleState(AlarmSeverity severity, AlarmRule alarmRule, Set<AlarmConditionFilterKey> entityKeys, PersistedAlarmRuleState state, DynamicPredicateValueCtx dynamicPredicateValueCtx) {
this.severity = severity; this.severity = severity;
this.alarmRule = alarmRule; this.alarmRule = alarmRule;
this.entityKeys = entityKeys; this.entityKeys = entityKeys;
@ -84,8 +89,8 @@ class AlarmRuleState {
this.dynamicPredicateValueCtx = dynamicPredicateValueCtx; this.dynamicPredicateValueCtx = dynamicPredicateValueCtx;
} }
public boolean validateTsUpdate(Set<EntityKey> changedKeys) { public boolean validateTsUpdate(Set<AlarmConditionFilterKey> changedKeys) {
for (EntityKey key : changedKeys) { for (AlarmConditionFilterKey key : changedKeys) {
if (entityKeys.contains(key)) { if (entityKeys.contains(key)) {
return true; return true;
} }
@ -93,18 +98,14 @@ class AlarmRuleState {
return false; return false;
} }
public boolean validateAttrUpdate(Set<EntityKey> changedKeys) { public boolean validateAttrUpdate(Set<AlarmConditionFilterKey> changedKeys) {
//If the attribute was updated, but no new telemetry arrived - we ignore this until new telemetry is there. //If the attribute was updated, but no new telemetry arrived - we ignore this until new telemetry is there.
for (EntityKey key : entityKeys) { for (AlarmConditionFilterKey key : entityKeys) {
if (key.getType().equals(EntityKeyType.TIME_SERIES)) { if (key.getType().equals(AlarmConditionKeyType.TIME_SERIES)) {
return false; return false;
} }
} }
for (EntityKey key : changedKeys) { for (AlarmConditionFilterKey key : changedKeys) {
EntityKeyType keyType = key.getType();
if (EntityKeyType.CLIENT_ATTRIBUTE.equals(keyType) || EntityKeyType.SERVER_ATTRIBUTE.equals(keyType) || EntityKeyType.SHARED_ATTRIBUTE.equals(keyType)) {
key = new EntityKey(EntityKeyType.ATTRIBUTE, key.getKey());
}
if (entityKeys.contains(key)) { if (entityKeys.contains(key)) {
return true; return true;
} }
@ -259,16 +260,46 @@ class AlarmRuleState {
private boolean eval(AlarmCondition condition, DataSnapshot data) { private boolean eval(AlarmCondition condition, DataSnapshot data) {
boolean eval = true; boolean eval = true;
for (KeyFilter keyFilter : condition.getCondition()) { for (var filter : condition.getCondition()) {
EntityKeyValue value = data.getValue(keyFilter.getKey()); EntityKeyValue value;
if (filter.getKey().getType().equals(AlarmConditionKeyType.CONSTANT)) {
try {
value = getConstantValue(filter);
} catch (RuntimeException e) {
log.warn("Failed to parse constant value from filter: {}", filter, e);
value = null;
}
} else {
value = data.getValue(filter.getKey());
}
if (value == null) { if (value == null) {
return false; return false;
} }
eval = eval && eval(data, value, keyFilter.getPredicate()); eval = eval && eval(data, value, filter.getPredicate());
} }
return eval; return eval;
} }
private EntityKeyValue getConstantValue(AlarmConditionFilter filter) {
EntityKeyValue value = new EntityKeyValue();
String valueStr = filter.getValue().toString();
switch (filter.getValueType()) {
case STRING:
value.setStrValue(valueStr);
break;
case DATE_TIME:
value.setLngValue(Long.valueOf(valueStr));
break;
case NUMERIC:
value.setDblValue(Double.valueOf(valueStr));
break;
case BOOLEAN:
value.setBoolValue(Boolean.valueOf(valueStr));
break;
}
return value;
}
private boolean eval(DataSnapshot data, EntityKeyValue value, KeyFilterPredicate predicate) { private boolean eval(DataSnapshot data, EntityKeyValue value, KeyFilterPredicate predicate) {
switch (predicate.getType()) { switch (predicate.getType()) {
case STRING: case STRING:
@ -389,22 +420,13 @@ class AlarmRuleState {
if (value.getDynamicValue() != null) { if (value.getDynamicValue() != null) {
switch (value.getDynamicValue().getSourceType()) { switch (value.getDynamicValue().getSourceType()) {
case CURRENT_DEVICE: case CURRENT_DEVICE:
ekv = data.getValue(new EntityKey(EntityKeyType.ATTRIBUTE, value.getDynamicValue().getSourceAttribute())); ekv = data.getValue(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, value.getDynamicValue().getSourceAttribute()));
if (ekv == null) { if (ekv != null || !value.getDynamicValue().isInherit()) {
ekv = data.getValue(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, value.getDynamicValue().getSourceAttribute()));
if (ekv == null) {
ekv = data.getValue(new EntityKey(EntityKeyType.SHARED_ATTRIBUTE, value.getDynamicValue().getSourceAttribute()));
if (ekv == null) {
ekv = data.getValue(new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, value.getDynamicValue().getSourceAttribute()));
}
}
}
if(ekv != null || !value.getDynamicValue().isInherit()) {
break; break;
} }
case CURRENT_CUSTOMER: case CURRENT_CUSTOMER:
ekv = dynamicPredicateValueCtx.getCustomerValue(value.getDynamicValue().getSourceAttribute()); ekv = dynamicPredicateValueCtx.getCustomerValue(value.getDynamicValue().getSourceAttribute());
if(ekv != null || !value.getDynamicValue().isInherit()) { if (ekv != null || !value.getDynamicValue().isInherit()) {
break; break;
} }
case CURRENT_TENANT: case CURRENT_TENANT:

View File

@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
@ -138,9 +139,9 @@ class AlarmState {
public boolean validateUpdate(SnapshotUpdate update, AlarmRuleState state) { public boolean validateUpdate(SnapshotUpdate update, AlarmRuleState state) {
if (update != null) { if (update != null) {
//Check that the update type and that keys match. //Check that the update type and that keys match.
if (update.getType().equals(EntityKeyType.TIME_SERIES)) { if (update.getType().equals(AlarmConditionKeyType.TIME_SERIES)) {
return state.validateTsUpdate(update.getKeys()); return state.validateTsUpdate(update.getKeys());
} else if (update.getType().equals(EntityKeyType.ATTRIBUTE)) { } else if (update.getType().equals(AlarmConditionKeyType.ATTRIBUTE)) {
return state.validateAttrUpdate(update.getKeys()); return state.validateAttrUpdate(update.getKeys());
} }
} }
@ -252,7 +253,7 @@ class AlarmState {
String alarmDetailsStr = ruleState.getAlarmRule().getAlarmDetails(); String alarmDetailsStr = ruleState.getAlarmRule().getAlarmDetails();
if (StringUtils.isNotEmpty(alarmDetailsStr)) { if (StringUtils.isNotEmpty(alarmDetailsStr)) {
for (KeyFilter keyFilter : ruleState.getAlarmRule().getCondition().getCondition()) { for (var keyFilter : ruleState.getAlarmRule().getCondition().getCondition()) {
EntityKeyValue entityKeyValue = dataSnapshot.getValue(keyFilter.getKey()); EntityKeyValue entityKeyValue = dataSnapshot.getValue(keyFilter.getKey());
alarmDetailsStr = alarmDetailsStr.replaceAll(String.format("\\$\\{%s}", keyFilter.getKey().getKey()), getValueAsString(entityKeyValue)); alarmDetailsStr = alarmDetailsStr.replaceAll(String.format("\\$\\{%s}", keyFilter.getKey().getKey()), getValueAsString(entityKeyValue));
} }

View File

@ -17,6 +17,8 @@ package org.thingsboard.rule.engine.profile;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.EntityKeyType;
@ -30,55 +32,42 @@ class DataSnapshot {
@Getter @Getter
@Setter @Setter
private long ts; private long ts;
private final Set<EntityKey> keys; private final Set<AlarmConditionFilterKey> keys;
private final Map<EntityKey, EntityKeyValue> values = new ConcurrentHashMap<>(); private final Map<AlarmConditionFilterKey, EntityKeyValue> values = new ConcurrentHashMap<>();
DataSnapshot(Set<EntityKey> entityKeysToFetch) { DataSnapshot(Set<AlarmConditionFilterKey> entityKeysToFetch) {
this.keys = entityKeysToFetch; this.keys = entityKeysToFetch;
} }
static AlarmConditionFilterKey toConditionKey(EntityKey key) {
return new AlarmConditionFilterKey(toConditionKeyType(key.getType()), key.getKey());
}
static AlarmConditionKeyType toConditionKeyType(EntityKeyType keyType) {
switch (keyType) {
case ATTRIBUTE:
case SERVER_ATTRIBUTE:
case SHARED_ATTRIBUTE:
case CLIENT_ATTRIBUTE:
return AlarmConditionKeyType.ATTRIBUTE;
case TIME_SERIES:
return AlarmConditionKeyType.TIME_SERIES;
case ENTITY_FIELD:
return AlarmConditionKeyType.ENTITY_FIELD;
default:
throw new RuntimeException("Not supported entity key: " + keyType.name());
}
}
void removeValue(EntityKey key) { void removeValue(EntityKey key) {
switch (key.getType()) { values.remove(toConditionKey(key));
case ATTRIBUTE:
values.remove(key);
values.remove(getAttrKey(key, EntityKeyType.CLIENT_ATTRIBUTE));
values.remove(getAttrKey(key, EntityKeyType.SHARED_ATTRIBUTE));
values.remove(getAttrKey(key, EntityKeyType.SERVER_ATTRIBUTE));
break;
case CLIENT_ATTRIBUTE:
case SHARED_ATTRIBUTE:
case SERVER_ATTRIBUTE:
values.remove(key);
values.remove(getAttrKey(key, EntityKeyType.ATTRIBUTE));
break;
default:
values.remove(key);
}
} }
boolean putValue(EntityKey key, long newTs, EntityKeyValue value) { boolean putValue(AlarmConditionFilterKey key, long newTs, EntityKeyValue value) {
boolean updateOfTs = ts != newTs; return putIfKeyExists(key, value, ts != newTs);
boolean result = false;
switch (key.getType()) {
case ATTRIBUTE:
result |= putIfKeyExists(key, value, updateOfTs);
result |= putIfKeyExists(getAttrKey(key, EntityKeyType.CLIENT_ATTRIBUTE), value, updateOfTs);
result |= putIfKeyExists(getAttrKey(key, EntityKeyType.SHARED_ATTRIBUTE), value, updateOfTs);
result |= putIfKeyExists(getAttrKey(key, EntityKeyType.SERVER_ATTRIBUTE), value, updateOfTs);
break;
case CLIENT_ATTRIBUTE:
case SHARED_ATTRIBUTE:
case SERVER_ATTRIBUTE:
result |= putIfKeyExists(key, value, updateOfTs);
result |= putIfKeyExists(getAttrKey(key, EntityKeyType.ATTRIBUTE), value, updateOfTs);
break;
default:
result |= putIfKeyExists(key, value, updateOfTs);
}
return result;
} }
private boolean putIfKeyExists(EntityKey key, EntityKeyValue value, boolean updateOfTs) { private boolean putIfKeyExists(AlarmConditionFilterKey key, EntityKeyValue value, boolean updateOfTs) {
if (keys.contains(key)) { if (keys.contains(key)) {
EntityKeyValue oldValue = values.put(key, value); EntityKeyValue oldValue = values.put(key, value);
if (updateOfTs) { if (updateOfTs) {
@ -91,25 +80,7 @@ class DataSnapshot {
} }
} }
EntityKeyValue getValue(EntityKey key) { EntityKeyValue getValue(AlarmConditionFilterKey key) {
if (EntityKeyType.ATTRIBUTE.equals(key.getType())) {
EntityKeyValue value = values.get(key);
if (value == null) {
value = values.get(getAttrKey(key, EntityKeyType.CLIENT_ATTRIBUTE));
if (value == null) {
value = values.get(getAttrKey(key, EntityKeyType.SHARED_ATTRIBUTE));
if (value == null) {
value = values.get(getAttrKey(key, EntityKeyType.SERVER_ATTRIBUTE));
}
}
}
return value;
} else {
return values.get(key); return values.get(key);
} }
}
private EntityKey getAttrKey(EntityKey key, EntityKeyType clientAttribute) {
return new EntityKey(clientAttribute, key.getKey());
}
} }

View File

@ -26,6 +26,8 @@ import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
@ -97,10 +99,10 @@ class DeviceState {
} }
public void updateProfile(TbContext ctx, DeviceProfile deviceProfile) throws ExecutionException, InterruptedException { public void updateProfile(TbContext ctx, DeviceProfile deviceProfile) throws ExecutionException, InterruptedException {
Set<EntityKey> oldKeys = this.deviceProfile.getEntityKeys(); Set<AlarmConditionFilterKey> oldKeys = this.deviceProfile.getEntityKeys();
this.deviceProfile.updateDeviceProfile(deviceProfile); this.deviceProfile.updateDeviceProfile(deviceProfile);
if (latestValues != null) { if (latestValues != null) {
Set<EntityKey> keysToFetch = new HashSet<>(this.deviceProfile.getEntityKeys()); Set<AlarmConditionFilterKey> keysToFetch = new HashSet<>(this.deviceProfile.getEntityKeys());
keysToFetch.removeAll(oldKeys); keysToFetch.removeAll(oldKeys);
if (!keysToFetch.isEmpty()) { if (!keysToFetch.isEmpty()) {
addEntityKeysToSnapshot(ctx, deviceId, keysToFetch, latestValues); addEntityKeysToSnapshot(ctx, deviceId, keysToFetch, latestValues);
@ -260,29 +262,29 @@ class DeviceState {
} }
private SnapshotUpdate merge(DataSnapshot latestValues, Long newTs, List<KvEntry> data) { private SnapshotUpdate merge(DataSnapshot latestValues, Long newTs, List<KvEntry> data) {
Set<EntityKey> keys = new HashSet<>(); Set<AlarmConditionFilterKey> keys = new HashSet<>();
for (KvEntry entry : data) { for (KvEntry entry : data) {
EntityKey entityKey = new EntityKey(EntityKeyType.TIME_SERIES, entry.getKey()); AlarmConditionFilterKey entityKey = new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, entry.getKey());
if (latestValues.putValue(entityKey, newTs, toEntityValue(entry))) { if (latestValues.putValue(entityKey, newTs, toEntityValue(entry))) {
keys.add(entityKey); keys.add(entityKey);
} }
} }
latestValues.setTs(newTs); latestValues.setTs(newTs);
return new SnapshotUpdate(EntityKeyType.TIME_SERIES, keys); return new SnapshotUpdate(AlarmConditionKeyType.TIME_SERIES, keys);
} }
private SnapshotUpdate merge(DataSnapshot latestValues, Set<AttributeKvEntry> attributes, String scope) { private SnapshotUpdate merge(DataSnapshot latestValues, Set<AttributeKvEntry> attributes, String scope) {
long newTs = 0; long newTs = 0;
Set<EntityKey> keys = new HashSet<>(); Set<AlarmConditionFilterKey> keys = new HashSet<>();
for (AttributeKvEntry entry : attributes) { for (AttributeKvEntry entry : attributes) {
newTs = Math.max(newTs, entry.getLastUpdateTs()); newTs = Math.max(newTs, entry.getLastUpdateTs());
EntityKey entityKey = new EntityKey(getKeyTypeFromScope(scope), entry.getKey()); AlarmConditionFilterKey entityKey = new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, entry.getKey());
if (latestValues.putValue(entityKey, newTs, toEntityValue(entry))) { if (latestValues.putValue(entityKey, newTs, toEntityValue(entry))) {
keys.add(entityKey); keys.add(entityKey);
} }
} }
latestValues.setTs(newTs); latestValues.setTs(newTs);
return new SnapshotUpdate(EntityKeyType.ATTRIBUTE, keys); return new SnapshotUpdate(AlarmConditionKeyType.ATTRIBUTE, keys);
} }
private static EntityKeyType getKeyTypeFromScope(String scope) { private static EntityKeyType getKeyTypeFromScope(String scope) {
@ -298,37 +300,22 @@ class DeviceState {
} }
private DataSnapshot fetchLatestValues(TbContext ctx, EntityId originator) throws ExecutionException, InterruptedException { private DataSnapshot fetchLatestValues(TbContext ctx, EntityId originator) throws ExecutionException, InterruptedException {
Set<EntityKey> entityKeysToFetch = deviceProfile.getEntityKeys(); Set<AlarmConditionFilterKey> entityKeysToFetch = deviceProfile.getEntityKeys();
DataSnapshot result = new DataSnapshot(entityKeysToFetch); DataSnapshot result = new DataSnapshot(entityKeysToFetch);
addEntityKeysToSnapshot(ctx, originator, entityKeysToFetch, result); addEntityKeysToSnapshot(ctx, originator, entityKeysToFetch, result);
return result; return result;
} }
private void addEntityKeysToSnapshot(TbContext ctx, EntityId originator, Set<EntityKey> entityKeysToFetch, DataSnapshot result) throws InterruptedException, ExecutionException { private void addEntityKeysToSnapshot(TbContext ctx, EntityId originator, Set<AlarmConditionFilterKey> entityKeysToFetch, DataSnapshot result) throws InterruptedException, ExecutionException {
Set<String> serverAttributeKeys = new HashSet<>(); Set<String> attributeKeys = new HashSet<>();
Set<String> clientAttributeKeys = new HashSet<>();
Set<String> sharedAttributeKeys = new HashSet<>();
Set<String> commonAttributeKeys = new HashSet<>();
Set<String> latestTsKeys = new HashSet<>(); Set<String> latestTsKeys = new HashSet<>();
Device device = null; Device device = null;
for (EntityKey entityKey : entityKeysToFetch) { for (AlarmConditionFilterKey entityKey : entityKeysToFetch) {
String key = entityKey.getKey(); String key = entityKey.getKey();
switch (entityKey.getType()) { switch (entityKey.getType()) {
case SERVER_ATTRIBUTE:
serverAttributeKeys.add(key);
break;
case CLIENT_ATTRIBUTE:
clientAttributeKeys.add(key);
break;
case SHARED_ATTRIBUTE:
sharedAttributeKeys.add(key);
break;
case ATTRIBUTE: case ATTRIBUTE:
serverAttributeKeys.add(key); attributeKeys.add(key);
clientAttributeKeys.add(key);
sharedAttributeKeys.add(key);
commonAttributeKeys.add(key);
break; break;
case TIME_SERIES: case TIME_SERIES:
latestTsKeys.add(key); latestTsKeys.add(key);
@ -361,32 +348,22 @@ class DeviceState {
List<TsKvEntry> data = ctx.getTimeseriesService().findLatest(ctx.getTenantId(), originator, latestTsKeys).get(); List<TsKvEntry> data = ctx.getTimeseriesService().findLatest(ctx.getTenantId(), originator, latestTsKeys).get();
for (TsKvEntry entry : data) { for (TsKvEntry entry : data) {
if (entry.getValue() != null) { if (entry.getValue() != null) {
result.putValue(new EntityKey(EntityKeyType.TIME_SERIES, entry.getKey()), entry.getTs(), toEntityValue(entry)); result.putValue(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, entry.getKey()), entry.getTs(), toEntityValue(entry));
} }
} }
} }
if (!clientAttributeKeys.isEmpty()) { if (!attributeKeys.isEmpty()) {
addToSnapshot(result, commonAttributeKeys, addToSnapshot(result, ctx.getAttributesService().find(ctx.getTenantId(), originator, DataConstants.CLIENT_SCOPE, attributeKeys).get());
ctx.getAttributesService().find(ctx.getTenantId(), originator, DataConstants.CLIENT_SCOPE, clientAttributeKeys).get()); addToSnapshot(result, ctx.getAttributesService().find(ctx.getTenantId(), originator, DataConstants.SHARED_SCOPE, attributeKeys).get());
} addToSnapshot(result, ctx.getAttributesService().find(ctx.getTenantId(), originator, DataConstants.SERVER_SCOPE, attributeKeys).get());
if (!sharedAttributeKeys.isEmpty()) {
addToSnapshot(result, commonAttributeKeys,
ctx.getAttributesService().find(ctx.getTenantId(), originator, DataConstants.SHARED_SCOPE, sharedAttributeKeys).get());
}
if (!serverAttributeKeys.isEmpty()) {
addToSnapshot(result, commonAttributeKeys,
ctx.getAttributesService().find(ctx.getTenantId(), originator, DataConstants.SERVER_SCOPE, serverAttributeKeys).get());
} }
} }
private void addToSnapshot(DataSnapshot snapshot, Set<String> commonAttributeKeys, List<AttributeKvEntry> data) { private void addToSnapshot(DataSnapshot snapshot, List<AttributeKvEntry> data) {
for (AttributeKvEntry entry : data) { for (AttributeKvEntry entry : data) {
if (entry.getValue() != null) { if (entry.getValue() != null) {
EntityKeyValue value = toEntityValue(entry); EntityKeyValue value = toEntityValue(entry);
snapshot.putValue(new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, entry.getKey()), entry.getLastUpdateTs(), value); snapshot.putValue(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, entry.getKey()), entry.getLastUpdateTs(), value);
if (commonAttributeKeys.contains(entry.getKey())) {
snapshot.putValue(new EntityKey(EntityKeyType.ATTRIBUTE, entry.getKey()), entry.getLastUpdateTs(), value);
}
} }
} }
} }

View File

@ -19,6 +19,9 @@ import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
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.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
@ -50,10 +53,10 @@ class ProfileState {
@Getter(AccessLevel.PACKAGE) @Getter(AccessLevel.PACKAGE)
private final List<DeviceProfileAlarm> alarmSettings = new CopyOnWriteArrayList<>(); private final List<DeviceProfileAlarm> alarmSettings = new CopyOnWriteArrayList<>();
@Getter(AccessLevel.PACKAGE) @Getter(AccessLevel.PACKAGE)
private final Set<EntityKey> entityKeys = ConcurrentHashMap.newKeySet(); private final Set<AlarmConditionFilterKey> entityKeys = ConcurrentHashMap.newKeySet();
private final Map<String, Map<AlarmSeverity, Set<EntityKey>>> alarmCreateKeys = new HashMap<>(); private final Map<String, Map<AlarmSeverity, Set<AlarmConditionFilterKey>>> alarmCreateKeys = new HashMap<>();
private final Map<String, Set<EntityKey>> alarmClearKeys = new HashMap<>(); private final Map<String, Set<AlarmConditionFilterKey>> alarmClearKeys = new HashMap<>();
ProfileState(DeviceProfile deviceProfile) { ProfileState(DeviceProfile deviceProfile) {
updateDeviceProfile(deviceProfile); updateDeviceProfile(deviceProfile);
@ -68,18 +71,18 @@ class ProfileState {
if (deviceProfile.getProfileData().getAlarms() != null) { if (deviceProfile.getProfileData().getAlarms() != null) {
alarmSettings.addAll(deviceProfile.getProfileData().getAlarms()); alarmSettings.addAll(deviceProfile.getProfileData().getAlarms());
for (DeviceProfileAlarm alarm : deviceProfile.getProfileData().getAlarms()) { for (DeviceProfileAlarm alarm : deviceProfile.getProfileData().getAlarms()) {
Map<AlarmSeverity, Set<EntityKey>> createAlarmKeys = alarmCreateKeys.computeIfAbsent(alarm.getId(), id -> new HashMap<>()); Map<AlarmSeverity, Set<AlarmConditionFilterKey>> createAlarmKeys = alarmCreateKeys.computeIfAbsent(alarm.getId(), id -> new HashMap<>());
alarm.getCreateRules().forEach(((severity, alarmRule) -> { alarm.getCreateRules().forEach(((severity, alarmRule) -> {
Set<EntityKey> ruleKeys = createAlarmKeys.computeIfAbsent(severity, id -> new HashSet<>()); var ruleKeys = createAlarmKeys.computeIfAbsent(severity, id -> new HashSet<>());
for (KeyFilter keyFilter : alarmRule.getCondition().getCondition()) { for (var keyFilter : alarmRule.getCondition().getCondition()) {
entityKeys.add(keyFilter.getKey()); entityKeys.add(keyFilter.getKey());
ruleKeys.add(keyFilter.getKey()); ruleKeys.add(keyFilter.getKey());
addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, ruleKeys); addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, ruleKeys);
} }
})); }));
if (alarm.getClearRule() != null) { if (alarm.getClearRule() != null) {
Set<EntityKey> clearAlarmKeys = alarmClearKeys.computeIfAbsent(alarm.getId(), id -> new HashSet<>()); var clearAlarmKeys = alarmClearKeys.computeIfAbsent(alarm.getId(), id -> new HashSet<>());
for (KeyFilter keyFilter : alarm.getClearRule().getCondition().getCondition()) { for (var keyFilter : alarm.getClearRule().getCondition().getCondition()) {
entityKeys.add(keyFilter.getKey()); entityKeys.add(keyFilter.getKey());
clearAlarmKeys.add(keyFilter.getKey()); clearAlarmKeys.add(keyFilter.getKey());
addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, clearAlarmKeys); addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, clearAlarmKeys);
@ -89,7 +92,7 @@ class ProfileState {
} }
} }
private void addDynamicValuesRecursively(KeyFilterPredicate predicate, Set<EntityKey> entityKeys, Set<EntityKey> ruleKeys) { private void addDynamicValuesRecursively(KeyFilterPredicate predicate, Set<AlarmConditionFilterKey> entityKeys, Set<AlarmConditionFilterKey> ruleKeys) {
switch (predicate.getType()) { switch (predicate.getType()) {
case STRING: case STRING:
case NUMERIC: case NUMERIC:
@ -98,7 +101,7 @@ class ProfileState {
if (value != null && (value.getSourceType() == DynamicValueSourceType.CURRENT_TENANT || if (value != null && (value.getSourceType() == DynamicValueSourceType.CURRENT_TENANT ||
value.getSourceType() == DynamicValueSourceType.CURRENT_CUSTOMER || value.getSourceType() == DynamicValueSourceType.CURRENT_CUSTOMER ||
value.getSourceType() == DynamicValueSourceType.CURRENT_DEVICE)) { value.getSourceType() == DynamicValueSourceType.CURRENT_DEVICE)) {
EntityKey entityKey = new EntityKey(EntityKeyType.ATTRIBUTE, value.getSourceAttribute()); AlarmConditionFilterKey entityKey = new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, value.getSourceAttribute());
entityKeys.add(entityKey); entityKeys.add(entityKey);
ruleKeys.add(entityKey); ruleKeys.add(entityKey);
} }
@ -115,12 +118,12 @@ class ProfileState {
return deviceProfile.getId(); return deviceProfile.getId();
} }
Set<EntityKey> getCreateAlarmKeys(String id, AlarmSeverity severity) { Set<AlarmConditionFilterKey> getCreateAlarmKeys(String id, AlarmSeverity severity) {
Map<AlarmSeverity, Set<EntityKey>> sKeys = alarmCreateKeys.get(id); Map<AlarmSeverity, Set<AlarmConditionFilterKey>> sKeys = alarmCreateKeys.get(id);
if (sKeys == null) { if (sKeys == null) {
return Collections.emptySet(); return Collections.emptySet();
} else { } else {
Set<EntityKey> keys = sKeys.get(severity); Set<AlarmConditionFilterKey> keys = sKeys.get(severity);
if (keys == null) { if (keys == null) {
return Collections.emptySet(); return Collections.emptySet();
} else { } else {
@ -129,8 +132,8 @@ class ProfileState {
} }
} }
Set<EntityKey> getClearAlarmKeys(String id) { Set<AlarmConditionFilterKey> getClearAlarmKeys(String id) {
Set<EntityKey> keys = alarmClearKeys.get(id); Set<AlarmConditionFilterKey> keys = alarmClearKeys.get(id);
if (keys == null) { if (keys == null) {
return Collections.emptySet(); return Collections.emptySet();
} else { } else {

View File

@ -16,6 +16,8 @@
package org.thingsboard.rule.engine.profile; package org.thingsboard.rule.engine.profile;
import lombok.Getter; import lombok.Getter;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.EntityKeyType;
@ -24,11 +26,11 @@ import java.util.Set;
class SnapshotUpdate { class SnapshotUpdate {
@Getter @Getter
private final EntityKeyType type; private final AlarmConditionKeyType type;
@Getter @Getter
private final Set<EntityKey> keys; private final Set<AlarmConditionFilterKey> keys;
SnapshotUpdate(EntityKeyType type, Set<EntityKey> keys) { SnapshotUpdate(AlarmConditionKeyType type, Set<AlarmConditionFilterKey> keys) {
this.type = type; this.type = type;
this.keys = keys; this.keys = keys;
} }

View File

@ -36,6 +36,9 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; 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.AlarmConditionFilter;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
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.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
@ -44,6 +47,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.DynamicValueSourceType; import org.thingsboard.server.common.data.query.DynamicValueSourceType;
import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKey;
@ -62,6 +66,7 @@ import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey;
import org.thingsboard.server.dao.model.sql.AttributeKvEntity; import org.thingsboard.server.dao.model.sql.AttributeKvEntity;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -69,6 +74,7 @@ import java.util.TreeMap;
import java.util.UUID; import java.util.UUID;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
@ -141,8 +147,8 @@ public class TbDeviceProfileNodeTest {
DeviceProfile deviceProfile = new DeviceProfile(); DeviceProfile deviceProfile = new DeviceProfile();
DeviceProfileData deviceProfileData = new DeviceProfileData(); DeviceProfileData deviceProfileData = new DeviceProfileData();
KeyFilter highTempFilter = new KeyFilter(); AlarmConditionFilter highTempFilter = new AlarmConditionFilter();
highTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
highTempFilter.setValueType(EntityKeyValueType.NUMERIC); highTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate(); NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate();
highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
@ -157,8 +163,8 @@ public class TbDeviceProfileNodeTest {
dpa.setAlarmType("highTemperatureAlarm"); dpa.setAlarmType("highTemperatureAlarm");
dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule)));
KeyFilter lowTempFilter = new KeyFilter(); AlarmConditionFilter lowTempFilter = new AlarmConditionFilter();
lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); lowTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
lowTempFilter.setValueType(EntityKeyValueType.NUMERIC); lowTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate lowTemperaturePredicate = new NumericFilterPredicate(); NumericFilterPredicate lowTemperaturePredicate = new NumericFilterPredicate();
lowTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS); lowTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS);
@ -203,6 +209,175 @@ public class TbDeviceProfileNodeTest {
} }
@Test
public void testConstantKeyFilterSimple() throws Exception {
init();
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setId(deviceProfileId);
DeviceProfileData deviceProfileData = new DeviceProfileData();
Device device = new Device();
device.setId(deviceId);
device.setCustomerId(customerId);
AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarmEnabled"
);
AttributeKvEntity attributeKvEntity = new AttributeKvEntity();
attributeKvEntity.setId(compositeKey);
attributeKvEntity.setBooleanValue(Boolean.TRUE);
attributeKvEntity.setLastUpdateTs(System.currentTimeMillis());
AttributeKvEntry entry = attributeKvEntity.toData();
ListenableFuture<List<AttributeKvEntry>> attrListListenableFuture = Futures.immediateFuture(Collections.singletonList(entry));
AlarmConditionFilter alarmEnabledFilter = new AlarmConditionFilter();
alarmEnabledFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.CONSTANT, "alarmEnabled"));
alarmEnabledFilter.setValue(Boolean.TRUE);
alarmEnabledFilter.setValueType(EntityKeyValueType.BOOLEAN);
BooleanFilterPredicate alarmEnabledPredicate = new BooleanFilterPredicate();
alarmEnabledPredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
alarmEnabledPredicate.setValue(new FilterPredicateValue<>(
Boolean.FALSE,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarmEnabled")
));
alarmEnabledFilter.setPredicate(alarmEnabledPredicate);
AlarmConditionFilter temperatureFilter = new AlarmConditionFilter();
temperatureFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
temperatureFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate temperaturePredicate = new NumericFilterPredicate();
temperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
temperaturePredicate.setValue(new FilterPredicateValue<>(20.0, null, null));
temperatureFilter.setPredicate(temperaturePredicate);
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setCondition(Arrays.asList(alarmEnabledFilter, temperatureFilter));
AlarmRule alarmRule = new AlarmRule();
alarmRule.setCondition(alarmCondition);
DeviceProfileAlarm dpa = new DeviceProfileAlarm();
dpa.setId("alarmEnabledAlarmID");
dpa.setAlarmType("alarmEnabledAlarm");
dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule)));
deviceProfileData.setAlarms(Collections.singletonList(dpa));
deviceProfile.setProfileData(deviceProfileData);
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "alarmEnabledAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(attrListListenableFuture);
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString()))
.thenReturn(theMsg);
ObjectNode data = mapper.createObjectNode();
data.put("temperature", 21);
TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg);
verify(ctx).tellSuccess(msg);
verify(ctx).tellNext(theMsg, "Alarm Created");
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any());
}
@Test
public void testConstantKeyFilterInherited() throws Exception {
init();
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setId(deviceProfileId);
DeviceProfileData deviceProfileData = new DeviceProfileData();
Device device = new Device();
device.setId(deviceId);
device.setCustomerId(customerId);
AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey(
EntityType.TENANT, tenantId.getId(), "SERVER_SCOPE", "alarmEnabled"
);
AttributeKvEntity attributeKvEntity = new AttributeKvEntity();
attributeKvEntity.setId(compositeKey);
attributeKvEntity.setBooleanValue(Boolean.TRUE);
attributeKvEntity.setLastUpdateTs(System.currentTimeMillis());
AttributeKvEntry entry = attributeKvEntity.toData();
ListenableFuture<Optional<AttributeKvEntry>> attrListListenableFuture = Futures.immediateFuture(Optional.of(entry));
AlarmConditionFilter alarmEnabledFilter = new AlarmConditionFilter();
alarmEnabledFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.CONSTANT, "alarmEnabled"));
alarmEnabledFilter.setValue(Boolean.TRUE);
alarmEnabledFilter.setValueType(EntityKeyValueType.BOOLEAN);
BooleanFilterPredicate alarmEnabledPredicate = new BooleanFilterPredicate();
alarmEnabledPredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
alarmEnabledPredicate.setValue(new FilterPredicateValue<>(
Boolean.FALSE,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarmEnabled", true)
));
alarmEnabledFilter.setPredicate(alarmEnabledPredicate);
AlarmConditionFilter temperatureFilter = new AlarmConditionFilter();
temperatureFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
temperatureFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate temperaturePredicate = new NumericFilterPredicate();
temperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
temperaturePredicate.setValue(new FilterPredicateValue<>(20.0, null, null));
temperatureFilter.setPredicate(temperaturePredicate);
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setCondition(Arrays.asList(alarmEnabledFilter, temperatureFilter));
AlarmRule alarmRule = new AlarmRule();
alarmRule.setCondition(alarmCondition);
DeviceProfileAlarm dpa = new DeviceProfileAlarm();
dpa.setId("alarmEnabledAlarmID");
dpa.setAlarmType("alarmEnabledAlarm");
dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule)));
deviceProfileData.setAlarms(Collections.singletonList(dpa));
deviceProfile.setProfileData(deviceProfileData);
Mockito.when(deviceService.findDeviceById(tenantId, deviceId)).thenReturn(device);
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "alarmEnabledAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(attributesService.find(eq(tenantId), eq(customerId), Mockito.anyString(), Mockito.anyString()))
.thenReturn(Futures.immediateFuture(Optional.empty()));
Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), Mockito.anyString(), Mockito.anyString()))
.thenReturn(attrListListenableFuture);
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString()))
.thenReturn(theMsg);
ObjectNode data = mapper.createObjectNode();
data.put("temperature", 21);
TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg);
verify(ctx).tellSuccess(msg);
verify(ctx).tellNext(theMsg, "Alarm Created");
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any());
}
@Test @Test
public void testCurrentDeviceAttributeForDynamicValue() throws Exception { public void testCurrentDeviceAttributeForDynamicValue() throws Exception {
init(); init();
@ -228,8 +403,8 @@ public class TbDeviceProfileNodeTest {
ListenableFuture<List<AttributeKvEntry>> listListenableFutureWithLess = ListenableFuture<List<AttributeKvEntry>> listListenableFutureWithLess =
Futures.immediateFuture(Collections.singletonList(entry)); Futures.immediateFuture(Collections.singletonList(entry));
KeyFilter highTempFilter = new KeyFilter(); AlarmConditionFilter highTempFilter = new AlarmConditionFilter();
highTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
highTempFilter.setValueType(EntityKeyValueType.NUMERIC); highTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate(); NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate();
highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
@ -303,8 +478,8 @@ public class TbDeviceProfileNodeTest {
ListenableFuture<Optional<AttributeKvEntry>> optionalListenableFutureWithLess = ListenableFuture<Optional<AttributeKvEntry>> optionalListenableFutureWithLess =
Futures.immediateFuture(Optional.of(entry)); Futures.immediateFuture(Optional.of(entry));
KeyFilter lowTempFilter = new KeyFilter(); AlarmConditionFilter lowTempFilter = new AlarmConditionFilter();
lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); lowTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
lowTempFilter.setValueType(EntityKeyValueType.NUMERIC); lowTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate lowTempPredicate = new NumericFilterPredicate(); NumericFilterPredicate lowTempPredicate = new NumericFilterPredicate();
lowTempPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS); lowTempPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS);
@ -382,8 +557,8 @@ public class TbDeviceProfileNodeTest {
ListenableFuture<Optional<AttributeKvEntry>> optionalListenableFutureWithLess = ListenableFuture<Optional<AttributeKvEntry>> optionalListenableFutureWithLess =
Futures.immediateFuture(Optional.of(entry)); Futures.immediateFuture(Optional.of(entry));
KeyFilter lowTempFilter = new KeyFilter(); AlarmConditionFilter lowTempFilter = new AlarmConditionFilter();
lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); lowTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
lowTempFilter.setValueType(EntityKeyValueType.NUMERIC); lowTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate lowTempPredicate = new NumericFilterPredicate(); NumericFilterPredicate lowTempPredicate = new NumericFilterPredicate();
lowTempPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS); lowTempPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS);
@ -454,8 +629,8 @@ public class TbDeviceProfileNodeTest {
ListenableFuture<List<AttributeKvEntry>> listListenableFutureWithLess = ListenableFuture<List<AttributeKvEntry>> listListenableFutureWithLess =
Futures.immediateFuture(Collections.singletonList(entry)); Futures.immediateFuture(Collections.singletonList(entry));
KeyFilter lowTempFilter = new KeyFilter(); AlarmConditionFilter lowTempFilter = new AlarmConditionFilter();
lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); lowTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
lowTempFilter.setValueType(EntityKeyValueType.NUMERIC); lowTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate lowTempPredicate = new NumericFilterPredicate(); NumericFilterPredicate lowTempPredicate = new NumericFilterPredicate();
lowTempPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); lowTempPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
@ -502,9 +677,9 @@ public class TbDeviceProfileNodeTest {
verify(ctx).tellSuccess(msg); verify(ctx).tellSuccess(msg);
verify(ctx).tellNext(theMsg, "Alarm Created"); verify(ctx).tellNext(theMsg, "Alarm Created");
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any());
} }
@Test @Test
public void testCustomerInheritModeForDynamicValues() throws Exception { public void testCustomerInheritModeForDynamicValues() throws Exception {
init(); init();
@ -527,8 +702,8 @@ public class TbDeviceProfileNodeTest {
ListenableFuture<Optional<AttributeKvEntry>> optionalListenableFutureWithLess = ListenableFuture<Optional<AttributeKvEntry>> optionalListenableFutureWithLess =
Futures.immediateFuture(Optional.of(entry)); Futures.immediateFuture(Optional.of(entry));
KeyFilter lowTempFilter = new KeyFilter(); AlarmConditionFilter lowTempFilter = new AlarmConditionFilter();
lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); lowTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
lowTempFilter.setValueType(EntityKeyValueType.NUMERIC); lowTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate lowTempPredicate = new NumericFilterPredicate(); NumericFilterPredicate lowTempPredicate = new NumericFilterPredicate();
lowTempPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); lowTempPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);