committed by
GitHub
25 changed files with 630 additions and 164 deletions
@ -0,0 +1,43 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 19.01.17. |
||||
|
*/ |
||||
|
public class MqttTopics { |
||||
|
|
||||
|
public static final String BASE_DEVICE_API_TOPIC = "v1/devices/me"; |
||||
|
public static final String DEVICE_RPC_RESPONSE_TOPIC = BASE_DEVICE_API_TOPIC + "/rpc/response/"; |
||||
|
public static final String DEVICE_RPC_RESPONSE_SUB_TOPIC = DEVICE_RPC_RESPONSE_TOPIC + "+"; |
||||
|
public static final String DEVICE_RPC_REQUESTS_TOPIC = BASE_DEVICE_API_TOPIC + "/rpc/request/"; |
||||
|
public static final String DEVICE_RPC_REQUESTS_SUB_TOPIC = DEVICE_RPC_REQUESTS_TOPIC + "+"; |
||||
|
public static final String DEVICE_ATTRIBUTES_RESPONSE_TOPIC_PREFIX = BASE_DEVICE_API_TOPIC + "/attributes/response/"; |
||||
|
public static final String DEVICE_ATTRIBUTES_RESPONSES_TOPIC = DEVICE_ATTRIBUTES_RESPONSE_TOPIC_PREFIX + "+"; |
||||
|
public static final String DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX = BASE_DEVICE_API_TOPIC + "/attributes/request/"; |
||||
|
public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + "/telemetry"; |
||||
|
public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + "/attributes"; |
||||
|
|
||||
|
public static final String BASE_GATEWAY_API_TOPIC = "v1/gateway"; |
||||
|
public static final String GATEWAY_CONNECT_TOPIC = "v1/gateway/connect"; |
||||
|
public static final String GATEWAY_DISCONNECT_TOPIC = "v1/gateway/disconnect"; |
||||
|
public static final String GATEWAY_ATTRIBUTES_TOPIC = "v1/gateway/attributes"; |
||||
|
public static final String GATEWAY_TELEMETRY_TOPIC = "v1/gateway/telemetry"; |
||||
|
|
||||
|
|
||||
|
private MqttTopics() { |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.adaptors; |
||||
|
|
||||
|
import io.netty.handler.codec.mqtt.MqttMessage; |
||||
|
import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg; |
||||
|
import org.thingsboard.server.common.msg.session.MsgType; |
||||
|
import org.thingsboard.server.common.msg.session.SessionActorToAdaptorMsg; |
||||
|
import org.thingsboard.server.common.transport.adaptor.AdaptorException; |
||||
|
import org.thingsboard.server.transport.mqtt.session.GatewaySessionCtx; |
||||
|
|
||||
|
import java.util.Optional; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 19.01.17. |
||||
|
*/ |
||||
|
public interface MqttGatewayAdaptor { |
||||
|
|
||||
|
AdaptorToSessionActorMsg convertToActorMsg(GatewaySessionCtx ctx, MsgType type, MqttMessage inbound) throws AdaptorException; |
||||
|
|
||||
|
Optional<MqttMessage> convertToAdaptorMsg(GatewaySessionCtx ctx, SessionActorToAdaptorMsg msg) throws AdaptorException; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,95 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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 org.thingsboard.server.common.data.Device; |
||||
|
import org.thingsboard.server.common.data.id.SessionId; |
||||
|
import org.thingsboard.server.common.msg.core.ResponseMsg; |
||||
|
import org.thingsboard.server.common.msg.session.*; |
||||
|
import org.thingsboard.server.common.msg.session.ex.SessionException; |
||||
|
import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; |
||||
|
import org.thingsboard.server.transport.mqtt.MqttTransportHandler; |
||||
|
|
||||
|
import java.util.Optional; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 19.01.17. |
||||
|
*/ |
||||
|
public class GatewayDeviceSessionCtx extends DeviceAwareSessionContext { |
||||
|
|
||||
|
private GatewaySessionCtx parent; |
||||
|
private final MqttSessionId sessionId; |
||||
|
private volatile boolean closed; |
||||
|
|
||||
|
public GatewayDeviceSessionCtx(GatewaySessionCtx parent, Device device) { |
||||
|
super(parent.getProcessor(), parent.getAuthService(), device); |
||||
|
this.parent = parent; |
||||
|
this.sessionId = new MqttSessionId(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public SessionId getSessionId() { |
||||
|
return sessionId; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public SessionType getSessionType() { |
||||
|
return SessionType.ASYNC; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onMsg(SessionActorToAdaptorMsg sessionMsg) throws SessionException { |
||||
|
Optional<MqttMessage> message = getToDeviceMsg(sessionMsg); |
||||
|
message.ifPresent(parent::writeAndFlush); |
||||
|
} |
||||
|
|
||||
|
private Optional<MqttMessage> getToDeviceMsg(SessionActorToAdaptorMsg sessionMsg) { |
||||
|
ToDeviceMsg msg = sessionMsg.getMsg(); |
||||
|
switch (msg.getMsgType()) { |
||||
|
case STATUS_CODE_RESPONSE: |
||||
|
ResponseMsg<?> responseMsg = (ResponseMsg) msg; |
||||
|
if (responseMsg.isSuccess()) { |
||||
|
MsgType requestMsgType = responseMsg.getRequestMsgType(); |
||||
|
Integer requestId = responseMsg.getRequestId(); |
||||
|
if (requestMsgType == MsgType.POST_ATTRIBUTES_REQUEST || requestMsgType == MsgType.POST_TELEMETRY_REQUEST) { |
||||
|
return Optional.of(MqttTransportHandler.createMqttPubAckMsg(requestId)); |
||||
|
} |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
return Optional.empty(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onMsg(SessionCtrlMsg msg) throws SessionException { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean isClosed() { |
||||
|
return closed; |
||||
|
} |
||||
|
|
||||
|
public void setClosed(boolean closed) { |
||||
|
this.closed = closed; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public long getTimeout() { |
||||
|
return 0; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,191 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.gson.*; |
||||
|
import io.netty.buffer.ByteBufAllocator; |
||||
|
import io.netty.buffer.UnpooledByteBufAllocator; |
||||
|
import io.netty.channel.ChannelHandlerContext; |
||||
|
import io.netty.handler.codec.mqtt.MqttMessage; |
||||
|
import io.netty.handler.codec.mqtt.MqttPublishMessage; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.util.StringUtils; |
||||
|
import org.thingsboard.server.common.data.Device; |
||||
|
import org.thingsboard.server.common.data.id.SessionId; |
||||
|
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; |
||||
|
import org.thingsboard.server.common.msg.core.BasicTelemetryUploadRequest; |
||||
|
import org.thingsboard.server.common.msg.core.BasicUpdateAttributesRequest; |
||||
|
import org.thingsboard.server.common.msg.core.TelemetryUploadRequest; |
||||
|
import org.thingsboard.server.common.msg.session.BasicAdaptorToSessionActorMsg; |
||||
|
import org.thingsboard.server.common.msg.session.BasicToDeviceActorSessionMsg; |
||||
|
import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; |
||||
|
import org.thingsboard.server.common.transport.SessionMsgProcessor; |
||||
|
import org.thingsboard.server.common.transport.adaptor.AdaptorException; |
||||
|
import org.thingsboard.server.common.transport.adaptor.JsonConverter; |
||||
|
import org.thingsboard.server.common.transport.auth.DeviceAuthService; |
||||
|
import org.thingsboard.server.dao.device.DeviceService; |
||||
|
import org.thingsboard.server.transport.mqtt.MqttTransportHandler; |
||||
|
import org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor; |
||||
|
|
||||
|
import java.nio.charset.Charset; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
import java.util.Optional; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
import static org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor.validateJsonPayload; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 19.01.17. |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public class GatewaySessionCtx { |
||||
|
|
||||
|
private static final Gson GSON = new Gson(); |
||||
|
private static final Charset UTF8 = Charset.forName("UTF-8"); |
||||
|
private static final ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false); |
||||
|
|
||||
|
private final Device gateway; |
||||
|
private final SessionId gatewaySessionId; |
||||
|
private final SessionMsgProcessor processor; |
||||
|
private final DeviceService deviceService; |
||||
|
private final DeviceAuthService authService; |
||||
|
private final Map<String, GatewayDeviceSessionCtx> devices; |
||||
|
private ChannelHandlerContext channel; |
||||
|
|
||||
|
public GatewaySessionCtx(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, DeviceSessionCtx gatewaySessionCtx) { |
||||
|
this.processor = processor; |
||||
|
this.deviceService = deviceService; |
||||
|
this.authService = authService; |
||||
|
this.gateway = gatewaySessionCtx.getDevice(); |
||||
|
this.gatewaySessionId = gatewaySessionCtx.getSessionId(); |
||||
|
this.devices = new HashMap<>(); |
||||
|
} |
||||
|
|
||||
|
public void onDeviceConnect(MqttPublishMessage msg) throws AdaptorException { |
||||
|
String deviceName = checkDeviceName(getDeviceName(msg)); |
||||
|
Optional<Device> deviceOpt = deviceService.findDeviceByTenantIdAndName(gateway.getTenantId(), deviceName); |
||||
|
Device device = deviceOpt.orElseGet(() -> { |
||||
|
Device newDevice = new Device(); |
||||
|
newDevice.setTenantId(gateway.getTenantId()); |
||||
|
newDevice.setName(deviceName); |
||||
|
return deviceService.saveDevice(newDevice); |
||||
|
}); |
||||
|
devices.put(deviceName, new GatewayDeviceSessionCtx(this, device)); |
||||
|
ack(msg); |
||||
|
} |
||||
|
|
||||
|
public void onDeviceDisconnect(MqttPublishMessage msg) throws AdaptorException { |
||||
|
String deviceName = checkDeviceName(getDeviceName(msg)); |
||||
|
GatewayDeviceSessionCtx deviceSessionCtx = devices.remove(deviceName); |
||||
|
deviceSessionCtx.setClosed(true); |
||||
|
ack(msg); |
||||
|
} |
||||
|
|
||||
|
public void onGatewayDisconnect() { |
||||
|
devices.forEach((k, v) -> { |
||||
|
processor.process(SessionCloseMsg.onDisconnect(v.getSessionId())); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public void onDeviceTelemetry(MqttPublishMessage mqttMsg) throws AdaptorException { |
||||
|
JsonElement json = validateJsonPayload(gatewaySessionId, mqttMsg.payload()); |
||||
|
int requestId = mqttMsg.variableHeader().messageId(); |
||||
|
if (json.isJsonObject()) { |
||||
|
JsonObject jsonObj = json.getAsJsonObject(); |
||||
|
for (Map.Entry<String, JsonElement> deviceEntry : jsonObj.entrySet()) { |
||||
|
String deviceName = checkDeviceConnected(deviceEntry.getKey()); |
||||
|
if (!deviceEntry.getValue().isJsonArray()) { |
||||
|
throw new JsonSyntaxException("Can't parse value: " + json); |
||||
|
} |
||||
|
BasicTelemetryUploadRequest request = new BasicTelemetryUploadRequest(requestId); |
||||
|
JsonArray deviceData = deviceEntry.getValue().getAsJsonArray(); |
||||
|
for (JsonElement element : deviceData) { |
||||
|
JsonConverter.parseWithTs(request, element.getAsJsonObject()); |
||||
|
} |
||||
|
GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); |
||||
|
processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), |
||||
|
new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request))); |
||||
|
} |
||||
|
} else { |
||||
|
throw new JsonSyntaxException("Can't parse value: " + json); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void onDeviceAttributes(MqttPublishMessage mqttMsg) throws AdaptorException { |
||||
|
JsonElement json = validateJsonPayload(gatewaySessionId, mqttMsg.payload()); |
||||
|
int requestId = mqttMsg.variableHeader().messageId(); |
||||
|
if (json.isJsonObject()) { |
||||
|
JsonObject jsonObj = json.getAsJsonObject(); |
||||
|
for (Map.Entry<String, JsonElement> deviceEntry : jsonObj.entrySet()) { |
||||
|
String deviceName = checkDeviceConnected(deviceEntry.getKey()); |
||||
|
if (!deviceEntry.getValue().isJsonObject()) { |
||||
|
throw new JsonSyntaxException("Can't parse value: " + json); |
||||
|
} |
||||
|
long ts = System.currentTimeMillis(); |
||||
|
BasicUpdateAttributesRequest request = new BasicUpdateAttributesRequest(requestId); |
||||
|
JsonObject deviceData = deviceEntry.getValue().getAsJsonObject(); |
||||
|
request.add(JsonConverter.parseValues(deviceData).stream().map(kv -> new BaseAttributeKvEntry(kv, ts)).collect(Collectors.toList())); |
||||
|
GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); |
||||
|
processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), |
||||
|
new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request))); |
||||
|
} |
||||
|
} else { |
||||
|
throw new JsonSyntaxException("Can't parse value: " + json); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private String checkDeviceConnected(String deviceName) { |
||||
|
if (!devices.containsKey(deviceName)) { |
||||
|
throw new RuntimeException("Device is not connected!"); |
||||
|
} else { |
||||
|
return deviceName; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private String checkDeviceName(String deviceName) { |
||||
|
if (StringUtils.isEmpty(deviceName)) { |
||||
|
throw new RuntimeException("Device name is empty!"); |
||||
|
} else { |
||||
|
return deviceName; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private String getDeviceName(MqttPublishMessage mqttMsg) throws AdaptorException { |
||||
|
JsonElement json = JsonMqttAdaptor.validateJsonPayload(gatewaySessionId, mqttMsg.payload()); |
||||
|
return json.getAsJsonObject().get("device").getAsString(); |
||||
|
} |
||||
|
|
||||
|
protected SessionMsgProcessor getProcessor() { |
||||
|
return processor; |
||||
|
} |
||||
|
|
||||
|
protected DeviceAuthService getAuthService() { |
||||
|
return authService; |
||||
|
} |
||||
|
|
||||
|
public void setChannel(ChannelHandlerContext channel) { |
||||
|
this.channel = channel; |
||||
|
} |
||||
|
|
||||
|
private void ack(MqttPublishMessage msg) { |
||||
|
writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(msg.variableHeader().messageId())); |
||||
|
} |
||||
|
|
||||
|
protected void writeAndFlush(MqttMessage mqttMessage) { |
||||
|
channel.writeAndFlush(mqttMessage); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue