|
|
|
@ -16,6 +16,7 @@ |
|
|
|
package org.thingsboard.server.service.subscription; |
|
|
|
|
|
|
|
import lombok.Data; |
|
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
|
import org.thingsboard.server.common.data.id.CustomerId; |
|
|
|
import org.thingsboard.server.common.data.id.EntityId; |
|
|
|
import org.thingsboard.server.common.data.id.TenantId; |
|
|
|
@ -34,12 +35,13 @@ import org.thingsboard.server.service.telemetry.sub.SubscriptionUpdate; |
|
|
|
|
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.Arrays; |
|
|
|
import java.util.Collection; |
|
|
|
import java.util.Collections; |
|
|
|
import java.util.HashMap; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.stream.Collectors; |
|
|
|
|
|
|
|
@Slf4j |
|
|
|
@Data |
|
|
|
public class TbEntityDataSubCtx { |
|
|
|
|
|
|
|
@ -54,7 +56,6 @@ public class TbEntityDataSubCtx { |
|
|
|
private PageData<EntityData> data; |
|
|
|
private boolean initialDataSent; |
|
|
|
private List<TbSubscription> tbSubs; |
|
|
|
private int internalSubIdx; |
|
|
|
private Map<Integer, EntityId> subToEntityIdMap; |
|
|
|
|
|
|
|
public TbEntityDataSubCtx(String serviceId, TelemetryWebSocketService wsService, TelemetryWebSocketSessionRef sessionRef, int cmdId) { |
|
|
|
@ -82,7 +83,6 @@ public class TbEntityDataSubCtx { |
|
|
|
|
|
|
|
public List<TbSubscription> createSubscriptions(List<EntityKey> keys) { |
|
|
|
this.subToEntityIdMap = new HashMap<>(); |
|
|
|
this.internalSubIdx = cmdId * MAX_SUBS_PER_CMD; |
|
|
|
tbSubs = new ArrayList<>(); |
|
|
|
List<EntityKey> attrSubKeys = new ArrayList<>(); |
|
|
|
List<EntityKey> tsSubKeys = new ArrayList<>(); |
|
|
|
@ -107,20 +107,28 @@ public class TbEntityDataSubCtx { |
|
|
|
} |
|
|
|
|
|
|
|
private TbSubscription createTsSub(EntityData entityData, List<EntityKey> tsSubKeys) { |
|
|
|
int subIdx = internalSubIdx++; |
|
|
|
int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet(); |
|
|
|
subToEntityIdMap.put(subIdx, entityData.getEntityId()); |
|
|
|
Map<String, Long> keyStates = new HashMap<>(); |
|
|
|
tsSubKeys.forEach(key -> keyStates.put(key.getKey(), 0L)); |
|
|
|
if (entityData.getLatest() != null) { |
|
|
|
Map<String, TsValue> currentValues = entityData.getLatest().get(EntityKeyType.TIME_SERIES); |
|
|
|
if (currentValues != null) { |
|
|
|
currentValues.forEach((k, v) -> keyStates.put(k, v.getTs())); |
|
|
|
currentValues.forEach((k, v) -> { |
|
|
|
log.trace("[{}][{}] Updating key: {} with ts: {}", serviceId, cmdId, k, v.getTs()); |
|
|
|
keyStates.put(k, v.getTs()); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
if (entityData.getTimeseries() != null) { |
|
|
|
entityData.getTimeseries().forEach((k, v) -> keyStates.put(k, Arrays.stream(v).map(TsValue::getTs).max(Long::compareTo).orElse(0L))); |
|
|
|
entityData.getTimeseries().forEach((k, v) -> { |
|
|
|
long ts = Arrays.stream(v).map(TsValue::getTs).max(Long::compareTo).orElse(0L); |
|
|
|
log.trace("[{}][{}] Updating key: {} with ts: {}", serviceId, cmdId, k, ts); |
|
|
|
keyStates.put(k, ts); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
log.trace("[{}][{}][{}] Creating subscription with keys: {}", serviceId, cmdId, subIdx, keyStates); |
|
|
|
return TbTimeseriesSubscription.builder() |
|
|
|
.serviceId(serviceId) |
|
|
|
.sessionId(sessionRef.getSessionId()) |
|
|
|
@ -136,19 +144,48 @@ public class TbEntityDataSubCtx { |
|
|
|
private void sendTsWsMsg(String sessionId, SubscriptionUpdate subscriptionUpdate) { |
|
|
|
EntityId entityId = subToEntityIdMap.get(subscriptionUpdate.getSubscriptionId()); |
|
|
|
if (entityId != null) { |
|
|
|
Map<String, TsValue> latest = new HashMap<>(); |
|
|
|
log.trace("[{}][{}][{}] Received subscription update: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), subscriptionUpdate); |
|
|
|
Map<String, TsValue> latestUpdate = new HashMap<>(); |
|
|
|
subscriptionUpdate.getData().forEach((k, v) -> { |
|
|
|
Object[] data = (Object[]) v.get(0); |
|
|
|
latest.put(k, new TsValue((Long) data[0], (String) data[1])); |
|
|
|
latestUpdate.put(k, new TsValue((Long) data[0], (String) data[1])); |
|
|
|
}); |
|
|
|
Map<EntityKeyType, Map<String, TsValue>> latestMap = Collections.singletonMap(EntityKeyType.TIME_SERIES, latest); |
|
|
|
EntityData entityData = new EntityData(entityId, latestMap, null); |
|
|
|
wsService.sendWsMsg(sessionId, new EntityDataUpdate(cmdId, null, Collections.singletonList(entityData))); |
|
|
|
EntityData entityData = getDataForEntity(entityId); |
|
|
|
if (entityData != null && entityData.getLatest() != null) { |
|
|
|
Map<String, TsValue> latestCtxValues = entityData.getLatest().get(EntityKeyType.TIME_SERIES); |
|
|
|
log.trace("[{}][{}][{}] Going to compare update with {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), latestCtxValues); |
|
|
|
if (latestCtxValues != null) { |
|
|
|
latestCtxValues.forEach((k, v) -> { |
|
|
|
TsValue update = latestUpdate.get(k); |
|
|
|
if (update.getTs() < v.getTs()) { |
|
|
|
log.trace("[{}][{}][{}] Removed stale update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs()); |
|
|
|
latestUpdate.remove(k); |
|
|
|
} else if ((update.getTs() == v.getTs() && update.getValue().equals(v.getValue()))) { |
|
|
|
log.trace("[{}][{}][{}] Removed duplicate update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs()); |
|
|
|
latestUpdate.remove(k); |
|
|
|
} |
|
|
|
}); |
|
|
|
//Setting new values
|
|
|
|
latestUpdate.forEach(latestCtxValues::put); |
|
|
|
} |
|
|
|
} |
|
|
|
if (!latestUpdate.isEmpty()) { |
|
|
|
Map<EntityKeyType, Map<String, TsValue>> latestMap = Collections.singletonMap(EntityKeyType.TIME_SERIES, latestUpdate); |
|
|
|
entityData = new EntityData(entityId, latestMap, null); |
|
|
|
wsService.sendWsMsg(sessionId, new EntityDataUpdate(cmdId, null, Collections.singletonList(entityData))); |
|
|
|
} |
|
|
|
} else { |
|
|
|
log.trace("[{}][{}][{}] Received stale subscription update: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), subscriptionUpdate); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private EntityData getDataForEntity(EntityId entityId) { |
|
|
|
return data.getData().stream().filter(item -> item.getEntityId().equals(entityId)).findFirst().orElse(null); |
|
|
|
} |
|
|
|
|
|
|
|
public void clearSubscriptions() { |
|
|
|
public Collection<Integer> clearSubscriptions() { |
|
|
|
List<Integer> oldSubIds = new ArrayList<>(subToEntityIdMap.keySet()); |
|
|
|
subToEntityIdMap.clear(); |
|
|
|
return oldSubIds; |
|
|
|
} |
|
|
|
} |
|
|
|
|