Added functionality to push latest timeseries values to edge on assing entities to edge
This commit is contained in:
		
							parent
							
								
									bcdda93de3
								
							
						
					
					
						commit
						031706e1cf
					
				@ -45,6 +45,7 @@ import org.thingsboard.server.common.data.id.UserId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.WidgetsBundleId;
 | 
			
		||||
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
 | 
			
		||||
import org.thingsboard.server.common.data.kv.DataType;
 | 
			
		||||
import org.thingsboard.server.common.data.kv.TsKvEntry;
 | 
			
		||||
import org.thingsboard.server.common.data.relation.EntityRelation;
 | 
			
		||||
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
 | 
			
		||||
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
 | 
			
		||||
@ -52,13 +53,10 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup;
 | 
			
		||||
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
 | 
			
		||||
import org.thingsboard.server.common.data.widget.WidgetType;
 | 
			
		||||
import org.thingsboard.server.common.data.widget.WidgetsBundle;
 | 
			
		||||
import org.thingsboard.server.dao.asset.AssetProfileService;
 | 
			
		||||
import org.thingsboard.server.dao.asset.AssetService;
 | 
			
		||||
import org.thingsboard.server.dao.attributes.AttributesService;
 | 
			
		||||
import org.thingsboard.server.dao.device.DeviceProfileService;
 | 
			
		||||
import org.thingsboard.server.dao.device.DeviceService;
 | 
			
		||||
import org.thingsboard.server.dao.edge.EdgeEventService;
 | 
			
		||||
import org.thingsboard.server.dao.relation.RelationService;
 | 
			
		||||
import org.thingsboard.server.dao.timeseries.TimeseriesService;
 | 
			
		||||
import org.thingsboard.server.dao.widget.WidgetTypeService;
 | 
			
		||||
import org.thingsboard.server.dao.widget.WidgetsBundleService;
 | 
			
		||||
import org.thingsboard.server.gen.edge.v1.AttributesRequestMsg;
 | 
			
		||||
@ -92,25 +90,16 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private AttributesService attributesService;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private TimeseriesService timeseriesService;
 | 
			
		||||
    
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RelationService relationService;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private DeviceService deviceService;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private AssetService assetService;
 | 
			
		||||
 | 
			
		||||
    @Lazy
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private TbEntityViewService entityViewService;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private DeviceProfileService deviceProfileService;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private AssetProfileService assetProfileService;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private WidgetsBundleService widgetsBundleService;
 | 
			
		||||
 | 
			
		||||
