committed by
GitHub
14 changed files with 1295 additions and 90 deletions
@ -0,0 +1,401 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.transport.mqtt.session; |
||||
|
|
||||
|
import com.google.common.util.concurrent.FutureCallback; |
||||
|
import com.google.common.util.concurrent.Futures; |
||||
|
import com.google.common.util.concurrent.ListenableFuture; |
||||
|
import com.google.common.util.concurrent.SettableFuture; |
||||
|
import com.google.gson.JsonElement; |
||||
|
import com.google.gson.JsonNull; |
||||
|
import com.google.protobuf.InvalidProtocolBufferException; |
||||
|
import io.netty.buffer.ByteBuf; |
||||
|
import io.netty.channel.ChannelFuture; |
||||
|
import io.netty.channel.ChannelHandlerContext; |
||||
|
import io.netty.handler.codec.mqtt.MqttMessage; |
||||
|
import io.netty.handler.codec.mqtt.MqttPublishMessage; |
||||
|
import io.netty.handler.codec.mqtt.MqttQoS; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.util.ConcurrentReferenceHashMap; |
||||
|
import org.thingsboard.server.common.data.StringUtils; |
||||
|
import org.thingsboard.server.common.transport.TransportService; |
||||
|
import org.thingsboard.server.common.transport.TransportServiceCallback; |
||||
|
import org.thingsboard.server.common.transport.adaptor.AdaptorException; |
||||
|
import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; |
||||
|
import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; |
||||
|
import org.thingsboard.server.gen.transport.TransportApiProtos; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; |
||||
|
import org.thingsboard.server.transport.mqtt.MqttTransportContext; |
||||
|
import org.thingsboard.server.transport.mqtt.MqttTransportHandler; |
||||
|
import org.thingsboard.server.transport.mqtt.adaptors.ProtoMqttAdaptor; |
||||
|
import org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugTopic; |
||||
|
|
||||
|
import javax.annotation.Nullable; |
||||
|
import java.util.List; |
||||
|
import java.util.UUID; |
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
import java.util.concurrent.ConcurrentMap; |
||||
|
import java.util.concurrent.locks.Lock; |
||||
|
import java.util.concurrent.locks.ReentrantLock; |
||||
|
|
||||
|
import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED; |
||||
|
import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_OPEN; |
||||
|
import static org.thingsboard.server.common.transport.service.DefaultTransportService.SUBSCRIBE_TO_ATTRIBUTE_UPDATES_ASYNC_MSG; |
||||
|
import static org.thingsboard.server.common.transport.service.DefaultTransportService.SUBSCRIBE_TO_RPC_ASYNC_MSG; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugTopicUtil.parseTopic; |
||||
|
|
||||
|
/** |
||||
|
* Created by nickAS21 on 12.12.22 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public class SparkplugNodeSessionHandler { |
||||
|
|
||||
|
private static final String DEFAULT_DEVICE_TYPE = "default"; |
||||
|
private static final String CAN_T_PARSE_VALUE = "Can't parse value: "; |
||||
|
private static final String DEVICE_PROPERTY = "device"; |
||||
|
|
||||
|
private final MqttTransportContext context; |
||||
|
private final TransportService transportService; |
||||
|
private final TransportDeviceInfo nodeSparkplugInfo; |
||||
|
private final UUID sessionId; |
||||
|
private final ConcurrentMap<String, Lock> deviceCreationLockMap; |
||||
|
private final ConcurrentMap<String, SparkplugSessionCtx> devices = new ConcurrentHashMap<>(); |
||||
|
private final ConcurrentMap<String, ListenableFuture<SparkplugSessionCtx>> deviceFutures = new ConcurrentHashMap<>(); |
||||
|
private final ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap; |
||||
|
private final ChannelHandlerContext channel; |
||||
|
private final DeviceSessionCtx deviceSessionCtx; |
||||
|
private String nodeTopic; |
||||
|
|
||||
|
public SparkplugNodeSessionHandler(DeviceSessionCtx deviceSessionCtx, UUID sessionId, String nodeTopic) { |
||||
|
this.context = deviceSessionCtx.getContext(); |
||||
|
this.transportService = context.getTransportService(); |
||||
|
this.deviceSessionCtx = deviceSessionCtx; |
||||
|
this.nodeSparkplugInfo = deviceSessionCtx.getDeviceInfo(); |
||||
|
this.sessionId = sessionId; |
||||
|
this.deviceCreationLockMap = createWeakMap(); |
||||
|
this.mqttQoSMap = deviceSessionCtx.getMqttQoSMap(); |
||||
|
this.channel = deviceSessionCtx.getChannel(); |
||||
|
this.nodeTopic = nodeTopic; |
||||
|
} |
||||
|
|
||||
|
ConcurrentReferenceHashMap<String, Lock> createWeakMap() { |
||||
|
return new ConcurrentReferenceHashMap<>(16, ConcurrentReferenceHashMap.ReferenceType.WEAK); |
||||
|
} |
||||
|
|
||||
|
public String getNodeId() { |
||||
|
return context.getNodeId(); |
||||
|
} |
||||
|
|
||||
|
public UUID getSessionId() { |
||||
|
return sessionId; |
||||
|
} |
||||
|
|
||||
|
public String getNodeTopic() { |
||||
|
return nodeTopic; |
||||
|
} |
||||
|
|
||||
|
public int nextMsgId() { |
||||
|
return deviceSessionCtx.nextMsgId(); |
||||
|
} |
||||
|
|
||||
|
public void deregisterSession(String deviceName) { |
||||
|
SparkplugSessionCtx deviceSessionCtx = devices.remove(deviceName); |
||||
|
if (deviceSessionCtx != null) { |
||||
|
deregisterSession(deviceName, deviceSessionCtx); |
||||
|
} else { |
||||
|
log.debug("[{}] Device [{}] was already removed from the gateway session", sessionId, deviceName); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void deregisterSession(String deviceName, SparkplugSessionCtx deviceSessionCtx) { |
||||
|
transportService.deregisterSession(deviceSessionCtx.getSessionInfo()); |
||||
|
transportService.process(deviceSessionCtx.getSessionInfo(), SESSION_EVENT_MSG_CLOSED, null); |
||||
|
log.debug("[{}] Removed device [{}] from the gateway session", sessionId, deviceName); |
||||
|
} |
||||
|
|
||||
|
public void onDeviceDeleted(String deviceName) { |
||||
|
deregisterSession(deviceName); |
||||
|
} |
||||
|
|
||||
|
private int getMsgId(MqttPublishMessage mqttMsg) { |
||||
|
return mqttMsg.variableHeader().packetId(); |
||||
|
} |
||||
|
|
||||
|
public void onDeviceConnectProto(MqttPublishMessage mqttMsg) throws AdaptorException { |
||||
|
try { |
||||
|
String deviceName = parseTopic(mqttMsg.variableHeader().topicName()).getDeviceId(); |
||||
|
String deviceType = StringUtils.isEmpty(nodeSparkplugInfo.getDeviceType()) ? DEFAULT_DEVICE_TYPE : nodeSparkplugInfo.getDeviceType(); |
||||
|
processOnConnect(mqttMsg, deviceName, deviceType); |
||||
|
} catch (Exception e) { |
||||
|
throw new AdaptorException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void onPublishMsg(ChannelHandlerContext ctx, String topicName, int msgId, MqttPublishMessage mqttMsg) throws Exception { |
||||
|
SparkplugTopic sparkplugTopic = parseTopic(topicName); |
||||
|
log.error("SparkplugPublishMsg [{}] [{}]", sparkplugTopic.isNode() ? "node" : "device: " + sparkplugTopic.getDeviceId(), sparkplugTopic.getType()); |
||||
|
if (sparkplugTopic.isNode()) { |
||||
|
// A node topic
|
||||
|
switch (sparkplugTopic.getType()) { |
||||
|
case STATE: |
||||
|
// TODO
|
||||
|
break; |
||||
|
case NBIRTH: |
||||
|
// TODO
|
||||
|
break; |
||||
|
case NCMD: |
||||
|
// TODO
|
||||
|
break; |
||||
|
case NDATA: |
||||
|
// TODO
|
||||
|
break; |
||||
|
case NDEATH: |
||||
|
onNodeDisconnectProto(mqttMsg); |
||||
|
break; |
||||
|
case NRECORD: |
||||
|
// TODO
|
||||
|
break; |
||||
|
default: |
||||
|
} |
||||
|
} else { |
||||
|
// A device topic
|
||||
|
switch (sparkplugTopic.getType()) { |
||||
|
case STATE: |
||||
|
// TODO
|
||||
|
break; |
||||
|
case DBIRTH: |
||||
|
onDeviceConnectProto(mqttMsg); |
||||
|
break; |
||||
|
case DCMD: |
||||
|
// TODO
|
||||
|
break; |
||||
|
case DDATA: |
||||
|
// TODO
|
||||
|
break; |
||||
|
case DDEATH: |
||||
|
onDeviceDisconnectProto(mqttMsg); |
||||
|
break; |
||||
|
case DRECORD: |
||||
|
// TODO
|
||||
|
break; |
||||
|
default: |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void onNodeDisconnectProto(MqttPublishMessage mqttMsg) throws AdaptorException { |
||||
|
try { |
||||
|
TransportApiProtos.DisconnectMsg connectProto = TransportApiProtos.DisconnectMsg.parseFrom(getBytes(mqttMsg.payload())); |
||||
|
String deviceName = checkDeviceName(connectProto.getDeviceName()); |
||||
|
processOnDisconnect(mqttMsg, deviceName); |
||||
|
} catch (RuntimeException | InvalidProtocolBufferException e) { |
||||
|
throw new AdaptorException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void onDeviceDisconnectProto(MqttPublishMessage mqttMsg) throws AdaptorException { |
||||
|
try { |
||||
|
TransportApiProtos.DisconnectMsg connectProto = TransportApiProtos.DisconnectMsg.parseFrom(getBytes(mqttMsg.payload())); |
||||
|
String deviceName = checkDeviceName(connectProto.getDeviceName()); |
||||
|
// TODO disconnect device without disconnect Node
|
||||
|
} catch (RuntimeException | InvalidProtocolBufferException e) { |
||||
|
throw new AdaptorException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void processOnDisconnect(MqttPublishMessage msg, String deviceName) { |
||||
|
deregisterSession(deviceName); |
||||
|
ack(msg); |
||||
|
} |
||||
|
|
||||
|
public void handleSparkplugSubscribeMsg(List<Integer> grantedQoSList, SparkplugTopic sparkplugTopic, MqttQoS reqQoS) { |
||||
|
String topicName = sparkplugTopic.toString(); |
||||
|
log.error("SparkplugSubscribeMsg [{}] [{}]", sparkplugTopic.isNode() ? "node" : "device: " + sparkplugTopic.getDeviceId(), sparkplugTopic.getType()); |
||||
|
|
||||
|
if (sparkplugTopic.isNode()) { |
||||
|
// A node topic
|
||||
|
switch (sparkplugTopic.getType()) { |
||||
|
case STATE: |
||||
|
// TODO
|
||||
|
break; |
||||
|
case NBIRTH: |
||||
|
// TODO
|
||||
|
break; |
||||
|
case NCMD: |
||||
|
// TODO
|
||||
|
break; |
||||
|
case NDATA: |
||||
|
// TODO
|
||||
|
break; |
||||
|
case NDEATH: |
||||
|
// TODO
|
||||
|
break; |
||||
|
case NRECORD: |
||||
|
// TODO
|
||||
|
break; |
||||
|
default: |
||||
|
} |
||||
|
} else { |
||||
|
// A device topic
|
||||
|
switch (sparkplugTopic.getType()) { |
||||
|
case STATE: |
||||
|
// TODO
|
||||
|
break; |
||||
|
case DBIRTH: |
||||
|
// TODO
|
||||
|
break; |
||||
|
case DCMD: |
||||
|
// TODO
|
||||
|
break; |
||||
|
case DDATA: |
||||
|
// TODO
|
||||
|
break; |
||||
|
case DDEATH: |
||||
|
// TODO
|
||||
|
break; |
||||
|
case DRECORD: |
||||
|
// TODO
|
||||
|
break; |
||||
|
default: |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private byte[] getBytes(ByteBuf payload) { |
||||
|
return ProtoMqttAdaptor.toBytes(payload); |
||||
|
} |
||||
|
|
||||
|
private void ack(MqttPublishMessage msg) { |
||||
|
int msgId = getMsgId(msg); |
||||
|
if (msgId > 0) { |
||||
|
writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(msgId)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ChannelFuture writeAndFlush(MqttMessage mqttMessage) { |
||||
|
return channel.writeAndFlush(mqttMessage); |
||||
|
} |
||||
|
|
||||
|
private String checkDeviceName(String deviceName) { |
||||
|
if (StringUtils.isEmpty(deviceName)) { |
||||
|
throw new RuntimeException("Device name is empty!"); |
||||
|
} else { |
||||
|
return deviceName; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private String getDeviceName(JsonElement json) { |
||||
|
return json.getAsJsonObject().get(DEVICE_PROPERTY).getAsString(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private String getDeviceType(JsonElement json) { |
||||
|
JsonElement type = json.getAsJsonObject().get("type"); |
||||
|
return type == null || type instanceof JsonNull ? DEFAULT_DEVICE_TYPE : type.getAsString(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private void processOnConnect(MqttPublishMessage msg, String deviceName, String deviceType) { |
||||
|
log.trace("[{}] onDeviceConnect: {}", sessionId, deviceName); |
||||
|
Futures.addCallback(onDeviceConnect(deviceName, deviceType), new FutureCallback<>() { |
||||
|
@Override |
||||
|
public void onSuccess(@Nullable SparkplugSessionCtx result) { |
||||
|
ack(msg); |
||||
|
log.trace("[{}] onDeviceConnectOk: {}", sessionId, deviceName); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onFailure(Throwable t) { |
||||
|
log.warn("[{}] Failed to process device connect command: {}", sessionId, deviceName, t); |
||||
|
|
||||
|
} |
||||
|
}, context.getExecutor()); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private ListenableFuture<SparkplugSessionCtx> onDeviceConnect(String deviceName, String deviceType) { |
||||
|
SparkplugSessionCtx result = devices.get(deviceName); |
||||
|
if (result == null) { |
||||
|
Lock deviceCreationLock = deviceCreationLockMap.computeIfAbsent(deviceName, s -> new ReentrantLock()); |
||||
|
deviceCreationLock.lock(); |
||||
|
try { |
||||
|
result = devices.get(deviceName); |
||||
|
if (result == null) { |
||||
|
return getDeviceCreationFuture(deviceName, deviceType); |
||||
|
} else { |
||||
|
return Futures.immediateFuture(result); |
||||
|
} |
||||
|
} finally { |
||||
|
deviceCreationLock.unlock(); |
||||
|
} |
||||
|
} else { |
||||
|
return Futures.immediateFuture(result); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private ListenableFuture<SparkplugSessionCtx> getDeviceCreationFuture(String deviceName, String deviceType) { |
||||
|
final SettableFuture<SparkplugSessionCtx> futureToSet = SettableFuture.create(); |
||||
|
ListenableFuture<SparkplugSessionCtx> future = deviceFutures.putIfAbsent(deviceName, futureToSet); |
||||
|
if (future != null) { |
||||
|
return future; |
||||
|
} |
||||
|
try { |
||||
|
transportService.process(TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg.newBuilder() |
||||
|
.setDeviceName(deviceName) |
||||
|
.setDeviceType(deviceType) |
||||
|
.setGatewayIdMSB(nodeSparkplugInfo.getDeviceId().getId().getMostSignificantBits()) |
||||
|
.setGatewayIdLSB(nodeSparkplugInfo.getDeviceId().getId().getLeastSignificantBits()) |
||||
|
.setSparkplug(true) |
||||
|
.build(), |
||||
|
new TransportServiceCallback<>() { |
||||
|
@Override |
||||
|
public void onSuccess(GetOrCreateDeviceFromGatewayResponse msg) { |
||||
|
if (msg.getDeviceInfo() == null) { |
||||
|
System.out.println("DeviceInfo == null"); |
||||
|
} |
||||
|
SparkplugSessionCtx nodeSparkplugSessionCtx = new SparkplugSessionCtx(SparkplugNodeSessionHandler.this, msg.getDeviceInfo(), msg.getDeviceProfile(), mqttQoSMap, transportService); |
||||
|
if (devices.putIfAbsent(deviceName, nodeSparkplugSessionCtx) == null) { |
||||
|
log.trace("[{}] First got or created device [{}], type [{}] for the gateway session", sessionId, deviceName, deviceType); |
||||
|
SessionInfoProto deviceSessionInfo = nodeSparkplugSessionCtx.getSessionInfo(); |
||||
|
transportService.registerAsyncSession(deviceSessionInfo, nodeSparkplugSessionCtx); |
||||
|
transportService.process(TransportProtos.TransportToDeviceActorMsg.newBuilder() |
||||
|
.setSessionInfo(deviceSessionInfo) |
||||
|
.setSessionEvent(SESSION_EVENT_MSG_OPEN) |
||||
|
.setSubscribeToAttributes(SUBSCRIBE_TO_ATTRIBUTE_UPDATES_ASYNC_MSG) |
||||
|
.setSubscribeToRPC(SUBSCRIBE_TO_RPC_ASYNC_MSG) |
||||
|
.build(), null); |
||||
|
} |
||||
|
futureToSet.set(devices.get(deviceName)); |
||||
|
deviceFutures.remove(deviceName); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onError(Throwable e) { |
||||
|
log.warn("[{}] Failed to process device connect command: {}", sessionId, deviceName, e); |
||||
|
futureToSet.setException(e); |
||||
|
deviceFutures.remove(deviceName); |
||||
|
} |
||||
|
}); |
||||
|
return futureToSet; |
||||
|
} catch (Throwable e) { |
||||
|
deviceFutures.remove(deviceName); |
||||
|
throw e; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,146 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.transport.mqtt.session; |
||||
|
|
||||
|
import io.netty.handler.codec.mqtt.MqttMessage; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.thingsboard.server.common.data.DeviceProfile; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.transport.SessionMsgListener; |
||||
|
import org.thingsboard.server.common.transport.TransportService; |
||||
|
import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; |
||||
|
|
||||
|
import java.util.UUID; |
||||
|
import java.util.concurrent.ConcurrentMap; |
||||
|
|
||||
|
/** |
||||
|
* Created by nickAS21 on 08.12.22 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public class SparkplugSessionCtx extends MqttDeviceAwareSessionContext implements SessionMsgListener { |
||||
|
|
||||
|
private final SparkplugNodeSessionHandler parent; |
||||
|
private final TransportService transportService; |
||||
|
|
||||
|
public SparkplugSessionCtx(SparkplugNodeSessionHandler parent, TransportDeviceInfo deviceInfo, |
||||
|
DeviceProfile deviceProfile, ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap, |
||||
|
TransportService transportService) { |
||||
|
super(UUID.randomUUID(), mqttQoSMap); |
||||
|
this.parent = parent; |
||||
|
setSessionInfo(SessionInfoProto.newBuilder() |
||||
|
.setNodeId(parent.getNodeId()) |
||||
|
.setSessionIdMSB(sessionId.getMostSignificantBits()) |
||||
|
.setSessionIdLSB(sessionId.getLeastSignificantBits()) |
||||
|
.setDeviceIdMSB(deviceInfo.getDeviceId().getId().getMostSignificantBits()) |
||||
|
.setDeviceIdLSB(deviceInfo.getDeviceId().getId().getLeastSignificantBits()) |
||||
|
.setTenantIdMSB(deviceInfo.getTenantId().getId().getMostSignificantBits()) |
||||
|
.setTenantIdLSB(deviceInfo.getTenantId().getId().getLeastSignificantBits()) |
||||
|
.setCustomerIdMSB(deviceInfo.getCustomerId().getId().getMostSignificantBits()) |
||||
|
.setCustomerIdLSB(deviceInfo.getCustomerId().getId().getLeastSignificantBits()) |
||||
|
.setDeviceName(deviceInfo.getDeviceName()) |
||||
|
.setDeviceType(deviceInfo.getDeviceType()) |
||||
|
.setGwSessionIdMSB(parent.getSessionId().getMostSignificantBits()) |
||||
|
.setGwSessionIdLSB(parent.getSessionId().getLeastSignificantBits()) |
||||
|
.setDeviceProfileIdMSB(deviceInfo.getDeviceProfileId().getId().getMostSignificantBits()) |
||||
|
.setDeviceProfileIdLSB(deviceInfo.getDeviceProfileId().getId().getLeastSignificantBits()) |
||||
|
.build()); |
||||
|
setDeviceInfo(deviceInfo); |
||||
|
setConnected(true); |
||||
|
setDeviceProfile(deviceProfile); |
||||
|
this.transportService = transportService; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public UUID getSessionId() { |
||||
|
return sessionId; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int nextMsgId() { |
||||
|
return parent.nextMsgId(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg response) { |
||||
|
// try {
|
||||
|
// parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), response).ifPresent(parent::writeAndFlush);
|
||||
|
// } catch (Exception e) {
|
||||
|
// log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
|
||||
|
// }
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onAttributeUpdate(UUID sessionId, TransportProtos.AttributeUpdateNotificationMsg notification) { |
||||
|
// log.trace("[{}] Received attributes update notification to device", sessionId);
|
||||
|
// try {
|
||||
|
// parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), notification).ifPresent(parent::writeAndFlush);
|
||||
|
// } catch (Exception e) {
|
||||
|
// log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
|
||||
|
// }
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onToDeviceRpcRequest(UUID sessionId, TransportProtos.ToDeviceRpcRequestMsg request) { |
||||
|
// log.trace("[{}] Received RPC command to device", sessionId);
|
||||
|
// try {
|
||||
|
// parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), request).ifPresent(
|
||||
|
// payload -> {
|
||||
|
// ChannelFuture channelFuture = parent.writeAndFlush(payload);
|
||||
|
// if (request.getPersisted()) {
|
||||
|
// channelFuture.addListener(result -> {
|
||||
|
// if (result.cause() == null) {
|
||||
|
// if (!isAckExpected(payload)) {
|
||||
|
// transportService.process(getSessionInfo(), request, RpcStatus.DELIVERED, TransportServiceCallback.EMPTY);
|
||||
|
// } else if (request.getPersisted()) {
|
||||
|
// transportService.process(getSessionInfo(), request, RpcStatus.SENT, TransportServiceCallback.EMPTY);
|
||||
|
//
|
||||
|
// }
|
||||
|
// }
|
||||
|
// });
|
||||
|
// }
|
||||
|
// }
|
||||
|
// );
|
||||
|
// } catch (Exception e) {
|
||||
|
// transportService.process(getSessionInfo(),
|
||||
|
// TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
|
||||
|
// .setRequestId(request.getRequestId()).setError("Failed to convert device RPC command to MQTT msg").build(), TransportServiceCallback.EMPTY);
|
||||
|
// log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
|
||||
|
// }
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onRemoteSessionCloseCommand(UUID sessionId, TransportProtos.SessionCloseNotificationProto sessionCloseNotification) { |
||||
|
log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage()); |
||||
|
parent.deregisterSession(getDeviceInfo().getDeviceName()); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse) { |
||||
|
// This feature is not supported in the TB IoT Gateway yet.
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onDeviceDeleted(DeviceId deviceId) { |
||||
|
parent.onDeviceDeleted(this.getSessionInfo().getDeviceName()); |
||||
|
} |
||||
|
|
||||
|
private boolean isAckExpected(MqttMessage message) { |
||||
|
return message.fixedHeader().qosLevel().value() > 0; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,110 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.transport.mqtt.util.sparkplug; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
|
||||
|
/** |
||||
|
* An enumeration of Sparkplug MQTT message types. The type provides an indication as to what the MQTT Payload of |
||||
|
* message will contain. |
||||
|
*/ |
||||
|
public enum SparkplugMessageType { |
||||
|
|
||||
|
/** |
||||
|
* Birth certificate for MQTT Edge of Network (EoN) Nodes. |
||||
|
*/ |
||||
|
NBIRTH, |
||||
|
|
||||
|
/** |
||||
|
* Death certificate for MQTT Edge of Network (EoN) Nodes. |
||||
|
*/ |
||||
|
NDEATH, |
||||
|
|
||||
|
/** |
||||
|
* Birth certificate for MQTT Devices. |
||||
|
*/ |
||||
|
DBIRTH, |
||||
|
|
||||
|
/** |
||||
|
* Death certificate for MQTT Devices. |
||||
|
*/ |
||||
|
DDEATH, |
||||
|
|
||||
|
/** |
||||
|
* Edge of Network (EoN) Node data message. |
||||
|
*/ |
||||
|
NDATA, |
||||
|
|
||||
|
/** |
||||
|
* Device data message. |
||||
|
*/ |
||||
|
DDATA, |
||||
|
|
||||
|
/** |
||||
|
* Edge of Network (EoN) Node command message. |
||||
|
*/ |
||||
|
NCMD, |
||||
|
|
||||
|
/** |
||||
|
* Device command message. |
||||
|
*/ |
||||
|
DCMD, |
||||
|
|
||||
|
/** |
||||
|
* Critical application state message. |
||||
|
*/ |
||||
|
STATE, |
||||
|
|
||||
|
/** |
||||
|
* Device record message. |
||||
|
*/ |
||||
|
DRECORD, |
||||
|
|
||||
|
/** |
||||
|
* Edge of Network (EoN) Node record message. |
||||
|
*/ |
||||
|
NRECORD; |
||||
|
|
||||
|
public static SparkplugMessageType parseMessageType(String type) throws ThingsboardException { |
||||
|
for (SparkplugMessageType messageType : SparkplugMessageType.values()) { |
||||
|
if (messageType.name().equals(type)) { |
||||
|
return messageType; |
||||
|
} |
||||
|
} |
||||
|
throw new ThingsboardException("Invalid message type: " + type, ThingsboardErrorCode.INVALID_ARGUMENTS); |
||||
|
} |
||||
|
|
||||
|
public boolean isDeath() { |
||||
|
return this.equals(DDEATH) || this.equals(NDEATH); |
||||
|
} |
||||
|
|
||||
|
public boolean isCommand() { |
||||
|
return this.equals(DCMD) || this.equals(NCMD); |
||||
|
} |
||||
|
|
||||
|
public boolean isData() { |
||||
|
return this.equals(DDATA) || this.equals(NDATA); |
||||
|
} |
||||
|
|
||||
|
public boolean isBirth() { |
||||
|
return this.equals(DBIRTH) || this.equals(NBIRTH); |
||||
|
} |
||||
|
|
||||
|
public boolean isRecord() { |
||||
|
return this.equals(DRECORD) || this.equals(NRECORD); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,160 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.transport.mqtt.util.sparkplug; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonInclude; |
||||
|
|
||||
|
/** |
||||
|
* Created by nickAS21 on 12.12.22 |
||||
|
*/ |
||||
|
@JsonInclude(JsonInclude.Include.NON_NULL) |
||||
|
public class SparkplugTopic { |
||||
|
|
||||
|
/** |
||||
|
* The Sparkplug namespace version. |
||||
|
* For the Sparkplug™ A version of the payload definition, the UTF-8 string constant for the namespace element will be: |
||||
|
* “spAv1.0” |
||||
|
* For the Sparkplug™ B version of the specification, the UTF-8 string constant for the namespace element will be: |
||||
|
* “spBv1.0” |
||||
|
*/ |
||||
|
private String namespace; |
||||
|
|
||||
|
/** |
||||
|
* The ID of the logical grouping of Edge of Network (EoN) Nodes and devices. |
||||
|
*/ |
||||
|
private String groupId; |
||||
|
|
||||
|
/** |
||||
|
* The ID of the Edge of Network (EoN) Node. |
||||
|
*/ |
||||
|
private String edgeNodeId; |
||||
|
|
||||
|
/** |
||||
|
* The ID of the device. |
||||
|
*/ |
||||
|
private String deviceId; |
||||
|
|
||||
|
/** |
||||
|
* The message type. |
||||
|
*/ |
||||
|
private SparkplugMessageType type; |
||||
|
|
||||
|
/** |
||||
|
* Constructor (device). |
||||
|
* |
||||
|
* @param namespace the namespace. |
||||
|
* @param groupId the group ID. |
||||
|
* @param edgeNodeId the edge node ID. |
||||
|
* @param deviceId the device ID. |
||||
|
* @param type the message type. |
||||
|
*/ |
||||
|
public SparkplugTopic(String namespace, String groupId, String edgeNodeId, String deviceId, SparkplugMessageType type) { |
||||
|
super(); |
||||
|
this.namespace = namespace; |
||||
|
this.groupId = groupId; |
||||
|
this.edgeNodeId = edgeNodeId; |
||||
|
this.deviceId = deviceId; |
||||
|
this.type = type; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Constructor (node). |
||||
|
* |
||||
|
* @param namespace the namespace. |
||||
|
* @param groupId the group ID. |
||||
|
* @param edgeNodeId the edge node ID. |
||||
|
* @param type the message type. |
||||
|
*/ |
||||
|
public SparkplugTopic(String namespace, String groupId, String edgeNodeId, SparkplugMessageType type) { |
||||
|
super(); |
||||
|
this.namespace = namespace; |
||||
|
this.groupId = groupId; |
||||
|
this.edgeNodeId = edgeNodeId; |
||||
|
this.deviceId = null; |
||||
|
this.type = type; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns the Sparkplug namespace version. |
||||
|
* |
||||
|
* @return the namespace |
||||
|
*/ |
||||
|
public String getNamespace() { |
||||
|
return namespace; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns the ID of the logical grouping of Edge of Network (EoN) Nodes and devices. |
||||
|
* |
||||
|
* @return the group ID |
||||
|
*/ |
||||
|
public String getGroupId() { |
||||
|
return groupId; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns the ID of the Edge of Network (EoN) Node. |
||||
|
* |
||||
|
* @return the edge node ID |
||||
|
*/ |
||||
|
public String getEdgeNodeId() { |
||||
|
return edgeNodeId; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns the ID of the device. |
||||
|
* |
||||
|
* @return the device ID |
||||
|
*/ |
||||
|
public String getDeviceId() { |
||||
|
return deviceId; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns the message type. |
||||
|
* |
||||
|
* @return the message type |
||||
|
*/ |
||||
|
public SparkplugMessageType getType() { |
||||
|
return type; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String toString() { |
||||
|
StringBuilder sb = new StringBuilder(getNamespace()).append("/") |
||||
|
.append(getGroupId()).append("/") |
||||
|
.append(getType()).append("/") |
||||
|
.append(getEdgeNodeId()); |
||||
|
if (getDeviceId() != null) { |
||||
|
sb.append("/").append(getDeviceId()); |
||||
|
} |
||||
|
return sb.toString(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns true if this topic's type matches the passes in type, false otherwise. |
||||
|
* |
||||
|
* @param type the type to check |
||||
|
* @return true if this topic's type matches the passes in type, false otherwise |
||||
|
*/ |
||||
|
public boolean isType(SparkplugMessageType type) { |
||||
|
return this.type != null && this.type.equals(type); |
||||
|
} |
||||
|
|
||||
|
public boolean isNode() { |
||||
|
return this.deviceId == null; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,114 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.transport.mqtt.util.sparkplug; |
||||
|
|
||||
|
import com.fasterxml.jackson.core.JsonProcessingException; |
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
|
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
/** |
||||
|
* Provides utility methods for handling Sparkplug MQTT message topics. |
||||
|
*/ |
||||
|
public class SparkplugTopicUtil { |
||||
|
|
||||
|
private static final Map<String, String[]> SPLIT_TOPIC_CACHE = new HashMap<String, String[]>(); |
||||
|
|
||||
|
public static String[] getSplitTopic(String topic) { |
||||
|
String[] splitTopic = SPLIT_TOPIC_CACHE.get(topic); |
||||
|
if (splitTopic == null) { |
||||
|
splitTopic = topic.split("/"); |
||||
|
SPLIT_TOPIC_CACHE.put(topic, splitTopic); |
||||
|
} |
||||
|
|
||||
|
return splitTopic; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Serializes a {@link SparkplugTopic} instance in to a JSON string. |
||||
|
* |
||||
|
* @param topic a {@link SparkplugTopic} instance |
||||
|
* @return a JSON string |
||||
|
* @throws JsonProcessingException |
||||
|
*/ |
||||
|
public static String sparkplugTopicToString(SparkplugTopic topic) throws JsonProcessingException { |
||||
|
ObjectMapper mapper = new ObjectMapper(); |
||||
|
return mapper.writeValueAsString(topic); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Parses a Sparkplug MQTT message topic string and returns a {@link SparkplugTopic} instance. |
||||
|
* |
||||
|
* @param topic a topic string |
||||
|
* @return a {@link SparkplugTopic} instance |
||||
|
* @throws ThingsboardException if an error occurs while parsing |
||||
|
*/ |
||||
|
public static SparkplugTopic parseTopic(String topic) throws ThingsboardException { |
||||
|
topic = topic.indexOf("#") > 0 ? topic.substring(0, topic.indexOf("#")) : topic; |
||||
|
return parseTopic(SparkplugTopicUtil.getSplitTopic(topic)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Parses a Sparkplug MQTT message topic string and returns a {@link SparkplugTopic} instance. |
||||
|
* |
||||
|
* @param splitTopic a topic split into tokens |
||||
|
* @return a {@link SparkplugTopic} instance |
||||
|
* @throws Exception if an error occurs while parsing |
||||
|
*/ |
||||
|
@SuppressWarnings("incomplete-switch") |
||||
|
public static SparkplugTopic parseTopic(String[] splitTopic) throws ThingsboardException { |
||||
|
SparkplugMessageType type; |
||||
|
String namespace, edgeNodeId, groupId; |
||||
|
int length = splitTopic.length; |
||||
|
|
||||
|
if (length < 4 || length > 5) { |
||||
|
throw new ThingsboardException("Invalid number of topic elements: " + length, ThingsboardErrorCode.INVALID_ARGUMENTS); |
||||
|
} |
||||
|
|
||||
|
namespace = splitTopic[0]; |
||||
|
groupId = splitTopic[1]; |
||||
|
type = SparkplugMessageType.parseMessageType(splitTopic[2]); |
||||
|
edgeNodeId = splitTopic[3]; |
||||
|
|
||||
|
if (length == 4) { |
||||
|
// A node topic
|
||||
|
switch (type) { |
||||
|
case STATE: |
||||
|
case NBIRTH: |
||||
|
case NCMD: |
||||
|
case NDATA: |
||||
|
case NDEATH: |
||||
|
case NRECORD: |
||||
|
return new SparkplugTopic(namespace, groupId, edgeNodeId, type); |
||||
|
} |
||||
|
} else { |
||||
|
// A device topic
|
||||
|
switch (type) { |
||||
|
case STATE: |
||||
|
case DBIRTH: |
||||
|
case DCMD: |
||||
|
case DDATA: |
||||
|
case DDEATH: |
||||
|
case DRECORD: |
||||
|
return new SparkplugTopic(namespace, groupId, edgeNodeId, splitTopic[4], type); |
||||
|
} |
||||
|
} |
||||
|
throw new ThingsboardException("Invalid number of topic elements " + length + " for topic type " + type, ThingsboardErrorCode.INVALID_ARGUMENTS); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,204 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 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. |
||||
|
*/ |
||||
|
syntax = "proto3"; |
||||
|
|
||||
|
import "google/protobuf/any.proto"; |
||||
|
|
||||
|
|
||||
|
option java_package = "org.thingsboard.server.gen.transport.mqtt"; |
||||
|
option java_outer_classname = "SparkplugBProto"; |
||||
|
|
||||
|
message Payload { |
||||
|
/* |
||||
|
// Indexes of Data Types |
||||
|
// Unknown placeholder for future expansion. |
||||
|
Unknown = 0; |
||||
|
// Basic Types |
||||
|
Int8 = 1; |
||||
|
Int16 = 2; |
||||
|
Int32 = 3; |
||||
|
Int64 = 4; |
||||
|
UInt8 = 5; |
||||
|
UInt16 = 6; |
||||
|
UInt32 = 7; |
||||
|
UInt64 = 8; |
||||
|
Float = 9; |
||||
|
Double = 10; |
||||
|
Boolean = 11; |
||||
|
String = 12; |
||||
|
DateTime = 13; |
||||
|
Text = 14; |
||||
|
// Additional Metric Types |
||||
|
UUID = 15; |
||||
|
DataSet = 16; |
||||
|
Bytes = 17; |
||||
|
File = 18; |
||||
|
Template = 19; |
||||
|
|
||||
|
// Additional PropertyValue Types |
||||
|
PropertySet = 20; |
||||
|
PropertySetList = 21; |
||||
|
*/ |
||||
|
|
||||
|
message Template { |
||||
|
|
||||
|
message Parameter { |
||||
|
optional string name = 1; |
||||
|
optional uint32 type = 2; |
||||
|
|
||||
|
oneof value { |
||||
|
uint32 int_value = 3; |
||||
|
uint64 long_value = 4; |
||||
|
float float_value = 5; |
||||
|
double double_value = 6; |
||||
|
bool boolean_value = 7; |
||||
|
string string_value = 8; |
||||
|
ParameterValueExtension extension_value = 9; |
||||
|
} |
||||
|
|
||||
|
message ParameterValueExtension { |
||||
|
google.protobuf.Any extensions = 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
optional string version = 1; // The version of the Template to prevent mismatches |
||||
|
repeated Metric metrics = 2; // Each metric is the name of the metric and the datatype of the member but does not contain a value |
||||
|
repeated Parameter parameters = 3; |
||||
|
optional string template_ref = 4; // Reference to a template if this is extending a Template or an instance - must exist if an instance |
||||
|
optional bool is_definition = 5; |
||||
|
google.protobuf.Any extensions = 6; |
||||
|
} |
||||
|
|
||||
|
message DataSet { |
||||
|
|
||||
|
message DataSetValue { |
||||
|
|
||||
|
oneof value { |
||||
|
uint32 int_value = 1; |
||||
|
uint64 long_value = 2; |
||||
|
float float_value = 3; |
||||
|
double double_value = 4; |
||||
|
bool boolean_value = 5; |
||||
|
string string_value = 6; |
||||
|
DataSetValueExtension extension_value = 7; |
||||
|
} |
||||
|
|
||||
|
message DataSetValueExtension { |
||||
|
google.protobuf.Any extensions = 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
message Row { |
||||
|
repeated DataSetValue elements = 1; |
||||
|
google.protobuf.Any extensions = 2; // For third party extensions |
||||
|
} |
||||
|
|
||||
|
optional uint64 num_of_columns = 1; |
||||
|
repeated string columns = 2; |
||||
|
repeated uint32 types = 3; |
||||
|
repeated Row rows = 4; |
||||
|
google.protobuf.Any extensions = 5; // For third party extensions |
||||
|
} |
||||
|
|
||||
|
message PropertyValue { |
||||
|
|
||||
|
optional uint32 type = 1; |
||||
|
optional bool is_null = 2; |
||||
|
|
||||
|
oneof value { |
||||
|
uint32 int_value = 3; |
||||
|
uint64 long_value = 4; |
||||
|
float float_value = 5; |
||||
|
double double_value = 6; |
||||
|
bool boolean_value = 7; |
||||
|
string string_value = 8; |
||||
|
PropertySet propertyset_value = 9; |
||||
|
PropertySetList propertysets_value = 10; // List of Property Values |
||||
|
PropertyValueExtension extension_value = 11; |
||||
|
} |
||||
|
|
||||
|
message PropertyValueExtension { |
||||
|
google.protobuf.Any extensions = 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
message PropertySet { |
||||
|
repeated string keys = 1; // Names of the properties |
||||
|
repeated PropertyValue values = 2; |
||||
|
google.protobuf.Any extensions = 3; |
||||
|
} |
||||
|
|
||||
|
message PropertySetList { |
||||
|
repeated PropertySet propertyset = 1; |
||||
|
google.protobuf.Any extensions = 2; |
||||
|
} |
||||
|
|
||||
|
message MetaData { |
||||
|
// Bytes specific metadata |
||||
|
optional bool is_multi_part = 1; |
||||
|
|
||||
|
// General metadata |
||||
|
optional string content_type = 2; // Content/Media type |
||||
|
optional uint64 size = 3; // File size, String size, Multi-part size, etc |
||||
|
optional uint64 seq = 4; // Sequence number for multi-part messages |
||||
|
|
||||
|
// File metadata |
||||
|
optional string file_name = 5; // File name |
||||
|
optional string file_type = 6; // File type (i.e. xml, json, txt, cpp, etc) |
||||
|
optional string md5 = 7; // md5 of data |
||||
|
|
||||
|
// Catchalls and future expansion |
||||
|
optional string description = 8; // Could be anything such as json or xml of custom properties |
||||
|
google.protobuf.Any extensions = 9; |
||||
|
} |
||||
|
|
||||
|
message Metric { |
||||
|
|
||||
|
optional string name = 1; // Metric name - should only be included on birth |
||||
|
optional uint64 alias = 2; // Metric alias - tied to name on birth and included in all later DATA messages |
||||
|
optional uint64 timestamp = 3; // Timestamp associated with data acquisition time |
||||
|
optional uint32 datatype = 4; // DataType of the metric/tag value |
||||
|
optional bool is_historical = 5; // If this is historical data and should not update real time tag |
||||
|
optional bool is_transient = 6; // Tells consuming clients such as MQTT Engine to not store this as a tag |
||||
|
optional bool is_null = 7; // If this is null - explicitly say so rather than using -1, false, etc for some datatypes. |
||||
|
optional MetaData metadata = 8; // Metadata for the payload |
||||
|
optional PropertySet properties = 9; |
||||
|
|
||||
|
oneof value { |
||||
|
uint32 int_value = 10; |
||||
|
uint64 long_value = 11; |
||||
|
float float_value = 12; |
||||
|
double double_value = 13; |
||||
|
bool boolean_value = 14; |
||||
|
string string_value = 15; |
||||
|
bytes bytes_value = 16; // Bytes, File |
||||
|
DataSet dataset_value = 17; |
||||
|
Template template_value = 18; |
||||
|
MetricValueExtension extension_value = 19; |
||||
|
} |
||||
|
|
||||
|
message MetricValueExtension { |
||||
|
google.protobuf.Any extensions = 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
optional uint64 timestamp = 1; // Timestamp at message sending time |
||||
|
repeated Metric metrics = 2; // Repeated forever - no limit in Google Protobufs |
||||
|
optional uint64 seq = 3; // Sequence number |
||||
|
optional string uuid = 4; // UUID to track message type in terms of schema definitions |
||||
|
optional bytes body = 5; // To optionally bypass the whole definition above |
||||
|
google.protobuf.Any extensions = 6; |
||||
|
} |
||||
Loading…
Reference in new issue