Browse Source

Device profile dynamic value

Co-authored-by: DanyloKobzar <69635327+DanyloKobzar@users.noreply.github.com>
Co-authored-by: Vladyslav_Prykhodko <vprykhodko@thingsboard.io>
pull/4872/head
AndrewVolosytnykhThingsboard 5 years ago
committed by GitHub
parent
commit
3712d73855
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 82
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  2. 2
      application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java
  3. 3
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DurationAlarmConditionSpec.java
  4. 5
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/RepeatingAlarmConditionSpec.java
  5. 84
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java
  6. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java
  7. 44
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java
  8. 649
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java
  9. 23
      ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts
  10. 3
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  11. 79
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.html
  12. 23
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.scss
  13. 153
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.ts
  14. 76
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html
  15. 31
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts
  16. 42
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.ts
  17. 5
      ui-ngx/src/app/shared/models/device.models.ts
  18. 15
      ui-ngx/src/app/shared/models/query/query.models.ts
  19. 2
      ui-ngx/src/assets/locale/locale.constant-en_US.json

82
application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.install.update;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@ -31,6 +32,7 @@ import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
@ -41,16 +43,21 @@ import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientsParams;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.alarm.AlarmDao;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.model.sql.DeviceProfileEntity;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.dao.oauth2.OAuth2Utils;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.sql.device.DeviceProfileRepository;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.service.install.InstallScripts;
@ -92,6 +99,9 @@ public class DefaultDataUpdateService implements DataUpdateService {
@Autowired
private AlarmDao alarmDao;
@Autowired
private DeviceProfileRepository deviceProfileRepository;
@Autowired
private OAuth2Service oAuth2Service;
@ -114,6 +124,7 @@ public class DefaultDataUpdateService implements DataUpdateService {
log.info("Updating data from version 3.2.2 to 3.3.0 ...");
tenantsDefaultEdgeRuleChainUpdater.updateEntities(null);
tenantsAlarmsCustomerUpdater.updateEntities(null);
deviceProfileEntityDynamicConditionsUpdater.updateEntities(null);
updateOAuth2Params();
break;
default:
@ -121,6 +132,45 @@ public class DefaultDataUpdateService implements DataUpdateService {
}
}
private final PaginatedUpdater<String, DeviceProfileEntity> deviceProfileEntityDynamicConditionsUpdater =
new PaginatedUpdater<>() {
@Override
protected String getName() {
return "Device Profile Entity Dynamic Conditions Updater";
}
@Override
protected PageData<DeviceProfileEntity> findEntities(String id, PageLink pageLink) {
return DaoUtil.pageToPageData(deviceProfileRepository.findAll(DaoUtil.toPageable(pageLink)));
}
@Override
protected void updateEntity(DeviceProfileEntity deviceProfile) {
if (deviceProfile.getProfileData().has("alarms") &&
!deviceProfile.getProfileData().get("alarms").isNull()) {
boolean isUpdated = false;
JsonNode array = deviceProfile.getProfileData().get("alarms");
for (JsonNode node : array) {
if (node.has("createRules")) {
JsonNode createRules = node.get("createRules");
for (AlarmSeverity severity : AlarmSeverity.values()) {
if (createRules.has(severity.name())) {
isUpdated = isUpdated || convertDeviceProfileAlarmRulesForVersion330(createRules.get(severity.name()).get("condition").get("spec"));
}
}
}
if (node.has("clearRule") && !node.get("clearRule").isNull()) {
isUpdated = isUpdated || convertDeviceProfileAlarmRulesForVersion330(node.get("clearRule").get("condition").get("spec"));
}
}
if (isUpdated) {
deviceProfileRepository.save(deviceProfile);
}
}
}
};
private final PaginatedUpdater<String, Tenant> tenantsDefaultRuleChainUpdater =
new PaginatedUpdater<>() {
@ -370,6 +420,33 @@ public class DefaultDataUpdateService implements DataUpdateService {
}
}
private boolean convertDeviceProfileAlarmRulesForVersion330(JsonNode spec) {
if (spec != null) {
if (spec.has("type") && spec.get("type").asText().equals("DURATION")) {
if (spec.has("value")) {
long value = spec.get("value").asLong();
var predicate = new FilterPredicateValue<>(
value, null, new DynamicValue<>(null, null, false)
);
((ObjectNode) spec).remove("value");
((ObjectNode) spec).putPOJO("predicate", predicate);
return true;
}
} else if (spec.has("type") && spec.get("type").asText().equals("REPEATING")) {
if (spec.has("count")) {
int count = spec.get("count").asInt();
var predicate = new FilterPredicateValue<>(
count, null, new DynamicValue<>(null, null, false)
);
((ObjectNode) spec).remove("count");
((ObjectNode) spec).putPOJO("predicate", predicate);
return true;
}
}
}
return false;
}
private void updateOAuth2Params() {
try {
OAuth2ClientsParams oauth2ClientsParams = oAuth2Service.findOAuth2Params();
@ -380,9 +457,8 @@ public class DefaultDataUpdateService implements DataUpdateService {
oAuth2Service.saveOAuth2Params(new OAuth2ClientsParams(false, Collections.emptyList()));
log.info("Successfully updated OAuth2 parameters!");
}
}
catch (Exception e) {
log.error("Failed to update OAuth2 parameters", e);
} catch (Exception e) {
log.error("Failed to update OAuth2 parameters", e);
}
}

2
application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java

@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@Slf4j
public abstract class PaginatedUpdater<I, D extends SearchTextBased<? extends UUIDBased>> {
public abstract class PaginatedUpdater<I, D> {
private static final int DEFAULT_LIMIT = 100;
private int updated = 0;

3
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DurationAlarmConditionSpec.java

@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.device.profile;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import java.util.concurrent.TimeUnit;
@ -25,7 +26,7 @@ import java.util.concurrent.TimeUnit;
public class DurationAlarmConditionSpec implements AlarmConditionSpec {
private TimeUnit unit;
private long value;
private FilterPredicateValue<Long> predicate;
@Override
public AlarmConditionSpecType getType() {

5
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/RepeatingAlarmConditionSpec.java

@ -17,14 +17,13 @@ package org.thingsboard.server.common.data.device.profile;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.util.concurrent.TimeUnit;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class RepeatingAlarmConditionSpec implements AlarmConditionSpec {
private int count;
private FilterPredicateValue<Integer> predicate;
@Override
public AlarmConditionSpecType getType() {

84
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java

@ -24,6 +24,7 @@ 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.AlarmConditionSpecType;
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;
@ -33,6 +34,7 @@ import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpe
import org.thingsboard.server.common.data.device.profile.SpecificTimeSchedule;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
@ -43,6 +45,7 @@ import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
@Data
@ -52,8 +55,6 @@ class AlarmRuleState {
private final AlarmSeverity severity;
private final AlarmRule alarmRule;
private final AlarmConditionSpec spec;
private final long requiredDurationInMs;
private final long requiredRepeats;
private final Set<AlarmConditionFilterKey> entityKeys;
private PersistedAlarmRuleState state;
private boolean updateFlag;
@ -69,20 +70,6 @@ class AlarmRuleState {
this.state = new PersistedAlarmRuleState(0L, 0L, 0L);
}
this.spec = getSpec(alarmRule);
long requiredDurationInMs = 0;
long requiredRepeats = 0;
switch (spec.getType()) {
case DURATION:
DurationAlarmConditionSpec duration = (DurationAlarmConditionSpec) spec;
requiredDurationInMs = duration.getUnit().toMillis(duration.getValue());
break;
case REPEATING:
RepeatingAlarmConditionSpec repeating = (RepeatingAlarmConditionSpec) spec;
requiredRepeats = repeating.getCount();
break;
}
this.requiredDurationInMs = requiredDurationInMs;
this.requiredRepeats = requiredRepeats;
this.dynamicPredicateValueCtx = dynamicPredicateValueCtx;
}
@ -211,6 +198,7 @@ class AlarmRuleState {
if (active && eval(alarmRule.getCondition(), data)) {
state.setEventCount(state.getEventCount() + 1);
updateFlag = true;
long requiredRepeats = resolveRequiredRepeats(data);
return state.getEventCount() >= requiredRepeats ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE;
} else {
return AlarmEvalResult.FALSE;
@ -230,18 +218,62 @@ class AlarmRuleState {
state.setDuration(0L);
updateFlag = true;
}
long requiredDurationInMs = resolveRequiredDurationInMs(data);
return state.getDuration() > requiredDurationInMs ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE;
} else {
return AlarmEvalResult.FALSE;
}
}
public AlarmEvalResult eval(long ts) {
private long resolveRequiredRepeats(DataSnapshot data) {
long repeatingTimes = 0;
AlarmConditionSpec alarmConditionSpec = getSpec();
AlarmConditionSpecType specType = alarmConditionSpec.getType();
if(specType.equals(AlarmConditionSpecType.REPEATING)) {
RepeatingAlarmConditionSpec repeating = (RepeatingAlarmConditionSpec) spec;
repeatingTimes = repeating.getPredicate().getDefaultValue();
if (repeating.getPredicate().getDynamicValue() != null &&
repeating.getPredicate().getDynamicValue().getSourceAttribute() != null) {
EntityKeyValue repeatingKeyValue = getDynamicPredicateValue(data, repeating.getPredicate().getDynamicValue());
if (repeatingKeyValue != null) {
repeatingTimes = repeatingKeyValue.getLngValue();
}
}
}
return repeatingTimes;
}
private long resolveRequiredDurationInMs(DataSnapshot data) {
long durationTimeInMs = 0;
AlarmConditionSpec alarmConditionSpec = getSpec();
AlarmConditionSpecType specType = alarmConditionSpec.getType();
if(specType.equals(AlarmConditionSpecType.DURATION)) {
DurationAlarmConditionSpec duration = (DurationAlarmConditionSpec) spec;
TimeUnit timeUnit = duration.getUnit();
durationTimeInMs = timeUnit.toMillis(duration.getPredicate().getDefaultValue());
if (duration.getPredicate().getDynamicValue() != null &&
duration.getPredicate().getDynamicValue().getSourceAttribute() != null) {
EntityKeyValue durationKeyValue = getDynamicPredicateValue(data, duration.getPredicate().getDynamicValue());
if (durationKeyValue != null) {
durationTimeInMs = timeUnit.toMillis(durationKeyValue.getLngValue());
}
}
}
return durationTimeInMs;
}
public AlarmEvalResult eval(long ts, DataSnapshot dataSnapshot) {
switch (spec.getType()) {
case SIMPLE:
case REPEATING:
return AlarmEvalResult.NOT_YET_TRUE;
case DURATION:
long requiredDurationInMs = resolveRequiredDurationInMs(dataSnapshot);
if (requiredDurationInMs > 0 && state.getLastEventTs() > 0 && ts > state.getLastEventTs()) {
long duration = state.getDuration() + (ts - state.getLastEventTs());
if (isActive(ts)) {
@ -411,7 +443,7 @@ class AlarmRuleState {
}
private <T> T getPredicateValue(DataSnapshot data, FilterPredicateValue<T> value, AlarmConditionFilter filter, Function<EntityKeyValue, T> transformFunction) {
EntityKeyValue ekv = getDynamicPredicateValue(data, value);
EntityKeyValue ekv = getDynamicPredicateValue(data, value.getDynamicValue());
if (ekv != null) {
T result = transformFunction.apply(ekv);
if (result != null) {
@ -425,22 +457,22 @@ class AlarmRuleState {
}
}
private <T> EntityKeyValue getDynamicPredicateValue(DataSnapshot data, FilterPredicateValue<T> value) {
private <T> EntityKeyValue getDynamicPredicateValue(DataSnapshot data, DynamicValue<T> value) {
EntityKeyValue ekv = null;
if (value.getDynamicValue() != null) {
switch (value.getDynamicValue().getSourceType()) {
if (value != null) {
switch (value.getSourceType()) {
case CURRENT_DEVICE:
ekv = data.getValue(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, value.getDynamicValue().getSourceAttribute()));
if (ekv != null || !value.getDynamicValue().isInherit()) {
ekv = data.getValue(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, value.getSourceAttribute()));
if (ekv != null || !value.isInherit()) {
break;
}
case CURRENT_CUSTOMER:
ekv = dynamicPredicateValueCtx.getCustomerValue(value.getDynamicValue().getSourceAttribute());
if (ekv != null || !value.getDynamicValue().isInherit()) {
ekv = dynamicPredicateValueCtx.getCustomerValue(value.getSourceAttribute());
if (ekv != null || !value.isInherit()) {
break;
}
case CURRENT_TENANT:
ekv = dynamicPredicateValueCtx.getTenantValue(value.getDynamicValue().getSourceAttribute());
ekv = dynamicPredicateValueCtx.getTenantValue(value.getSourceAttribute());
}
}
return ekv;

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java

@ -80,7 +80,7 @@ class AlarmState {
public boolean process(TbContext ctx, long ts) throws ExecutionException, InterruptedException {
initCurrentAlarm(ctx);
return createOrClearAlarms(ctx, null, ts, null, AlarmRuleState::eval);
return createOrClearAlarms(ctx, null, ts, null, (alarmState, tsParam) -> alarmState.eval(tsParam, dataSnapshot));
}
public <T> boolean createOrClearAlarms(TbContext ctx, TbMsg msg, T data, SnapshotUpdate update, BiFunction<AlarmRuleState, T, AlarmEvalResult> evalFunction) {

44
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java

@ -19,24 +19,21 @@ import lombok.AccessLevel;
import lombok.Getter;
import org.thingsboard.server.common.data.DeviceProfile;
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.AlarmConditionSpec;
import org.thingsboard.server.common.data.device.profile.AlarmConditionSpecType;
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.DurationAlarmConditionSpec;
import org.thingsboard.server.common.data.device.profile.RepeatingAlarmConditionSpec;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.DynamicValueSourceType;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate;
import org.thingsboard.server.common.data.query.StringFilterPredicate;
import javax.print.attribute.standard.Severity;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@ -79,6 +76,7 @@ class ProfileState {
ruleKeys.add(keyFilter.getKey());
addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, ruleKeys);
}
addEntityKeysFromAlarmConditionSpec(alarmRule);
}));
if (alarm.getClearRule() != null) {
var clearAlarmKeys = alarmClearKeys.computeIfAbsent(alarm.getId(), id -> new HashSet<>());
@ -87,11 +85,43 @@ class ProfileState {
clearAlarmKeys.add(keyFilter.getKey());
addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, clearAlarmKeys);
}
addEntityKeysFromAlarmConditionSpec(alarm.getClearRule());
}
}
}
}
private void addEntityKeysFromAlarmConditionSpec(AlarmRule alarmRule) {
AlarmConditionSpec spec = alarmRule.getCondition().getSpec();
if (spec == null) {
return;
}
AlarmConditionSpecType specType = spec.getType();
switch (specType) {
case DURATION:
DurationAlarmConditionSpec duration = (DurationAlarmConditionSpec) spec;
if(duration.getPredicate().getDynamicValue() != null
&& duration.getPredicate().getDynamicValue().getSourceAttribute() != null) {
entityKeys.add(
new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE,
duration.getPredicate().getDynamicValue().getSourceAttribute())
);
}
break;
case REPEATING:
RepeatingAlarmConditionSpec repeating = (RepeatingAlarmConditionSpec) spec;
if(repeating.getPredicate().getDynamicValue() != null
&& repeating.getPredicate().getDynamicValue().getSourceAttribute() != null) {
entityKeys.add(
new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE,
repeating.getPredicate().getDynamicValue().getSourceAttribute())
);
}
break;
}
}
private void addDynamicValuesRecursively(KeyFilterPredicate predicate, Set<AlarmConditionFilterKey> entityKeys, Set<AlarmConditionFilterKey> ruleKeys) {
switch (predicate.getType()) {
case STRING:

649
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java

@ -42,6 +42,8 @@ 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.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.DurationAlarmConditionSpec;
import org.thingsboard.server.common.data.device.profile.RepeatingAlarmConditionSpec;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
@ -64,12 +66,15 @@ import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey;
import org.thingsboard.server.dao.model.sql.AttributeKvEntity;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
@ -94,10 +99,10 @@ public class TbDeviceProfileNodeTest {
@Mock
private AttributesService attributesService;
private TenantId tenantId = new TenantId(UUID.randomUUID());
private DeviceId deviceId = new DeviceId(UUID.randomUUID());
private CustomerId customerId = new CustomerId(UUID.randomUUID());
private DeviceProfileId deviceProfileId = new DeviceProfileId(UUID.randomUUID());
private final TenantId tenantId = new TenantId(UUID.randomUUID());
private final DeviceId deviceId = new DeviceId(UUID.randomUUID());
private final CustomerId customerId = new CustomerId(UUID.randomUUID());
private final DeviceProfileId deviceProfileId = new DeviceProfileId(UUID.randomUUID());
@Test
public void testRandomMessageType() throws Exception {
@ -444,6 +449,642 @@ public class TbDeviceProfileNodeTest {
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any());
}
@Test
public void testCurrentDeviceAttributeForDynamicDurationValue() throws Exception {
init();
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setId(deviceProfileId);
DeviceProfileData deviceProfileData = new DeviceProfileData();
AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute"
);
AttributeKvEntity attributeKvEntity = new AttributeKvEntity();
attributeKvEntity.setId(compositeKey);
attributeKvEntity.setLongValue(30L);
attributeKvEntity.setLastUpdateTs(0L);
AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay"
);
AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity();
alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey);
long alarmDelayInSeconds = 5L;
alarmDelayAttributeKvEntity.setLongValue(alarmDelayInSeconds);
alarmDelayAttributeKvEntity.setLastUpdateTs(0L);
AttributeKvEntry entry = attributeKvEntity.toData();
AttributeKvEntry alarmDelayAttributeKvEntry = alarmDelayAttributeKvEntity.toData();
ListenableFuture<List<AttributeKvEntry>> listListenableFuture =
Futures.immediateFuture(Arrays.asList(entry, alarmDelayAttributeKvEntry));
AlarmConditionFilter highTempFilter = new AlarmConditionFilter();
highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
highTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate();
highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperaturePredicate.setValue(new FilterPredicateValue<>(
0.0,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute", false)
));
highTempFilter.setPredicate(highTemperaturePredicate);
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setCondition(Collections.singletonList(highTempFilter));
FilterPredicateValue<Long> filterPredicateValue = new FilterPredicateValue<>(
10L,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_delay", false)
);
DurationAlarmConditionSpec durationSpec = new DurationAlarmConditionSpec();
durationSpec.setUnit(TimeUnit.SECONDS);
durationSpec.setPredicate(filterPredicateValue);
alarmCondition.setSpec(durationSpec);
AlarmRule alarmRule = new AlarmRule();
alarmRule.setCondition(alarmCondition);
DeviceProfileAlarm dpa = new DeviceProfileAlarm();
dpa.setId("highTemperatureAlarmID");
dpa.setAlarmType("highTemperatureAlarm");
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, "highTemperatureAlarm"))
.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(listListenableFuture);
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString()))
.thenReturn(theMsg);
ObjectNode data = mapper.createObjectNode();
data.put("temperature", 35);
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);
int halfOfAlarmDelay = new BigDecimal(alarmDelayInSeconds)
.multiply(BigDecimal.valueOf(1000))
.divide(BigDecimal.valueOf(2), 3, RoundingMode.HALF_EVEN)
.intValueExact();
Thread.sleep(halfOfAlarmDelay);
verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created");
Thread.sleep(halfOfAlarmDelay);
TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg2);
verify(ctx).tellSuccess(msg2);
verify(ctx).tellNext(theMsg, "Alarm Created");
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any());
}
@Test
public void testInheritTenantAttributeForDuration() 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", "greaterAttribute"
);
AttributeKvEntity attributeKvEntity = new AttributeKvEntity();
attributeKvEntity.setId(compositeKey);
attributeKvEntity.setLongValue(30L);
attributeKvEntity.setLastUpdateTs(0L);
AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay"
);
AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity();
alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey);
long alarmDelayInSeconds = 5L;
alarmDelayAttributeKvEntity.setLongValue(alarmDelayInSeconds);
alarmDelayAttributeKvEntity.setLastUpdateTs(0L);
AttributeKvEntry entry = attributeKvEntity.toData();
AttributeKvEntry alarmDelayAttributeKvEntry = alarmDelayAttributeKvEntity.toData();
ListenableFuture<Optional<AttributeKvEntry>> optionalDurationAttribute =
Futures.immediateFuture(Optional.of(alarmDelayAttributeKvEntry));
ListenableFuture<List<AttributeKvEntry>> listNoDurationAttribute =
Futures.immediateFuture(Collections.singletonList(entry));
ListenableFuture<Optional<AttributeKvEntry>> emptyOptional =
Futures.immediateFuture(Optional.empty());
AlarmConditionFilter highTempFilter = new AlarmConditionFilter();
highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
highTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate();
highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperaturePredicate.setValue(new FilterPredicateValue<>(
0.0,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute", false)
));
highTempFilter.setPredicate(highTemperaturePredicate);
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setCondition(Collections.singletonList(highTempFilter));
FilterPredicateValue<Long> filterPredicateValue = new FilterPredicateValue<>(
10L,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_delay", true)
);
DurationAlarmConditionSpec durationSpec = new DurationAlarmConditionSpec();
durationSpec.setUnit(TimeUnit.SECONDS);
durationSpec.setPredicate(filterPredicateValue);
alarmCondition.setSpec(durationSpec);
AlarmRule alarmRule = new AlarmRule();
alarmRule.setCondition(alarmCondition);
DeviceProfileAlarm dpa = new DeviceProfileAlarm();
dpa.setId("highTemperatureAlarmID");
dpa.setAlarmType("highTemperatureAlarm");
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, "highTemperatureAlarm"))
.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(tenantId), Mockito.anyString(), Mockito.anyString()))
.thenReturn(optionalDurationAttribute);
Mockito.when(ctx.getDeviceService().findDeviceById(tenantId, deviceId))
.thenReturn(device);
Mockito.when(attributesService.find(eq(tenantId), eq(customerId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString()))
.thenReturn(emptyOptional);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(listNoDurationAttribute);
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString()))
.thenReturn(theMsg);
ObjectNode data = mapper.createObjectNode();
data.put("temperature", 150);
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);
int halfOfAlarmDelay = new BigDecimal(alarmDelayInSeconds)
.multiply(BigDecimal.valueOf(1000))
.divide(BigDecimal.valueOf(2), 3, RoundingMode.HALF_EVEN)
.intValueExact();
Thread.sleep(halfOfAlarmDelay);
verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created");
Thread.sleep(halfOfAlarmDelay);
TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg2);
verify(ctx).tellSuccess(msg2);
verify(ctx).tellNext(theMsg, "Alarm Created");
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any());
}
@Test
public void testCurrentDeviceAttributeForDynamicRepeatingValue() throws Exception {
init();
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setId(deviceProfileId);
DeviceProfileData deviceProfileData = new DeviceProfileData();
AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute"
);
AttributeKvEntity attributeKvEntity = new AttributeKvEntity();
attributeKvEntity.setId(compositeKey);
attributeKvEntity.setLongValue(30L);
attributeKvEntity.setLastUpdateTs(0L);
AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay"
);
AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity();
alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey);
long alarmRepeating = 2;
alarmDelayAttributeKvEntity.setLongValue(alarmRepeating);
alarmDelayAttributeKvEntity.setLastUpdateTs(0L);
AttributeKvEntry entry = attributeKvEntity.toData();
AttributeKvEntry alarmDelayAttributeKvEntry = alarmDelayAttributeKvEntity.toData();
ListenableFuture<List<AttributeKvEntry>> listListenableFuture =
Futures.immediateFuture(Arrays.asList(entry, alarmDelayAttributeKvEntry));
AlarmConditionFilter highTempFilter = new AlarmConditionFilter();
highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
highTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate();
highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperaturePredicate.setValue(new FilterPredicateValue<>(
0.0,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute", false)
));
highTempFilter.setPredicate(highTemperaturePredicate);
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setCondition(Collections.singletonList(highTempFilter));
FilterPredicateValue<Integer> filterPredicateValue = new FilterPredicateValue<>(
10,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_delay", false)
);
RepeatingAlarmConditionSpec repeatingSpec = new RepeatingAlarmConditionSpec();
repeatingSpec.setPredicate(filterPredicateValue);
alarmCondition.setSpec(repeatingSpec);
AlarmRule alarmRule = new AlarmRule();
alarmRule.setCondition(alarmCondition);
DeviceProfileAlarm dpa = new DeviceProfileAlarm();
dpa.setId("highTemperatureAlarmID");
dpa.setAlarmType("highTemperatureAlarm");
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, "highTemperatureAlarm"))
.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(listListenableFuture);
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString()))
.thenReturn(theMsg);
ObjectNode data = mapper.createObjectNode();
data.put("temperature", 150);
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, Mockito.never()).tellNext(theMsg, "Alarm Created");
TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg2);
verify(ctx).tellSuccess(msg2);
verify(ctx).tellNext(theMsg, "Alarm Created");
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any());
}
@Test
public void testInheritTenantAttributeForRepeating() throws Exception {
init();
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setId(deviceProfileId);
DeviceProfileData deviceProfileData = new DeviceProfileData();
AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute"
);
Device device = new Device();
device.setId(deviceId);
device.setCustomerId(customerId);
AttributeKvEntity attributeKvEntity = new AttributeKvEntity();
attributeKvEntity.setId(compositeKey);
attributeKvEntity.setLongValue(30L);
attributeKvEntity.setLastUpdateTs(0L);
AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay"
);
AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity();
alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey);
long repeatingCondition = 2;
alarmDelayAttributeKvEntity.setLongValue(repeatingCondition);
alarmDelayAttributeKvEntity.setLastUpdateTs(0L);
AttributeKvEntry entry = attributeKvEntity.toData();
AttributeKvEntry alarmDelayAttributeKvEntry = alarmDelayAttributeKvEntity.toData();
ListenableFuture<Optional<AttributeKvEntry>> optionalDurationAttribute =
Futures.immediateFuture(Optional.of(alarmDelayAttributeKvEntry));
ListenableFuture<List<AttributeKvEntry>> listNoDurationAttribute =
Futures.immediateFuture(Collections.singletonList(entry));
ListenableFuture<Optional<AttributeKvEntry>> emptyOptional =
Futures.immediateFuture(Optional.empty());
AlarmConditionFilter highTempFilter = new AlarmConditionFilter();
highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
highTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate();
highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperaturePredicate.setValue(new FilterPredicateValue<>(
0.0,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute", false)
));
highTempFilter.setPredicate(highTemperaturePredicate);
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setCondition(Collections.singletonList(highTempFilter));
FilterPredicateValue<Integer> filterPredicateValue = new FilterPredicateValue<>(
10,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_delay", true)
);
RepeatingAlarmConditionSpec repeatingSpec = new RepeatingAlarmConditionSpec();
repeatingSpec.setPredicate(filterPredicateValue);
alarmCondition.setSpec(repeatingSpec);
AlarmRule alarmRule = new AlarmRule();
alarmRule.setCondition(alarmCondition);
DeviceProfileAlarm dpa = new DeviceProfileAlarm();
dpa.setId("highTemperatureAlarmID");
dpa.setAlarmType("highTemperatureAlarm");
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, "highTemperatureAlarm"))
.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(tenantId), Mockito.anyString(), Mockito.anyString()))
.thenReturn(optionalDurationAttribute);
Mockito.when(ctx.getDeviceService().findDeviceById(tenantId, deviceId))
.thenReturn(device);
Mockito.when(attributesService.find(eq(tenantId), eq(customerId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString()))
.thenReturn(emptyOptional);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(listNoDurationAttribute);
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString()))
.thenReturn(theMsg);
ObjectNode data = mapper.createObjectNode();
data.put("temperature", 150);
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, Mockito.never()).tellNext(theMsg, "Alarm Created");
TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg2);
verify(ctx).tellSuccess(msg2);
verify(ctx).tellNext(theMsg, "Alarm Created");
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any());
}
@Test
public void testCurrentDeviceAttributeForUseDefaultDurationWhenDynamicDurationValueIsNull() throws Exception {
init();
long alarmDelayInSeconds = 5;
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", "greaterAttribute"
);
AttributeKvEntity attributeKvEntity = new AttributeKvEntity();
attributeKvEntity.setId(compositeKey);
attributeKvEntity.setLongValue(30L);
attributeKvEntity.setLastUpdateTs(0L);
AttributeKvEntry entry = attributeKvEntity.toData();
ListenableFuture<List<AttributeKvEntry>> listListenableFuture =
Futures.immediateFuture(Collections.singletonList(entry));
AlarmConditionFilter highTempFilter = new AlarmConditionFilter();
highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
highTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate();
highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperaturePredicate.setValue(new FilterPredicateValue<>(
0.0,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute")
));
highTempFilter.setPredicate(highTemperaturePredicate);
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setCondition(Collections.singletonList(highTempFilter));
FilterPredicateValue<Long> filterPredicateValue = new FilterPredicateValue<>(
alarmDelayInSeconds,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, null, false)
);
DurationAlarmConditionSpec durationSpec = new DurationAlarmConditionSpec();
durationSpec.setUnit(TimeUnit.SECONDS);
durationSpec.setPredicate(filterPredicateValue);
alarmCondition.setSpec(durationSpec);
AlarmRule alarmRule = new AlarmRule();
alarmRule.setCondition(alarmCondition);
DeviceProfileAlarm dpa = new DeviceProfileAlarm();
dpa.setId("highTemperatureAlarmID");
dpa.setAlarmType("highTemperatureAlarm");
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, "highTemperatureAlarm"))
.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(listListenableFuture);
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString()))
.thenReturn(theMsg);
ObjectNode data = mapper.createObjectNode();
data.put("temperature", 35);
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);
int halfOfAlarmDelay = new BigDecimal(alarmDelayInSeconds)
.multiply(BigDecimal.valueOf(1000))
.divide(BigDecimal.valueOf(2), 3, RoundingMode.HALF_EVEN)
.intValueExact();
Thread.sleep(halfOfAlarmDelay);
verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created");
Thread.sleep(halfOfAlarmDelay);
TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg2);
verify(ctx).tellSuccess(msg2);
verify(ctx).tellNext(theMsg, "Alarm Created");
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any());
}
@Test
public void testCurrentDeviceAttributeForUseDefaultRepeatingWhenDynamicDurationValueIsNull() 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", "greaterAttribute"
);
AttributeKvEntity attributeKvEntity = new AttributeKvEntity();
attributeKvEntity.setId(compositeKey);
attributeKvEntity.setLongValue(30L);
attributeKvEntity.setLastUpdateTs(0L);
AttributeKvEntry entry = attributeKvEntity.toData();
ListenableFuture<List<AttributeKvEntry>> listListenableFuture =
Futures.immediateFuture(Collections.singletonList(entry));
AlarmConditionFilter highTempFilter = new AlarmConditionFilter();
highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
highTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate();
highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperaturePredicate.setValue(new FilterPredicateValue<>(
0.0,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute")
));
highTempFilter.setPredicate(highTemperaturePredicate);
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setCondition(Collections.singletonList(highTempFilter));
RepeatingAlarmConditionSpec repeating = new RepeatingAlarmConditionSpec();
repeating.setPredicate(new FilterPredicateValue<>(
0,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_rule", false)
));
alarmCondition.setSpec(repeating);
AlarmRule alarmRule = new AlarmRule();
alarmRule.setCondition(alarmCondition);
DeviceProfileAlarm dpa = new DeviceProfileAlarm();
dpa.setId("highTemperatureAlarmID");
dpa.setAlarmType("highTemperatureAlarm");
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, "highTemperatureAlarm"))
.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(listListenableFuture);
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString()))
.thenReturn(theMsg);
ObjectNode data = mapper.createObjectNode();
data.put("temperature", 35);
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 testCurrentCustomersAttributeForDynamicValue() throws Exception {
init();

23
ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts

@ -30,7 +30,9 @@ import {
DynamicValueSourceType,
dynamicValueSourceTypeTranslationMap,
EntityKeyValueType,
FilterPredicateValue
FilterPredicateValue,
getDynamicSourcesForAllowUser,
inheritModeForDynamicValueSourceType
} from '@shared/models/query/query.models';
@Component({
@ -52,22 +54,14 @@ import {
})
export class FilterPredicateValueComponent implements ControlValueAccessor, Validator, OnInit {
private readonly inheritModeForSources: DynamicValueSourceType[] = [
DynamicValueSourceType.CURRENT_CUSTOMER,
DynamicValueSourceType.CURRENT_DEVICE];
private readonly inheritModeForSources: DynamicValueSourceType[] = inheritModeForDynamicValueSourceType;
@Input() disabled: boolean;
@Input()
set allowUserDynamicSource(allow: boolean) {
this.dynamicValueSourceTypes = [DynamicValueSourceType.CURRENT_TENANT,
DynamicValueSourceType.CURRENT_CUSTOMER];
this.dynamicValueSourceTypes = getDynamicSourcesForAllowUser(allow);
this.allow = allow;
if (allow) {
this.dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_USER);
} else {
this.dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_DEVICE);
}
}
private onlyUserDynamicSourceValue = false;
@ -92,8 +86,9 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, Vali
valueTypeEnum = EntityKeyValueType;
dynamicValueSourceTypes: DynamicValueSourceType[] = [DynamicValueSourceType.CURRENT_TENANT,
DynamicValueSourceType.CURRENT_CUSTOMER, DynamicValueSourceType.CURRENT_USER];
allow = true;
dynamicValueSourceTypes: DynamicValueSourceType[] = getDynamicSourcesForAllowUser(this.allow);
dynamicValueSourceTypeTranslations = dynamicValueSourceTypeTranslationMap;
@ -103,8 +98,6 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, Vali
inheritMode = false;
allow = true;
private propagateChange = null;
private propagateChangePending = false;

3
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -138,6 +138,7 @@ import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/emb
import { EdgeDownlinkTableComponent } from '@home/components/edge/edge-downlink-table.component';
import { EdgeDownlinkTableHeaderComponent } from '@home/components/edge/edge-downlink-table-header.component';
import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-page/widget-types-panel.component';
import { AlarmDurationPredicateValueComponent } from '@home/components/profile/alarm/alarm-duration-predicate-value.component';
import { SecurityConfigLwm2mComponent } from '@home/components/device/security-config-lwm2m.component';
import { SecurityConfigLwm2mServerComponent } from '@home/components/device/security-config-lwm2m-server.component';
import { DashboardImageDialogComponent } from '@home/components/dashboard-page/dashboard-image-dialog.component';
@ -242,6 +243,7 @@ import { SnmpDeviceProfileTransportModule } from '@home/components/profile/devic
AlarmScheduleInfoComponent,
DeviceProfileProvisionConfigurationComponent,
AlarmScheduleComponent,
AlarmDurationPredicateValueComponent,
DeviceWizardDialogComponent,
DeviceCredentialsComponent,
CopyDeviceCredentialsComponent,
@ -358,6 +360,7 @@ import { SnmpDeviceProfileTransportModule } from '@home/components/profile/devic
AlarmScheduleInfoComponent,
AlarmScheduleComponent,
AlarmScheduleDialogComponent,
AlarmDurationPredicateValueComponent,
EditAlarmDetailsDialogComponent,
DeviceProfileProvisionConfigurationComponent,
AlarmScheduleComponent,

79
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.html

@ -0,0 +1,79 @@
<!--
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.
-->
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="alarmDurationPredicateValueFormGroup">
<div fxFlex fxLayout="column" [fxShow]="!dynamicMode">
<mat-form-field floatLabel="always" hideRequiredMarker class="mat-block">
<mat-label></mat-label>
<input required type="number" matInput
step="1" min="1" max="2147483647"
formControlName="defaultValue"
placeholder="{{ defaultValuePlaceholder | translate }}">
<mat-error *ngIf="alarmDurationPredicateValueFormGroup.get('defaultValue').hasError('required')">
{{ defaultValueRequiredError | translate }}
</mat-error>
<mat-error *ngIf="alarmDurationPredicateValueFormGroup.get('defaultValue').hasError('min')">
{{ defaultValueRangeError | translate }}
</mat-error>
<mat-error *ngIf="alarmDurationPredicateValueFormGroup.get('defaultValue').hasError('max')">
{{ defaultValueRangeError | translate }}
</mat-error>
<mat-error *ngIf="alarmDurationPredicateValueFormGroup.get('defaultValue').hasError('pattern')">
{{ defaultValuePatternError | translate }}
</mat-error>
</mat-form-field>
</div>
<div fxFlex fxLayout="column" [fxShow]="dynamicMode">
<div formGroupName="dynamicValue" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<div fxFlex="40" fxLayout="column">
<mat-form-field floatLabel="always" hideRequiredMarker class="mat-block">
<mat-label></mat-label>
<mat-select formControlName="sourceType" placeholder="{{'filter.dynamic-source-type' | translate}}">
<mat-option [value]="null">
{{'filter.no-dynamic-value' | translate}}
</mat-option>
<mat-option *ngFor="let sourceType of dynamicValueSourceTypes" [value]="sourceType">
{{dynamicValueSourceTypeTranslations.get(sourceType) | translate}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div fxFlex fxLayout="column">
<mat-form-field floatLabel="always" hideRequiredMarker class="mat-block source-attribute">
<mat-label></mat-label>
<input matInput formControlName="sourceAttribute" placeholder="{{'filter.source-attribute' | translate}}">
</mat-form-field>
</div>
<div *ngIf="inheritMode"
fxLayout="column"
style="padding-top: 6px">
<mat-checkbox formControlName="inherit">
{{ 'filter.inherit-owner' | translate}}
</mat-checkbox>
</div>
</div>
</div>
<button mat-icon-button
class="mat-elevation-z1 tb-mat-32"
color="primary"
type="button"
matTooltip="{{ (dynamicMode ? 'filter.switch-to-default-value' : 'filter.switch-to-dynamic-value') | translate }}"
matTooltipPosition="above"
(click)="dynamicMode = !dynamicMode">
<mat-icon class="tb-mat-20" [svgIcon]="dynamicMode ? 'mdi:numeric' : 'mdi:variable'"></mat-icon>
</button>
</div>

23
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.scss

@ -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.
*/
:host ::ng-deep {
.source-attribute {
.mat-form-field-infix{
width: 100%;
}
}
}

153
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.ts

@ -0,0 +1,153 @@
///
/// 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.
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import {
DynamicValueSourceType,
dynamicValueSourceTypeTranslationMap,
FilterPredicateValue,
getDynamicSourcesForAllowUser,
inheritModeForDynamicValueSourceType
} from '@shared/models/query/query.models';
import { AlarmConditionType } from '@shared/models/device.models';
@Component({
selector: 'tb-alarm-duration-predicate-value',
templateUrl: './alarm-duration-predicate-value.component.html',
styleUrls: ['./alarm-duration-predicate-value.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AlarmDurationPredicateValueComponent),
multi: true
}
]
})
export class AlarmDurationPredicateValueComponent implements ControlValueAccessor, OnInit {
private readonly inheritModeForSources = inheritModeForDynamicValueSourceType;
@Input()
set alarmConditionType(alarmConditionType: AlarmConditionType) {
switch (alarmConditionType) {
case AlarmConditionType.REPEATING:
this.defaultValuePlaceholder = 'device-profile.condition-repeating-value-required';
this.defaultValueRequiredError = 'device-profile.condition-repeating-value-range';
this.defaultValueRangeError = 'device-profile.condition-repeating-value-range';
this.defaultValuePatternError = 'device-profile.condition-repeating-value-pattern';
break;
case AlarmConditionType.DURATION:
this.defaultValuePlaceholder = 'device-profile.condition-duration-value';
this.defaultValueRequiredError = 'device-profile.condition-duration-value-required';
this.defaultValueRangeError = 'device-profile.condition-duration-value-range';
this.defaultValuePatternError = 'device-profile.condition-duration-value-pattern';
break;
}
}
defaultValuePlaceholder = '';
defaultValueRequiredError = '';
defaultValueRangeError = '';
defaultValuePatternError = '';
dynamicValueSourceTypes: DynamicValueSourceType[] = getDynamicSourcesForAllowUser(false);
dynamicValueSourceTypeTranslations = dynamicValueSourceTypeTranslationMap;
alarmDurationPredicateValueFormGroup: FormGroup;
dynamicMode = false;
inheritMode = false;
private propagateChange = null;
constructor(private fb: FormBuilder) {
}
ngOnInit(): void {
this.alarmDurationPredicateValueFormGroup = this.fb.group({
defaultValue: [0, [Validators.required, Validators.min(1), Validators.max(2147483647), Validators.pattern('[0-9]*')]],
dynamicValue: this.fb.group(
{
sourceType: [null],
sourceAttribute: [null],
inherit: [false]
}
)
});
this.alarmDurationPredicateValueFormGroup.get('dynamicValue').get('sourceType').valueChanges.subscribe(
(sourceType) => {
if (!sourceType) {
this.alarmDurationPredicateValueFormGroup.get('dynamicValue').get('sourceAttribute').patchValue(null, {emitEvent: false});
}
this.updateShowInheritMode(sourceType);
}
);
this.alarmDurationPredicateValueFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
if (isDisabled) {
this.alarmDurationPredicateValueFormGroup.disable({emitEvent: false});
} else {
this.alarmDurationPredicateValueFormGroup.enable({emitEvent: false});
}
}
writeValue(predicateValue: FilterPredicateValue<string | number | boolean>): void {
this.alarmDurationPredicateValueFormGroup.patchValue({
defaultValue: predicateValue ? predicateValue.defaultValue : null,
dynamicValue: {
sourceType: predicateValue?.dynamicValue ? predicateValue.dynamicValue.sourceType : null,
sourceAttribute: predicateValue?.dynamicValue ? predicateValue.dynamicValue.sourceAttribute : null,
inherit: predicateValue?.dynamicValue ? predicateValue.dynamicValue.inherit : null
}
}, {emitEvent: false});
}
private updateModel() {
let predicateValue: FilterPredicateValue<string | number | boolean> = null;
if (this.alarmDurationPredicateValueFormGroup.valid) {
predicateValue = this.alarmDurationPredicateValueFormGroup.getRawValue();
if (predicateValue.dynamicValue) {
if (!predicateValue.dynamicValue.sourceType || !predicateValue.dynamicValue.sourceAttribute) {
predicateValue.dynamicValue = null;
}
}
}
this.propagateChange(predicateValue);
}
private updateShowInheritMode(sourceType: DynamicValueSourceType) {
if (this.inheritModeForSources.includes(sourceType)) {
this.inheritMode = true;
} else {
this.alarmDurationPredicateValueFormGroup.get('dynamicValue.inherit').patchValue(false, {emitEvent: false});
this.inheritMode = false;
}
}
}

76
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html

@ -37,7 +37,7 @@
[entityId]="entityId"
formControlName="keyFilters">
</tb-key-filter-list>
<section formGroupName="spec" class="row">
<section formGroupName="spec" style="margin-top: 1em">
<mat-form-field class="mat-block" hideRequiredMarker>
<mat-label translate>device-profile.condition-type</mat-label>
<mat-select formControlName="type" required>
@ -49,60 +49,26 @@
{{ 'device-profile.condition-type-required' | translate }}
</mat-error>
</mat-form-field>
<div fxLayout="row" fxLayoutGap="8px" *ngIf="conditionFormGroup.get('spec.type').value == AlarmConditionType.DURATION">
<mat-form-field class="mat-block" hideRequiredMarker fxFlex floatLabel="always">
<mat-label></mat-label>
<input type="number" required
step="1" min="1" max="2147483647" matInput
placeholder="{{ 'device-profile.condition-duration-value' | translate }}"
formControlName="value">
<mat-error *ngIf="conditionFormGroup.get('spec.value').hasError('required')">
{{ 'device-profile.condition-duration-value-required' | translate }}
</mat-error>
<mat-error *ngIf="conditionFormGroup.get('spec.value').hasError('min')">
{{ 'device-profile.condition-duration-value-range' | translate }}
</mat-error>
<mat-error *ngIf="conditionFormGroup.get('spec.value').hasError('max')">
{{ 'device-profile.condition-duration-value-range' | translate }}
</mat-error>
<mat-error *ngIf="conditionFormGroup.get('spec.value').hasError('pattern')">
{{ 'device-profile.condition-duration-value-pattern' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block" hideRequiredMarker fxFlex floatLabel="always">
<mat-label></mat-label>
<mat-select formControlName="unit"
required
placeholder="{{ 'device-profile.condition-duration-time-unit' | translate }}">
<mat-option *ngFor="let timeUnit of timeUnits" [value]="timeUnit">
{{ timeUnitTranslations.get(timeUnit) | translate }}
</mat-option>
</mat-select>
<mat-error *ngIf="conditionFormGroup.get('spec.unit').hasError('required')">
{{ 'device-profile.condition-duration-time-unit-required' | translate }}
</mat-error>
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap="8px" *ngIf="conditionFormGroup.get('spec.type').value == AlarmConditionType.REPEATING">
<mat-form-field class="mat-block" hideRequiredMarker fxFlex floatLabel="always">
<mat-label></mat-label>
<input type="number" required
step="1" min="1" max="2147483647" matInput
placeholder="{{ 'device-profile.condition-repeating-value' | translate }}"
formControlName="count">
<mat-error *ngIf="conditionFormGroup.get('spec.count').hasError('required')">
{{ 'device-profile.condition-repeating-value-required' | translate }}
</mat-error>
<mat-error *ngIf="conditionFormGroup.get('spec.count').hasError('min')">
{{ 'device-profile.condition-repeating-value-range' | translate }}
</mat-error>
<mat-error *ngIf="conditionFormGroup.get('spec.count').hasError('max')">
{{ 'device-profile.condition-repeating-value-range' | translate }}
</mat-error>
<mat-error *ngIf="conditionFormGroup.get('spec.count').hasError('pattern')">
{{ 'device-profile.condition-repeating-value-pattern' | translate }}
</mat-error>
</mat-form-field>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px" *ngIf="conditionFormGroup.get('spec.type').value != AlarmConditionType.SIMPLE">
<tb-alarm-duration-predicate-value
fxLayout="row" fxFlex formControlName="predicate"
[alarmConditionType]="conditionFormGroup.get('spec.type').value"
>
</tb-alarm-duration-predicate-value>
<div fxFlex="23" *ngIf="conditionFormGroup.get('spec.type').value == AlarmConditionType.DURATION">
<mat-form-field class="mat-block" hideRequiredMarker floatLabel="always">
<mat-label></mat-label>
<mat-select formControlName="unit" required
placeholder="{{ 'device-profile.condition-duration-time-unit' | translate }}">
<mat-option *ngFor="let timeUnit of timeUnits" [value]="timeUnit">
{{ timeUnitTranslations.get(timeUnit) | translate }}
</mat-option>
</mat-select>
<mat-error *ngIf="conditionFormGroup.get('spec.unit').hasError('required')">
{{ 'device-profile.condition-duration-time-unit-required' | translate }}
</mat-error>
</mat-form-field>
</div>
</div>
</section>
</div>

31
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts

@ -43,12 +43,11 @@ export interface AlarmRuleConditionDialogData {
export class AlarmRuleConditionDialogComponent extends DialogComponent<AlarmRuleConditionDialogComponent, AlarmCondition>
implements OnInit, ErrorStateMatcher {
timeUnits = Object.keys(TimeUnit);
timeUnits = Object.values(TimeUnit);
timeUnitTranslations = timeUnitTranslationMap;
alarmConditionTypes = Object.keys(AlarmConditionType);
alarmConditionTypes = Object.values(AlarmConditionType);
AlarmConditionType = AlarmConditionType;
alarmConditionTypeTranslation = AlarmConditionTypeTranslationMap;
readonly = this.data.readonly;
condition = this.data.condition;
entityId = this.data.entityId;
@ -70,9 +69,8 @@ export class AlarmRuleConditionDialogComponent extends DialogComponent<AlarmRule
keyFilters: [keyFiltersToKeyFilterInfos(this.condition?.condition), Validators.required],
spec: this.fb.group({
type: [AlarmConditionType.SIMPLE, Validators.required],
unit: [{value: null, disable: true}, Validators.required],
value: [{value: null, disable: true}, [Validators.required, Validators.min(1), Validators.max(2147483647), Validators.pattern('[0-9]*')]],
count: [{value: null, disable: true}, [Validators.required, Validators.min(1), Validators.max(2147483647), Validators.pattern('[0-9]*')]]
unit: [null, Validators.required],
predicate: [null, Validators.required]
})
});
this.conditionFormGroup.patchValue({spec: this.condition?.spec});
@ -98,42 +96,37 @@ export class AlarmRuleConditionDialogComponent extends DialogComponent<AlarmRule
private updateValidators(type: AlarmConditionType, resetDuration = false, emitEvent = false) {
switch (type) {
case AlarmConditionType.DURATION:
this.conditionFormGroup.get('spec.value').enable();
this.conditionFormGroup.get('spec.unit').enable();
this.conditionFormGroup.get('spec.count').disable();
this.conditionFormGroup.get('spec.predicate').enable();
if (resetDuration) {
this.conditionFormGroup.get('spec').patchValue({
count: null
predicate: null
});
}
break;
case AlarmConditionType.REPEATING:
this.conditionFormGroup.get('spec.count').enable();
this.conditionFormGroup.get('spec.value').disable();
this.conditionFormGroup.get('spec.predicate').enable();
this.conditionFormGroup.get('spec.unit').disable();
if (resetDuration) {
this.conditionFormGroup.get('spec').patchValue({
value: null,
unit: null
unit: null,
predicate: null
});
}
break;
case AlarmConditionType.SIMPLE:
this.conditionFormGroup.get('spec.value').disable();
this.conditionFormGroup.get('spec.unit').disable();
this.conditionFormGroup.get('spec.count').disable();
this.conditionFormGroup.get('spec.predicate').disable();
if (resetDuration) {
this.conditionFormGroup.get('spec').patchValue({
value: null,
unit: null,
count: null
predicate: null
});
}
break;
}
this.conditionFormGroup.get('spec.value').updateValueAndValidity({emitEvent});
this.conditionFormGroup.get('spec.predicate').updateValueAndValidity({emitEvent});
this.conditionFormGroup.get('spec.unit').updateValueAndValidity({emitEvent});
this.conditionFormGroup.get('spec.count').updateValueAndValidity({emitEvent});
}
cancel(): void {

42
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.ts

@ -18,22 +18,25 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
FormControl, FormGroup,
FormControl,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
Validator, Validators
Validator,
Validators
} from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { deepClone, isUndefined } from '@core/utils';
import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common';
import { AlarmCondition, AlarmConditionSpec, AlarmConditionType } from '@shared/models/device.models';
import { AlarmCondition, AlarmConditionType } from '@shared/models/device.models';
import {
AlarmRuleConditionDialogComponent,
AlarmRuleConditionDialogData
} from '@home/components/profile/alarm/alarm-rule-condition-dialog.component';
import { TimeUnit } from '@shared/models/time/time.models';
import { EntityId } from '@shared/models/id/entity-id';
import { dynamicValueSourceTypeTranslationMap } from '@shared/models/query/query.models';
@Component({
selector: 'tb-alarm-rule-condition',
@ -159,22 +162,43 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit
let duringText = '';
switch (spec.unit) {
case TimeUnit.SECONDS:
duringText = this.translate.instant('timewindow.seconds', {seconds: spec.value});
duringText = this.translate.instant('timewindow.seconds', {seconds: spec.predicate.defaultValue});
break;
case TimeUnit.MINUTES:
duringText = this.translate.instant('timewindow.minutes', {minutes: spec.value});
duringText = this.translate.instant('timewindow.minutes', {minutes: spec.predicate.defaultValue});
break;
case TimeUnit.HOURS:
duringText = this.translate.instant('timewindow.hours', {hours: spec.value});
duringText = this.translate.instant('timewindow.hours', {hours: spec.predicate.defaultValue});
break;
case TimeUnit.DAYS:
duringText = this.translate.instant('timewindow.days', {days: spec.value});
duringText = this.translate.instant('timewindow.days', {days: spec.predicate.defaultValue});
break;
}
this.specText = this.translate.instant('device-profile.condition-during', {during: duringText});
if (spec.predicate.dynamicValue && spec.predicate.dynamicValue.sourceAttribute) {
const attributeSource =
this.translate.instant(dynamicValueSourceTypeTranslationMap.get(spec.predicate.dynamicValue.sourceType));
this.specText = this.translate.instant('device-profile.condition-during-dynamic', {
during: duringText,
attribute: `${attributeSource}.${spec.predicate.dynamicValue.sourceAttribute}`
});
} else {
this.specText = this.translate.instant('device-profile.condition-during', {
during: duringText
});
}
break;
case AlarmConditionType.REPEATING:
this.specText = this.translate.instant('device-profile.condition-repeat-times', {count: spec.count});
if (spec.predicate.dynamicValue && spec.predicate.dynamicValue.sourceAttribute) {
const attributeSource =
this.translate.instant(dynamicValueSourceTypeTranslationMap.get(spec.predicate.dynamicValue.sourceType));
this.specText = this.translate.instant('device-profile.condition-repeat-times-dynamic', {
count: spec.predicate.defaultValue,
attribute: `${attributeSource}.${spec.predicate.dynamicValue.sourceAttribute}`
});
} else {
this.specText = this.translate.instant('device-profile.condition-repeat-times',
{count: spec.predicate.defaultValue});
}
break;
}
}

5
ui-ngx/src/app/shared/models/device.models.ts

@ -23,7 +23,7 @@ import { EntitySearchQuery } from '@shared/models/relation.models';
import { DeviceProfileId } from '@shared/models/id/device-profile-id';
import { RuleChainId } from '@shared/models/id/rule-chain-id';
import { EntityInfoData } from '@shared/models/entity.models';
import { KeyFilter } from '@shared/models/query/query.models';
import { FilterPredicateValue, KeyFilter } from '@shared/models/query/query.models';
import { TimeUnit } from '@shared/models/time/time.models';
import * as _moment from 'moment';
import { AbstractControl, ValidationErrors } from '@angular/forms';
@ -424,8 +424,7 @@ export const AlarmConditionTypeTranslationMap = new Map<AlarmConditionType, stri
export interface AlarmConditionSpec{
type?: AlarmConditionType;
unit?: TimeUnit;
value?: number;
count?: number;
predicate: FilterPredicateValue<number>;
}
export interface AlarmCondition {

15
ui-ngx/src/app/shared/models/query/query.models.ts

@ -202,6 +202,17 @@ export function createDefaultFilterPredicate(valueType: EntityKeyValueType, comp
return predicate;
}
export function getDynamicSourcesForAllowUser(allow: boolean): DynamicValueSourceType[] {
const dynamicValueSourceTypes = [DynamicValueSourceType.CURRENT_TENANT,
DynamicValueSourceType.CURRENT_CUSTOMER];
if (allow) {
dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_USER);
} else {
dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_DEVICE);
}
return dynamicValueSourceTypes;
}
export enum FilterPredicateType {
STRING = 'STRING',
NUMERIC = 'NUMERIC',
@ -289,6 +300,10 @@ export const dynamicValueSourceTypeTranslationMap = new Map<DynamicValueSourceTy
]
);
export const inheritModeForDynamicValueSourceType = [
DynamicValueSourceType.CURRENT_CUSTOMER,
DynamicValueSourceType.CURRENT_DEVICE];
export interface DynamicValue<T> {
sourceType: DynamicValueSourceType;
sourceAttribute: string;

2
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -1179,6 +1179,7 @@
"condition-type-simple": "Simple",
"condition-type-duration": "Duration",
"condition-during": "During {{during}}",
"condition-during-dynamic": "During \"{{ attribute }}\" ({{during}})",
"condition-type-repeating": "Repeating",
"condition-type-required": "Condition type is required.",
"condition-repeating-value": "Count of events",
@ -1186,6 +1187,7 @@
"condition-repeating-value-pattern": "Count of events should be integers.",
"condition-repeating-value-required": "Count of events is required.",
"condition-repeat-times": "Repeats { count, plural, 1 {1 time} other {# times} }",
"condition-repeat-times-dynamic": "Repeats \"{ attribute }\" ({ count, plural, 1 {1 time} other {# times} })",
"schedule-type": "Scheduler type",
"schedule-type-required": "Scheduler type is required.",
"schedule": "Schedule",

Loading…
Cancel
Save