@ -141,77 +130,89 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
 | 
			
		||||
        EntityId entityId = EntityIdFactory.getByTypeAndUuid(
 | 
			
		||||
                EntityType.valueOf(attributesRequestMsg.getEntityType()),
 | 
			
		||||
                new UUID(attributesRequestMsg.getEntityIdMSB(), attributesRequestMsg.getEntityIdLSB()));
 | 
			
		||||
        final EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(entityId.getEntityType());
 | 
			
		||||
        if (type == null) {
 | 
			
		||||
        final EdgeEventType entityType = EdgeUtils.getEdgeEventTypeByEntityType(entityId.getEntityType());
 | 
			
		||||
        if (entityType == null) {
 | 
			
		||||
            log.warn("[{}] Type doesn't supported {}", tenantId, entityId.getEntityType());
 | 
			
		||||
            return Futures.immediateFuture(null);
 | 
			
		||||
        }
 | 
			
		||||
        SettableFuture<Void> futureToSet = SettableFuture.create();
 | 
			
		||||
        String scope = attributesRequestMsg.getScope();
 | 
			
		||||
        ListenableFuture<List<AttributeKvEntry>> findAttrFuture = attributesService.findAll(tenantId, entityId, scope);
 | 
			
		||||
        Futures.addCallback(findAttrFuture, new FutureCallback<>() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onSuccess(@Nullable List<AttributeKvEntry> ssAttributes) {
 | 
			
		||||
                if (ssAttributes == null || ssAttributes.isEmpty()) {
 | 
			
		||||
                    log.trace("[{}][{}] No attributes found for entity {} [{}]", tenantId,
 | 
			
		||||
                            edge.getName(),
 | 
			
		||||
                            entityId.getEntityType(),
 | 
			
		||||
                            entityId.getId());
 | 
			
		||||
                    futureToSet.set(null);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                try {
 | 
			
		||||
                    Map<String, Object> entityData = new HashMap<>();
 | 
			
		||||
                    ObjectNode attributes = JacksonUtil.OBJECT_MAPPER.createObjectNode();
 | 
			
		||||
                    for (AttributeKvEntry attr : ssAttributes) {
 | 
			
		||||
                        if (DefaultDeviceStateService.PERSISTENT_ATTRIBUTES.contains(attr.getKey())
 | 
			
		||||
                                && !DefaultDeviceStateService.INACTIVITY_TIMEOUT.equals(attr.getKey())) {
 | 
			
		||||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
                        if (attr.getDataType() == DataType.BOOLEAN && attr.getBooleanValue().isPresent()) {
 | 
			
		||||
                            attributes.put(attr.getKey(), attr.getBooleanValue().get());
 | 
			
		||||
                        } else if (attr.getDataType() == DataType.DOUBLE && attr.getDoubleValue().isPresent()) {
 | 
			
		||||
                            attributes.put(attr.getKey(), attr.getDoubleValue().get());
 | 
			
		||||
                        } else if (attr.getDataType() == DataType.LONG && attr.getLongValue().isPresent()) {
 | 
			
		||||
                            attributes.put(attr.getKey(), attr.getLongValue().get());
 | 
			
		||||
                        } else {
 | 
			
		||||
                            attributes.put(attr.getKey(), attr.getValueAsString());
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    entityData.put("kv", attributes);
 | 
			
		||||
                    entityData.put("scope", scope);
 | 
			
		||||
                    JsonNode body = JacksonUtil.OBJECT_MAPPER.valueToTree(entityData);
 | 
			
		||||
                    log.debug("Sending attributes data msg, entityId [{}], attributes [{}]", entityId, body);
 | 
			
		||||
                    ListenableFuture<Void> future = saveEdgeEvent(tenantId, edge.getId(), type, EdgeEventActionType.ATTRIBUTES_UPDATED, entityId, body);
 | 
			
		||||
                    Futures.addCallback(future, new FutureCallback<>() {
 | 
			
		||||
                        @Override
 | 
			
		||||
                        public void onSuccess(@Nullable Void unused) {
 | 
			
		||||
                            futureToSet.set(null);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        @Override
 | 
			
		||||
                        public void onFailure(Throwable throwable) {
 | 
			
		||||
                            String errMsg = String.format("[%s] Failed to save edge event [%s]", edge.getId(), attributesRequestMsg);
 | 
			
		||||
                            log.error(errMsg, throwable);
 | 
			
		||||
                            futureToSet.setException(new RuntimeException(errMsg, throwable));
 | 
			
		||||
                        }
 | 
			
		||||
                    }, dbCallbackExecutorService);
 | 
			
		||||
                } catch (Exception e) {
 | 
			
		||||
                    String errMsg = String.format("[%s] Failed to save attribute updates to the edge [%s]", edge.getId(), attributesRequestMsg);
 | 
			
		||||
                    log.error(errMsg, e);
 | 
			
		||||
                    futureToSet.setException(new RuntimeException(errMsg, e));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onFailure(Throwable t) {
 | 
			
		||||
                String errMsg = String.format("[%s] Can't find attributes [%s]", edge.getId(), attributesRequestMsg);
 | 
			
		||||
                log.error(errMsg, t);
 | 
			
		||||
                futureToSet.setException(new RuntimeException(errMsg, t));
 | 
			
		||||
        return Futures.transformAsync(findAttrFuture, ssAttributes -> {
 | 
			
		||||
            if (ssAttributes == null || ssAttributes.isEmpty()) {
 | 
			
		||||
                log.trace("[{}][{}] No attributes found for entity {} [{}]", tenantId,
 | 
			
		||||
                        edge.getName(),
 | 
			
		||||
                        entityId.getEntityType(),
 | 
			
		||||
                        entityId.getId());
 | 
			
		||||
                return Futures.immediateFuture(null);
 | 
			
		||||
            }
 | 
			
		||||
            return processEntityAttributesAndAddToEdgeQueue(tenantId, entityId, edge, entityType, scope, ssAttributes, attributesRequestMsg);
 | 
			
		||||
        }, dbCallbackExecutorService);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ListenableFuture<Void> processEntityAttributesAndAddToEdgeQueue(TenantId tenantId, EntityId entityId, Edge edge,
 | 
			
		||||
                                                                            EdgeEventType entityType, String scope, List<AttributeKvEntry> ssAttributes,
 | 
			
		||||
                                                                            AttributesRequestMsg attributesRequestMsg) {
 | 
			
		||||
        try {
 | 
			
		||||
            Map<String, Object> entityData = new HashMap<>();
 | 
			
		||||
            ObjectNode attributes = JacksonUtil.OBJECT_MAPPER.createObjectNode();
 | 
			
		||||
            for (AttributeKvEntry attr : ssAttributes) {
 | 
			
		||||
                if (DefaultDeviceStateService.PERSISTENT_ATTRIBUTES.contains(attr.getKey())
 | 
			
		||||
                        && !DefaultDeviceStateService.INACTIVITY_TIMEOUT.equals(attr.getKey())) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                if (attr.getDataType() == DataType.BOOLEAN && attr.getBooleanValue().isPresent()) {
 | 
			
		||||
                    attributes.put(attr.getKey(), attr.getBooleanValue().get());
 | 
			
		||||
                } else if (attr.getDataType() == DataType.DOUBLE && attr.getDoubleValue().isPresent()) {
 | 
			
		||||
                    attributes.put(attr.getKey(), attr.getDoubleValue().get());
 | 
			
		||||
                } else if (attr.getDataType() == DataType.LONG && attr.getLongValue().isPresent()) {
 | 
			
		||||
                    attributes.put(attr.getKey(), attr.getLongValue().get());
 | 
			
		||||
                } else {
 | 
			
		||||
                    attributes.put(attr.getKey(), attr.getValueAsString());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            ListenableFuture<Void> future;
 | 
			
		||||
            if (attributes.size() > 0) {
 | 
			
		||||
                entityData.put("kv", attributes);
 | 
			
		||||
                entityData.put("scope", scope);
 | 
			
		||||
                JsonNode body = JacksonUtil.OBJECT_MAPPER.valueToTree(entityData);
 | 
			
		||||
                log.debug("Sending attributes data msg, entityId [{}], attributes [{}]", entityId, body);
 | 
			
		||||
                future = saveEdgeEvent(tenantId, edge.getId(), entityType, EdgeEventActionType.ATTRIBUTES_UPDATED, entityId, body);
 | 
			
		||||
            } else {
 | 
			
		||||
                future = Futures.immediateFuture(null);
 | 
			
		||||
            }
 | 
			
		||||
            return Futures.transformAsync(future, v -> processLatestTimeseriesAndAddToEdgeQueue(tenantId, entityId, edge, entityType), dbCallbackExecutorService);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            String errMsg = String.format("[%s] Failed to save attribute updates to the edge [%s]", edge.getId(), attributesRequestMsg);
 | 
			
		||||
            log.error(errMsg, e);
 | 
			
		||||
            return Futures.immediateFailedFuture(new RuntimeException(errMsg, e));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private ListenableFuture<Void> processLatestTimeseriesAndAddToEdgeQueue(TenantId tenantId, EntityId entityId, Edge edge,
 | 
			
		||||
                                                                            EdgeEventType entityType) {
 | 
			
		||||
        ListenableFuture<List<TsKvEntry>> getAllLatestFuture = timeseriesService.findAllLatest(tenantId, entityId);
 | 
			
		||||
        return Futures.transformAsync(getAllLatestFuture, tsKvEntries -> {
 | 
			
		||||
            if (tsKvEntries == null || tsKvEntries.isEmpty()) {
 | 
			
		||||
                log.trace("[{}][{}] No timeseries found for entity {} [{}]", tenantId,
 | 
			
		||||
                        edge.getName(),
 | 
			
		||||
                        entityId.getEntityType(),
 | 
			
		||||
                        entityId.getId());
 | 
			
		||||
                return Futures.immediateFuture(null);
 | 
			
		||||
            }
 | 
			
		||||
            List<ListenableFuture<Void>> futures = new ArrayList<>();
 | 
			
		||||
            for (TsKvEntry tsKvEntry : tsKvEntries) {
 | 
			
		||||
                if (DefaultDeviceStateService.PERSISTENT_ATTRIBUTES.contains(tsKvEntry.getKey())) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                ObjectNode entityBody = JacksonUtil.OBJECT_MAPPER.createObjectNode();
 | 
			
		||||
                ObjectNode ts = JacksonUtil.OBJECT_MAPPER.createObjectNode();
 | 
			
		||||
                ts.put(tsKvEntry.getKey(), tsKvEntry.getValueAsString());
 | 
			
		||||
                entityBody.set("data", ts);
 | 
			
		||||
                entityBody.put("ts", tsKvEntry.getTs());
 | 
			
		||||
                futures.add(saveEdgeEvent(tenantId, edge.getId(), entityType, EdgeEventActionType.TIMESERIES_UPDATED, entityId, JacksonUtil.valueToTree(entityBody)));
 | 
			
		||||
            }
 | 
			
		||||
            return Futures.transform(Futures.allAsList(futures), v -> null, dbCallbackExecutorService);
 | 
			
		||||
        }, dbCallbackExecutorService);
 | 
			
		||||
        return futureToSet;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 | 
			
		||||
@ -323,6 +323,9 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest {
 | 
			
		||||
                "inactivityTimeout", "3600000");
 | 
			
		||||
        sendAttributesRequestAndVerify(device, DataConstants.SHARED_SCOPE, "{\"key2\":\"value2\"}",
 | 
			
		||||
                "key2", "value2");
 | 
			
		||||
 | 
			
		||||
        doDelete("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/" + DataConstants.SERVER_SCOPE, "keys","key1, inactivityTimeout");
 | 
			
		||||
        doDelete("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/" + DataConstants.SHARED_SCOPE, "keys", "key2");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
@ -640,4 +643,53 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest {
 | 
			
		||||
 | 
			
		||||
        client.disconnect();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testVerifyDeliveryOfLatestTimeseriesOnAttributesRequest() throws Exception {
 | 
			
		||||
        Device device = findDeviceByName("Edge Device 1");
 | 
			
		||||
 | 
			
		||||
        JsonNode timeseriesData = mapper.readTree("{\"temperature\":25}");
 | 
			
		||||
 | 
			
		||||
        doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE,
 | 
			
		||||
                timeseriesData);
 | 
			
		||||
 | 
			
		||||
        // Wait before device timeseries saved to database before requesting them from edge
 | 
			
		||||
        Awaitility.await()
 | 
			
		||||
                .atMost(10, TimeUnit.SECONDS)
 | 
			
		||||
                .until(() -> {
 | 
			
		||||
                    String urlTemplate = "/api/plugins/telemetry/DEVICE/" + device.getId() + "/keys/timeseries";
 | 
			
		||||
                    List<String> actualKeys = doGetAsyncTyped(urlTemplate, new TypeReference<>() {});
 | 
			
		||||
                    return actualKeys != null && !actualKeys.isEmpty() && actualKeys.contains("temperature");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
        UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
 | 
			
		||||
        AttributesRequestMsg.Builder attributesRequestMsgBuilder = AttributesRequestMsg.newBuilder();
 | 
			
		||||
        attributesRequestMsgBuilder.setEntityIdMSB(device.getUuidId().getMostSignificantBits());
 | 
			
		||||
        attributesRequestMsgBuilder.setEntityIdLSB(device.getUuidId().getLeastSignificantBits());
 | 
			
		||||
        attributesRequestMsgBuilder.setEntityType(EntityType.DEVICE.name());
 | 
			
		||||
        attributesRequestMsgBuilder.setScope(DataConstants.SERVER_SCOPE);
 | 
			
		||||
        uplinkMsgBuilder.addAttributesRequestMsg(attributesRequestMsgBuilder.build());
 | 
			
		||||
 | 
			
		||||
        edgeImitator.expectResponsesAmount(1);
 | 
			
		||||
        edgeImitator.expectMessageAmount(1);
 | 
			
		||||
        edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
 | 
			
		||||
        Assert.assertTrue(edgeImitator.waitForResponses());
 | 
			
		||||
        Assert.assertTrue(edgeImitator.waitForMessages());
 | 
			
		||||
 | 
			
		||||
        AbstractMessage latestMessage = edgeImitator.getLatestMessage();
 | 
			
		||||
        Assert.assertTrue(latestMessage instanceof EntityDataProto);
 | 
			
		||||
        EntityDataProto latestEntityDataMsg = (EntityDataProto) latestMessage;
 | 
			
		||||
        Assert.assertEquals(device.getUuidId().getMostSignificantBits(), latestEntityDataMsg.getEntityIdMSB());
 | 
			
		||||
        Assert.assertEquals(device.getUuidId().getLeastSignificantBits(), latestEntityDataMsg.getEntityIdLSB());
 | 
			
		||||
        Assert.assertEquals(device.getId().getEntityType().name(), latestEntityDataMsg.getEntityType());
 | 
			
		||||
        Assert.assertTrue(latestEntityDataMsg.hasPostTelemetryMsg());
 | 
			
		||||
 | 
			
		||||
        TransportProtos.PostTelemetryMsg timeseriesUpdatedMsg = latestEntityDataMsg.getPostTelemetryMsg();
 | 
			
		||||
        Assert.assertEquals(1, timeseriesUpdatedMsg.getTsKvListList().size());
 | 
			
		||||
        TransportProtos.TsKvListProto tsKvListProto = timeseriesUpdatedMsg.getTsKvListList().get(0);
 | 
			
		||||
        Assert.assertEquals(1, tsKvListProto.getKvList().size());
 | 
			
		||||
        TransportProtos.KeyValueProto keyValueProto = tsKvListProto.getKvList().get(0);
 | 
			
		||||
        Assert.assertEquals(25, keyValueProto.getLongV());
 | 
			
		||||
        Assert.assertEquals("temperature", keyValueProto.getKey());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user