Ability to display and subscribe to server side attributes
This commit is contained in:
parent
3b05869463
commit
15f442fb18
@ -29,6 +29,8 @@ public class DataConstants {
|
|||||||
public static final String SERVER_SCOPE = "SERVER_SCOPE";
|
public static final String SERVER_SCOPE = "SERVER_SCOPE";
|
||||||
public static final String SHARED_SCOPE = "SHARED_SCOPE";
|
public static final String SHARED_SCOPE = "SHARED_SCOPE";
|
||||||
|
|
||||||
|
public static final String[] ALL_SCOPES = {CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE};
|
||||||
|
|
||||||
public static final String ALARM = "ALARM";
|
public static final String ALARM = "ALARM";
|
||||||
public static final String ERROR = "ERROR";
|
public static final String ERROR = "ERROR";
|
||||||
public static final String LC_EVENT = "LC_EVENT";
|
public static final String LC_EVENT = "LC_EVENT";
|
||||||
|
|||||||
@ -175,6 +175,23 @@ public class SubscriptionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onAttributesUpdateFromServer(PluginContext ctx, DeviceId deviceId, String scope, List<AttributeKvEntry> attributes) {
|
||||||
|
Optional<ServerAddress> serverAddress = ctx.resolve(deviceId);
|
||||||
|
if (!serverAddress.isPresent()) {
|
||||||
|
onLocalSubscriptionUpdate(ctx, deviceId, SubscriptionType.ATTRIBUTES, s -> {
|
||||||
|
List<TsKvEntry> subscriptionUpdate = new ArrayList<TsKvEntry>();
|
||||||
|
for (AttributeKvEntry kv : attributes) {
|
||||||
|
if (s.isAllKeys() || s.getKeyStates().containsKey(kv.getKey())) {
|
||||||
|
subscriptionUpdate.add(new BasicTsKvEntry(kv.getLastUpdateTs(), kv));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return subscriptionUpdate;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
rpcHandler.onAttributesUpdate(ctx, serverAddress.get(), deviceId, scope, attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updateSubscriptionState(String sessionId, Subscription subState, SubscriptionUpdate update) {
|
private void updateSubscriptionState(String sessionId, Subscription subState, SubscriptionUpdate update) {
|
||||||
log.trace("[{}] updating subscription state {} using onUpdate {}", sessionId, subState, update);
|
log.trace("[{}] updating subscription state {} using onUpdate {}", sessionId, subState, update);
|
||||||
update.getLatestValues().entrySet().forEach(e -> subState.setKeyState(e.getKey(), e.getValue()));
|
update.getLatestValues().entrySet().forEach(e -> subState.setKeyState(e.getKey(), e.getValue()));
|
||||||
|
|||||||
@ -43,7 +43,7 @@ public class TelemetryStoragePlugin extends AbstractPlugin<EmptyComponentConfigu
|
|||||||
|
|
||||||
public TelemetryStoragePlugin() {
|
public TelemetryStoragePlugin() {
|
||||||
this.subscriptionManager = new SubscriptionManager();
|
this.subscriptionManager = new SubscriptionManager();
|
||||||
this.restMsgHandler = new TelemetryRestMsgHandler();
|
this.restMsgHandler = new TelemetryRestMsgHandler(subscriptionManager);
|
||||||
this.ruleMsgHandler = new TelemetryRuleMsgHandler(subscriptionManager);
|
this.ruleMsgHandler = new TelemetryRuleMsgHandler(subscriptionManager);
|
||||||
this.websocketMsgHandler = new TelemetryWebsocketMsgHandler(subscriptionManager);
|
this.websocketMsgHandler = new TelemetryWebsocketMsgHandler(subscriptionManager);
|
||||||
this.rpcMsgHandler = new TelemetryRpcMsgHandler(subscriptionManager);
|
this.rpcMsgHandler = new TelemetryRpcMsgHandler(subscriptionManager);
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import org.thingsboard.server.extensions.api.plugins.handlers.DefaultRestMsgHand
|
|||||||
import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
|
import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
|
||||||
import org.thingsboard.server.extensions.api.plugins.rest.RestRequest;
|
import org.thingsboard.server.extensions.api.plugins.rest.RestRequest;
|
||||||
import org.thingsboard.server.extensions.core.plugin.telemetry.AttributeData;
|
import org.thingsboard.server.extensions.core.plugin.telemetry.AttributeData;
|
||||||
|
import org.thingsboard.server.extensions.core.plugin.telemetry.SubscriptionManager;
|
||||||
import org.thingsboard.server.extensions.core.plugin.telemetry.TsData;
|
import org.thingsboard.server.extensions.core.plugin.telemetry.TsData;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
@ -39,6 +40,12 @@ import java.util.stream.Collectors;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
|
public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
|
||||||
|
|
||||||
|
private final SubscriptionManager subscriptionManager;
|
||||||
|
|
||||||
|
public TelemetryRestMsgHandler(SubscriptionManager subscriptionManager) {
|
||||||
|
this.subscriptionManager = subscriptionManager;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleHttpGetRequest(PluginContext ctx, PluginRestMsg msg) throws ServletException {
|
public void handleHttpGetRequest(PluginContext ctx, PluginRestMsg msg) throws ServletException {
|
||||||
RestRequest request = msg.getRequest();
|
RestRequest request = msg.getRequest();
|
||||||
@ -74,9 +81,8 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
|
|||||||
if (!StringUtils.isEmpty(scope)) {
|
if (!StringUtils.isEmpty(scope)) {
|
||||||
attributes = ctx.loadAttributes(deviceId, scope);
|
attributes = ctx.loadAttributes(deviceId, scope);
|
||||||
} else {
|
} else {
|
||||||
attributes = ctx.loadAttributes(deviceId, DataConstants.CLIENT_SCOPE);
|
attributes = new ArrayList<>();
|
||||||
attributes.addAll(ctx.loadAttributes(deviceId, DataConstants.SERVER_SCOPE));
|
Arrays.stream(DataConstants.ALL_SCOPES).forEach(s -> attributes.addAll(ctx.loadAttributes(deviceId, s)));
|
||||||
attributes.addAll(ctx.loadAttributes(deviceId, DataConstants.SHARED_SCOPE));
|
|
||||||
}
|
}
|
||||||
List<String> keys = attributes.stream().map(attrKv -> attrKv.getKey()).collect(Collectors.toList());
|
List<String> keys = attributes.stream().map(attrKv -> attrKv.getKey()).collect(Collectors.toList());
|
||||||
msg.getResponseHolder().setResult(new ResponseEntity<>(keys, HttpStatus.OK));
|
msg.getResponseHolder().setResult(new ResponseEntity<>(keys, HttpStatus.OK));
|
||||||
@ -99,9 +105,8 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
|
|||||||
if (!StringUtils.isEmpty(scope)) {
|
if (!StringUtils.isEmpty(scope)) {
|
||||||
attributes = getAttributeKvEntries(ctx, scope, deviceId, keys);
|
attributes = getAttributeKvEntries(ctx, scope, deviceId, keys);
|
||||||
} else {
|
} else {
|
||||||
attributes = getAttributeKvEntries(ctx, DataConstants.CLIENT_SCOPE, deviceId, keys);
|
attributes = new ArrayList<>();
|
||||||
attributes.addAll(getAttributeKvEntries(ctx, DataConstants.SHARED_SCOPE, deviceId, keys));
|
Arrays.stream(DataConstants.ALL_SCOPES).forEach(s -> attributes.addAll(getAttributeKvEntries(ctx, s, deviceId, keys)));
|
||||||
attributes.addAll(getAttributeKvEntries(ctx, DataConstants.SERVER_SCOPE, deviceId, keys));
|
|
||||||
}
|
}
|
||||||
List<AttributeData> values = attributes.stream().map(attribute -> new AttributeData(attribute.getLastUpdateTs(),
|
List<AttributeData> values = attributes.stream().map(attribute -> new AttributeData(attribute.getLastUpdateTs(),
|
||||||
attribute.getKey(), attribute.getValue())).collect(Collectors.toList());
|
attribute.getKey(), attribute.getValue())).collect(Collectors.toList());
|
||||||
@ -145,6 +150,7 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(PluginContext ctx, Void value) {
|
public void onSuccess(PluginContext ctx, Void value) {
|
||||||
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK));
|
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK));
|
||||||
|
subscriptionManager.onAttributesUpdateFromServer(ctx, deviceId, scope, attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -172,7 +178,8 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
|
|||||||
DeviceId deviceId = DeviceId.fromString(pathParams[0]);
|
DeviceId deviceId = DeviceId.fromString(pathParams[0]);
|
||||||
String scope = pathParams[1];
|
String scope = pathParams[1];
|
||||||
if (DataConstants.SERVER_SCOPE.equals(scope) ||
|
if (DataConstants.SERVER_SCOPE.equals(scope) ||
|
||||||
DataConstants.SHARED_SCOPE.equals(scope)) {
|
DataConstants.SHARED_SCOPE.equals(scope) ||
|
||||||
|
DataConstants.CLIENT_SCOPE.equals(scope)) {
|
||||||
String keysParam = request.getParameter("keys");
|
String keysParam = request.getParameter("keys");
|
||||||
if (!StringUtils.isEmpty(keysParam)) {
|
if (!StringUtils.isEmpty(keysParam)) {
|
||||||
String[] keys = keysParam.split(",");
|
String[] keys = keysParam.split(",");
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import com.google.protobuf.InvalidProtocolBufferException;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.thingsboard.server.common.data.id.DeviceId;
|
import org.thingsboard.server.common.data.id.DeviceId;
|
||||||
|
import org.thingsboard.server.common.data.kv.*;
|
||||||
import org.thingsboard.server.common.msg.cluster.ServerAddress;
|
import org.thingsboard.server.common.msg.cluster.ServerAddress;
|
||||||
import org.thingsboard.server.extensions.api.plugins.PluginContext;
|
import org.thingsboard.server.extensions.api.plugins.PluginContext;
|
||||||
import org.thingsboard.server.extensions.api.plugins.handlers.RpcMsgHandler;
|
import org.thingsboard.server.extensions.api.plugins.handlers.RpcMsgHandler;
|
||||||
@ -42,9 +43,10 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler {
|
|||||||
private final SubscriptionManager subscriptionManager;
|
private final SubscriptionManager subscriptionManager;
|
||||||
|
|
||||||
private static final int SUBSCRIPTION_CLAZZ = 1;
|
private static final int SUBSCRIPTION_CLAZZ = 1;
|
||||||
private static final int SUBSCRIPTION_UPDATE_CLAZZ = 2;
|
private static final int ATTRIBUTES_UPDATE_CLAZZ = 2;
|
||||||
private static final int SESSION_CLOSE_CLAZZ = 3;
|
private static final int SUBSCRIPTION_UPDATE_CLAZZ = 3;
|
||||||
private static final int SUBSCRIPTION_CLOSE_CLAZZ = 4;
|
private static final int SESSION_CLOSE_CLAZZ = 4;
|
||||||
|
private static final int SUBSCRIPTION_CLOSE_CLAZZ = 5;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void process(PluginContext ctx, RpcMsg msg) {
|
public void process(PluginContext ctx, RpcMsg msg) {
|
||||||
@ -55,6 +57,9 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler {
|
|||||||
case SUBSCRIPTION_UPDATE_CLAZZ:
|
case SUBSCRIPTION_UPDATE_CLAZZ:
|
||||||
processRemoteSubscriptionUpdate(ctx, msg);
|
processRemoteSubscriptionUpdate(ctx, msg);
|
||||||
break;
|
break;
|
||||||
|
case ATTRIBUTES_UPDATE_CLAZZ:
|
||||||
|
processAttributeUpdate(ctx, msg);
|
||||||
|
break;
|
||||||
case SESSION_CLOSE_CLAZZ:
|
case SESSION_CLOSE_CLAZZ:
|
||||||
processSessionClose(ctx, msg);
|
processSessionClose(ctx, msg);
|
||||||
break;
|
break;
|
||||||
@ -76,6 +81,17 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler {
|
|||||||
subscriptionManager.onRemoteSubscriptionUpdate(ctx, proto.getSessionId(), convert(proto));
|
subscriptionManager.onRemoteSubscriptionUpdate(ctx, proto.getSessionId(), convert(proto));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void processAttributeUpdate(PluginContext ctx, RpcMsg msg) {
|
||||||
|
AttributeUpdateProto proto;
|
||||||
|
try {
|
||||||
|
proto = AttributeUpdateProto.parseFrom(msg.getMsgData());
|
||||||
|
} catch (InvalidProtocolBufferException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
subscriptionManager.onAttributesUpdateFromServer(ctx, DeviceId.fromString(proto.getDeviceId()), proto.getScope(),
|
||||||
|
proto.getDataList().stream().map(this::toAttribute).collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
private void processSubscriptionCmd(PluginContext ctx, RpcMsg msg) {
|
private void processSubscriptionCmd(PluginContext ctx, RpcMsg msg) {
|
||||||
SubscriptionProto proto;
|
SubscriptionProto proto;
|
||||||
try {
|
try {
|
||||||
@ -167,11 +183,7 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler {
|
|||||||
} else {
|
} else {
|
||||||
Map<String, List<Object>> data = new TreeMap<>();
|
Map<String, List<Object>> data = new TreeMap<>();
|
||||||
proto.getDataList().forEach(v -> {
|
proto.getDataList().forEach(v -> {
|
||||||
List<Object> values = data.get(v.getKey());
|
List<Object> values = data.computeIfAbsent(v.getKey(), k -> new ArrayList<>());
|
||||||
if (values == null) {
|
|
||||||
values = new ArrayList<>();
|
|
||||||
data.put(v.getKey(), values);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < v.getTsCount(); i++) {
|
for (int i = 0; i < v.getTsCount(); i++) {
|
||||||
Object[] value = new Object[2];
|
Object[] value = new Object[2];
|
||||||
value[0] = v.getTs(i);
|
value[0] = v.getTs(i);
|
||||||
@ -182,4 +194,59 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler {
|
|||||||
return new SubscriptionUpdate(proto.getSubscriptionId(), data);
|
return new SubscriptionUpdate(proto.getSubscriptionId(), data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onAttributesUpdate(PluginContext ctx, ServerAddress address, DeviceId deviceId, String scope, List<AttributeKvEntry> attributes) {
|
||||||
|
ctx.sendPluginRpcMsg(new RpcMsg(address, ATTRIBUTES_UPDATE_CLAZZ, getAttributesUpdateProto(deviceId, scope, attributes).toByteArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AttributeUpdateProto getAttributesUpdateProto(DeviceId deviceId, String scope, List<AttributeKvEntry> attributes) {
|
||||||
|
AttributeUpdateProto.Builder builder = AttributeUpdateProto.newBuilder();
|
||||||
|
builder.setDeviceId(deviceId.toString());
|
||||||
|
builder.setScope(scope);
|
||||||
|
attributes.forEach(
|
||||||
|
attr -> {
|
||||||
|
AttributeUpdateValueListProto.Builder dataBuilder = AttributeUpdateValueListProto.newBuilder();
|
||||||
|
dataBuilder.setKey(attr.getKey());
|
||||||
|
dataBuilder.setTs(attr.getLastUpdateTs());
|
||||||
|
dataBuilder.setValueType(attr.getDataType().ordinal());
|
||||||
|
switch (attr.getDataType()) {
|
||||||
|
case BOOLEAN:
|
||||||
|
dataBuilder.setBoolValue(attr.getBooleanValue().get());
|
||||||
|
break;
|
||||||
|
case LONG:
|
||||||
|
dataBuilder.setLongValue(attr.getLongValue().get());
|
||||||
|
break;
|
||||||
|
case DOUBLE:
|
||||||
|
dataBuilder.setDoubleValue(attr.getDoubleValue().get());
|
||||||
|
break;
|
||||||
|
case STRING:
|
||||||
|
dataBuilder.setStrValue(attr.getStrValue().get());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
builder.addData(dataBuilder.build());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private AttributeKvEntry toAttribute(AttributeUpdateValueListProto proto) {
|
||||||
|
KvEntry entry = null;
|
||||||
|
DataType type = DataType.values()[proto.getValueType()];
|
||||||
|
switch (type) {
|
||||||
|
case BOOLEAN:
|
||||||
|
entry = new BooleanDataEntry(proto.getKey(), proto.getBoolValue());
|
||||||
|
break;
|
||||||
|
case LONG:
|
||||||
|
entry = new LongDataEntry(proto.getKey(), proto.getLongValue());
|
||||||
|
break;
|
||||||
|
case DOUBLE:
|
||||||
|
entry = new DoubleDataEntry(proto.getKey(), proto.getDoubleValue());
|
||||||
|
break;
|
||||||
|
case STRING:
|
||||||
|
entry = new StringDataEntry(proto.getKey(), proto.getStrValue());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return new BaseAttributeKvEntry(entry, proto.getTs());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,7 +104,8 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
|
|||||||
SubscriptionState sub;
|
SubscriptionState sub;
|
||||||
if (keysOptional.isPresent()) {
|
if (keysOptional.isPresent()) {
|
||||||
List<String> keys = new ArrayList<>(keysOptional.get());
|
List<String> keys = new ArrayList<>(keysOptional.get());
|
||||||
List<AttributeKvEntry> data = ctx.loadAttributes(deviceId, DataConstants.CLIENT_SCOPE, keys);
|
List<AttributeKvEntry> data = new ArrayList<>();
|
||||||
|
Arrays.stream(DataConstants.ALL_SCOPES).forEach(s -> data.addAll(ctx.loadAttributes(deviceId, s, keys)));
|
||||||
List<TsKvEntry> attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList());
|
List<TsKvEntry> attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList());
|
||||||
sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), attributesData));
|
sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), attributesData));
|
||||||
|
|
||||||
@ -114,7 +115,8 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
|
|||||||
|
|
||||||
sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.ATTRIBUTES, false, subState);
|
sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.ATTRIBUTES, false, subState);
|
||||||
} else {
|
} else {
|
||||||
List<AttributeKvEntry> data = ctx.loadAttributes(deviceId, DataConstants.CLIENT_SCOPE);
|
List<AttributeKvEntry> data = new ArrayList<>();
|
||||||
|
Arrays.stream(DataConstants.ALL_SCOPES).forEach(s -> data.addAll(ctx.loadAttributes(deviceId, s)));
|
||||||
List<TsKvEntry> attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList());
|
List<TsKvEntry> attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList());
|
||||||
sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), attributesData));
|
sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), attributesData));
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,12 @@ message SubscriptionUpdateProto {
|
|||||||
repeated SubscriptionUpdateValueListProto data = 5;
|
repeated SubscriptionUpdateValueListProto data = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message AttributeUpdateProto {
|
||||||
|
string deviceId = 1;
|
||||||
|
string scope = 2;
|
||||||
|
repeated AttributeUpdateValueListProto data = 3;
|
||||||
|
}
|
||||||
|
|
||||||
message SessionCloseProto {
|
message SessionCloseProto {
|
||||||
string sessionId = 1;
|
string sessionId = 1;
|
||||||
}
|
}
|
||||||
@ -55,3 +61,13 @@ message SubscriptionUpdateValueListProto {
|
|||||||
repeated int64 ts = 2;
|
repeated int64 ts = 2;
|
||||||
repeated string value = 3;
|
repeated string value = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message AttributeUpdateValueListProto {
|
||||||
|
string key = 1;
|
||||||
|
int64 ts = 2;
|
||||||
|
int32 valueType = 3;
|
||||||
|
string strValue = 4;
|
||||||
|
int64 longValue = 5;
|
||||||
|
double doubleValue = 6;
|
||||||
|
bool boolValue = 7;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user