|
|
|
@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.profile; |
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper; |
|
|
|
import com.fasterxml.jackson.databind.node.ObjectNode; |
|
|
|
import com.google.common.util.concurrent.Futures; |
|
|
|
import com.google.common.util.concurrent.ListenableFuture; |
|
|
|
import org.junit.Test; |
|
|
|
import org.junit.runner.RunWith; |
|
|
|
import org.mockito.AdditionalAnswers; |
|
|
|
@ -29,15 +30,22 @@ import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; |
|
|
|
import org.thingsboard.rule.engine.api.TbContext; |
|
|
|
import org.thingsboard.rule.engine.api.TbNodeConfiguration; |
|
|
|
import org.thingsboard.rule.engine.api.TbNodeException; |
|
|
|
import org.thingsboard.server.common.data.DataConstants; |
|
|
|
import org.thingsboard.server.common.data.Device; |
|
|
|
import org.thingsboard.server.common.data.DeviceProfile; |
|
|
|
import org.thingsboard.server.common.data.EntityType; |
|
|
|
import org.thingsboard.server.common.data.alarm.AlarmSeverity; |
|
|
|
import org.thingsboard.server.common.data.device.profile.AlarmCondition; |
|
|
|
import org.thingsboard.server.common.data.device.profile.AlarmRule; |
|
|
|
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; |
|
|
|
import org.thingsboard.server.common.data.device.profile.DeviceProfileData; |
|
|
|
import org.thingsboard.server.common.data.id.CustomerId; |
|
|
|
import org.thingsboard.server.common.data.id.DeviceId; |
|
|
|
import org.thingsboard.server.common.data.id.DeviceProfileId; |
|
|
|
import org.thingsboard.server.common.data.id.TenantId; |
|
|
|
import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
|
|
|
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.EntityKeyValueType; |
|
|
|
@ -48,13 +56,19 @@ import org.thingsboard.server.common.msg.TbMsg; |
|
|
|
import org.thingsboard.server.common.msg.TbMsgDataType; |
|
|
|
import org.thingsboard.server.common.msg.TbMsgMetaData; |
|
|
|
import org.thingsboard.server.common.msg.session.SessionMsgType; |
|
|
|
import org.thingsboard.server.dao.attributes.AttributesService; |
|
|
|
import org.thingsboard.server.dao.device.DeviceService; |
|
|
|
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.util.Collections; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Optional; |
|
|
|
import java.util.TreeMap; |
|
|
|
import java.util.UUID; |
|
|
|
|
|
|
|
import static org.mockito.ArgumentMatchers.eq; |
|
|
|
import static org.mockito.Mockito.verify; |
|
|
|
|
|
|
|
@RunWith(MockitoJUnitRunner.class) |
|
|
|
@ -74,9 +88,12 @@ public class TbDeviceProfileNodeTest { |
|
|
|
private RuleEngineAlarmService alarmService; |
|
|
|
@Mock |
|
|
|
private DeviceService deviceService; |
|
|
|
@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()); |
|
|
|
|
|
|
|
@Test |
|
|
|
@ -186,12 +203,244 @@ public class TbDeviceProfileNodeTest { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void testCurrentDeviceAttributeForDynamicValue() 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>> listListenableFutureWithLess = |
|
|
|
Futures.immediateFuture(Collections.singletonList(entry)); |
|
|
|
|
|
|
|
KeyFilter highTempFilter = new KeyFilter(); |
|
|
|
highTempFilter.setKey(new EntityKey(EntityKeyType.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)); |
|
|
|
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(listListenableFutureWithLess); |
|
|
|
|
|
|
|
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", 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(); |
|
|
|
|
|
|
|
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", "lessAttribute" |
|
|
|
); |
|
|
|
|
|
|
|
AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); |
|
|
|
attributeKvEntity.setId(compositeKey); |
|
|
|
attributeKvEntity.setLongValue(30L); |
|
|
|
attributeKvEntity.setLastUpdateTs(0L); |
|
|
|
|
|
|
|
AttributeKvEntry entry = attributeKvEntity.toData(); |
|
|
|
ListenableFuture<List<AttributeKvEntry>> listListenableFutureWithLess = |
|
|
|
Futures.immediateFuture(Collections.singletonList(entry)); |
|
|
|
ListenableFuture<Optional<AttributeKvEntry>> optionalListenableFutureWithLess = |
|
|
|
Futures.immediateFuture(Optional.of(entry)); |
|
|
|
|
|
|
|
KeyFilter lowTempFilter = new KeyFilter(); |
|
|
|
lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); |
|
|
|
lowTempFilter.setValueType(EntityKeyValueType.NUMERIC); |
|
|
|
NumericFilterPredicate lowTempPredicate = new NumericFilterPredicate(); |
|
|
|
lowTempPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS); |
|
|
|
lowTempPredicate.setValue( |
|
|
|
new FilterPredicateValue<>( |
|
|
|
20.0, |
|
|
|
null, |
|
|
|
new DynamicValue<>(DynamicValueSourceType.CURRENT_CUSTOMER, "lessAttribute")) |
|
|
|
); |
|
|
|
lowTempFilter.setPredicate(lowTempPredicate); |
|
|
|
AlarmCondition alarmCondition = new AlarmCondition(); |
|
|
|
alarmCondition.setCondition(Collections.singletonList(lowTempFilter)); |
|
|
|
AlarmRule alarmRule = new AlarmRule(); |
|
|
|
alarmRule.setCondition(alarmCondition); |
|
|
|
DeviceProfileAlarm dpa = new DeviceProfileAlarm(); |
|
|
|
dpa.setId("lesstempID"); |
|
|
|
dpa.setAlarmType("lessTemperatureAlarm"); |
|
|
|
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, "lessTemperatureAlarm")) |
|
|
|
.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(listListenableFutureWithLess); |
|
|
|
Mockito.when(ctx.getDeviceService().findDeviceById(tenantId, deviceId)) |
|
|
|
.thenReturn(device); |
|
|
|
Mockito.when(attributesService.find(eq(tenantId), eq(customerId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) |
|
|
|
.thenReturn(optionalListenableFutureWithLess); |
|
|
|
|
|
|
|
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", 25); |
|
|
|
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 testCurrentTenantAttributeForDynamicValue() throws Exception { |
|
|
|
init(); |
|
|
|
|
|
|
|
DeviceProfile deviceProfile = new DeviceProfile(); |
|
|
|
DeviceProfileData deviceProfileData = new DeviceProfileData(); |
|
|
|
|
|
|
|
Device device = new Device(); |
|
|
|
device.setId(deviceId); |
|
|
|
device.setCustomerId(customerId); |
|
|
|
|
|
|
|
AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( |
|
|
|
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "lessAttribute" |
|
|
|
); |
|
|
|
|
|
|
|
AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); |
|
|
|
attributeKvEntity.setId(compositeKey); |
|
|
|
attributeKvEntity.setLongValue(50L); |
|
|
|
attributeKvEntity.setLastUpdateTs(0L); |
|
|
|
|
|
|
|
AttributeKvEntry entry = attributeKvEntity.toData(); |
|
|
|
ListenableFuture<List<AttributeKvEntry>> listListenableFutureWithLess = |
|
|
|
Futures.immediateFuture(Collections.singletonList(entry)); |
|
|
|
ListenableFuture<Optional<AttributeKvEntry>> optionalListenableFutureWithLess = |
|
|
|
Futures.immediateFuture(Optional.of(entry)); |
|
|
|
|
|
|
|
KeyFilter lowTempFilter = new KeyFilter(); |
|
|
|
lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); |
|
|
|
lowTempFilter.setValueType(EntityKeyValueType.NUMERIC); |
|
|
|
NumericFilterPredicate lowTempPredicate = new NumericFilterPredicate(); |
|
|
|
lowTempPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS); |
|
|
|
lowTempPredicate.setValue( |
|
|
|
new FilterPredicateValue<>( |
|
|
|
32.0, |
|
|
|
null, |
|
|
|
new DynamicValue<>(DynamicValueSourceType.CURRENT_TENANT, "lessAttribute")) |
|
|
|
); |
|
|
|
lowTempFilter.setPredicate(lowTempPredicate); |
|
|
|
AlarmCondition alarmCondition = new AlarmCondition(); |
|
|
|
alarmCondition.setCondition(Collections.singletonList(lowTempFilter)); |
|
|
|
AlarmRule alarmRule = new AlarmRule(); |
|
|
|
alarmRule.setCondition(alarmCondition); |
|
|
|
DeviceProfileAlarm dpa = new DeviceProfileAlarm(); |
|
|
|
dpa.setId("lesstempID"); |
|
|
|
dpa.setAlarmType("lessTemperatureAlarm"); |
|
|
|
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, "lessTemperatureAlarm")) |
|
|
|
.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(listListenableFutureWithLess); |
|
|
|
Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) |
|
|
|
.thenReturn(optionalListenableFutureWithLess); |
|
|
|
|
|
|
|
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", 40); |
|
|
|
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()); |
|
|
|
} |
|
|
|
|
|
|
|
private void init() throws TbNodeException { |
|
|
|
Mockito.when(ctx.getTenantId()).thenReturn(tenantId); |
|
|
|
Mockito.when(ctx.getDeviceProfileCache()).thenReturn(cache); |
|
|
|
Mockito.when(ctx.getTimeseriesService()).thenReturn(timeseriesService); |
|
|
|
Mockito.when(ctx.getAlarmService()).thenReturn(alarmService); |
|
|
|
Mockito.when(ctx.getDeviceService()).thenReturn(deviceService); |
|
|
|
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); |
|
|
|
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.createObjectNode()); |
|
|
|
node = new TbDeviceProfileNode(); |
|
|
|
node.init(ctx, nodeConfiguration); |
|
|
|
|