增加时间同步
This commit is contained in:
parent
263ab0ebb3
commit
f38b76fc24
|
@ -0,0 +1,81 @@
|
||||||
|
package org.jetlinks.protocol.official;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.eclipse.californium.core.coap.CoAP;
|
||||||
|
import org.jetlinks.core.message.DeviceMessage;
|
||||||
|
import org.jetlinks.core.message.codec.*;
|
||||||
|
import org.reactivestreams.Publisher;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public abstract class AbstractCoapDeviceMessageCodec implements DeviceMessageCodec {
|
||||||
|
|
||||||
|
protected abstract Flux<DeviceMessage> decode(CoapMessage message, MessageDecodeContext context, Consumer<Object> response);
|
||||||
|
|
||||||
|
protected String getPath(CoapMessage message){
|
||||||
|
String path = message.getPath();
|
||||||
|
if (!path.startsWith("/")) {
|
||||||
|
path = "/" + path;
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getDeviceId(CoapMessage message){
|
||||||
|
String deviceId = message.getStringOption(2100).orElse(null);
|
||||||
|
String[] paths = TopicMessageCodec.removeProductPath(getPath(message));
|
||||||
|
if (StringUtils.isEmpty(deviceId) && paths.length > 1) {
|
||||||
|
deviceId = paths[1];
|
||||||
|
}
|
||||||
|
return deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public Flux<DeviceMessage> decode(@Nonnull MessageDecodeContext context) {
|
||||||
|
if (context.getMessage() instanceof CoapExchangeMessage) {
|
||||||
|
CoapExchangeMessage exchangeMessage = ((CoapExchangeMessage) context.getMessage());
|
||||||
|
AtomicBoolean alreadyReply = new AtomicBoolean();
|
||||||
|
Consumer<Object> responseHandler = (resp) -> {
|
||||||
|
if (alreadyReply.compareAndSet(false, true)) {
|
||||||
|
if (resp instanceof CoAP.ResponseCode) {
|
||||||
|
exchangeMessage.getExchange().respond(((CoAP.ResponseCode) resp));
|
||||||
|
}
|
||||||
|
if (resp instanceof String) {
|
||||||
|
exchangeMessage.getExchange().respond(((String) resp));
|
||||||
|
}
|
||||||
|
if (resp instanceof byte[]) {
|
||||||
|
exchangeMessage.getExchange().respond(CoAP.ResponseCode.CONTENT, ((byte[]) resp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this
|
||||||
|
.decode(exchangeMessage, context, responseHandler)
|
||||||
|
.doOnComplete(() -> responseHandler.accept(CoAP.ResponseCode.CREATED))
|
||||||
|
.doOnError(error -> {
|
||||||
|
log.error("decode coap message error", error);
|
||||||
|
responseHandler.accept(CoAP.ResponseCode.BAD_REQUEST);
|
||||||
|
})
|
||||||
|
.switchIfEmpty(Mono.fromRunnable(() -> responseHandler.accept(CoAP.ResponseCode.BAD_REQUEST)));
|
||||||
|
}
|
||||||
|
if (context.getMessage() instanceof CoapMessage) {
|
||||||
|
return decode(((CoapMessage) context.getMessage()), context, resp -> {
|
||||||
|
log.info("skip response coap request:{}", resp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Flux.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public Publisher<? extends EncodedMessage> encode(@Nonnull MessageEncodeContext context) {
|
||||||
|
return Mono.empty();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package org.jetlinks.protocol.official;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.jetlinks.core.device.DeviceOperator;
|
||||||
|
import org.jetlinks.core.message.DeviceMessage;
|
||||||
|
import org.jetlinks.core.utils.TopicUtils;
|
||||||
|
import org.jetlinks.protocol.official.functional.TimeSyncRequest;
|
||||||
|
import org.jetlinks.protocol.official.functional.TimeSyncResponse;
|
||||||
|
import org.reactivestreams.Publisher;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能性的topic,不和平台交互
|
||||||
|
*/
|
||||||
|
public enum FunctionalTopicHandlers {
|
||||||
|
|
||||||
|
//同步时间
|
||||||
|
timeSync("/*/*/time-sync") {
|
||||||
|
@SneakyThrows
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
Mono<DeviceMessage> doHandle(DeviceOperator device,
|
||||||
|
String[] topic,
|
||||||
|
byte[] payload,
|
||||||
|
ObjectMapper mapper,
|
||||||
|
Function<TopicPayload, Mono<Void>> sender) {
|
||||||
|
TopicPayload topicPayload = new TopicPayload();
|
||||||
|
topicPayload.setTopic(String.join("/", topic) + "/reply");
|
||||||
|
TimeSyncRequest msg = mapper.readValue(payload, TimeSyncRequest.class);
|
||||||
|
TimeSyncResponse response = TimeSyncResponse.of(msg.getMessageId(), System.currentTimeMillis());
|
||||||
|
topicPayload.setPayload(mapper.writeValueAsBytes(response));
|
||||||
|
//直接回复给设备
|
||||||
|
return sender
|
||||||
|
.apply(topicPayload)
|
||||||
|
.then(Mono.empty());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FunctionalTopicHandlers(String topic) {
|
||||||
|
this.pattern = topic.split("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String[] pattern;
|
||||||
|
|
||||||
|
abstract Publisher<DeviceMessage> doHandle(DeviceOperator device,
|
||||||
|
String[] topic,
|
||||||
|
byte[] payload,
|
||||||
|
ObjectMapper mapper,
|
||||||
|
Function<TopicPayload, Mono<Void>> sender);
|
||||||
|
|
||||||
|
|
||||||
|
public static Publisher<DeviceMessage> handle(DeviceOperator device,
|
||||||
|
String[] topic,
|
||||||
|
byte[] payload,
|
||||||
|
ObjectMapper mapper,
|
||||||
|
Function<TopicPayload, Mono<Void>> sender) {
|
||||||
|
return Mono
|
||||||
|
.justOrEmpty(fromTopic(topic))
|
||||||
|
.flatMapMany(handler -> handler.doHandle(device, topic, payload, mapper, sender));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Optional<FunctionalTopicHandlers> fromTopic(String[] topic) {
|
||||||
|
for (FunctionalTopicHandlers value : values()) {
|
||||||
|
if (TopicUtils.match(value.pattern, topic)) {
|
||||||
|
return Optional.of(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,18 +8,20 @@ import org.eclipse.californium.core.coap.CoAP;
|
||||||
import org.eclipse.californium.core.coap.OptionNumberRegistry;
|
import org.eclipse.californium.core.coap.OptionNumberRegistry;
|
||||||
import org.hswebframework.web.id.IDGenerator;
|
import org.hswebframework.web.id.IDGenerator;
|
||||||
import org.jetlinks.core.message.DeviceMessage;
|
import org.jetlinks.core.message.DeviceMessage;
|
||||||
import org.jetlinks.core.message.codec.*;
|
import org.jetlinks.core.message.codec.CoapMessage;
|
||||||
|
import org.jetlinks.core.message.codec.DefaultTransport;
|
||||||
|
import org.jetlinks.core.message.codec.MessageDecodeContext;
|
||||||
|
import org.jetlinks.core.message.codec.Transport;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class JetLinksCoapDTLSDeviceMessageCodec implements DeviceMessageCodec {
|
public class JetLinksCoapDTLSDeviceMessageCodec extends AbstractCoapDeviceMessageCodec {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Transport getSupportTransport() {
|
public Transport getSupportTransport() {
|
||||||
|
@ -28,8 +30,12 @@ public class JetLinksCoapDTLSDeviceMessageCodec implements DeviceMessageCodec {
|
||||||
|
|
||||||
|
|
||||||
public Flux<DeviceMessage> decode(CoapMessage message, MessageDecodeContext context, Consumer<Object> response) {
|
public Flux<DeviceMessage> decode(CoapMessage message, MessageDecodeContext context, Consumer<Object> response) {
|
||||||
|
if (context.getDevice() == null) {
|
||||||
|
return Flux.empty();
|
||||||
|
}
|
||||||
return Flux.defer(() -> {
|
return Flux.defer(() -> {
|
||||||
String path = message.getPath();
|
String path = getPath(message);
|
||||||
|
String deviceId = getDeviceId(message);
|
||||||
String sign = message.getStringOption(2110).orElse(null);
|
String sign = message.getStringOption(2110).orElse(null);
|
||||||
String token = message.getStringOption(2111).orElse(null);
|
String token = message.getStringOption(2111).orElse(null);
|
||||||
byte[] payload = message.payloadAsBytes();
|
byte[] payload = message.payloadAsBytes();
|
||||||
|
@ -39,26 +45,34 @@ public class JetLinksCoapDTLSDeviceMessageCodec implements DeviceMessageCodec {
|
||||||
.map(MediaType.APPLICATION_CBOR::includes)
|
.map(MediaType.APPLICATION_CBOR::includes)
|
||||||
.orElse(false);
|
.orElse(false);
|
||||||
ObjectMapper objectMapper = cbor ? ObjectMappers.CBOR_MAPPER : ObjectMappers.JSON_MAPPER;
|
ObjectMapper objectMapper = cbor ? ObjectMappers.CBOR_MAPPER : ObjectMappers.JSON_MAPPER;
|
||||||
if ("/auth".equals(path)) {
|
|
||||||
|
if (StringUtils.isEmpty(deviceId)) {
|
||||||
|
response.accept(CoAP.ResponseCode.UNAUTHORIZED);
|
||||||
|
return Mono.empty();
|
||||||
|
}
|
||||||
|
// TODO: 2021/7/30 移到 FunctionalTopicHandlers
|
||||||
|
if (path.endsWith("/request-token")) {
|
||||||
//认证
|
//认证
|
||||||
return context
|
return context
|
||||||
.getDevice()
|
.getDevice(deviceId)
|
||||||
.getConfig("secureKey")
|
.switchIfEmpty(Mono.fromRunnable(() -> response.accept(CoAP.ResponseCode.UNAUTHORIZED)))
|
||||||
.flatMap(sk -> {
|
.flatMap(device -> device
|
||||||
String secureKey = sk.asString();
|
.getConfig("secureKey")
|
||||||
if (!verifySign(secureKey, context.getDevice().getDeviceId(), payload, sign)) {
|
.flatMap(sk -> {
|
||||||
response.accept(CoAP.ResponseCode.BAD_REQUEST);
|
String secureKey = sk.asString();
|
||||||
return Mono.empty();
|
if (!verifySign(secureKey, deviceId, payload, sign)) {
|
||||||
}
|
response.accept(CoAP.ResponseCode.BAD_REQUEST);
|
||||||
String newToken = IDGenerator.MD5.generate();
|
return Mono.empty();
|
||||||
return context.getDevice()
|
}
|
||||||
.setConfig("coap-token", newToken)
|
String newToken = IDGenerator.MD5.generate();
|
||||||
.doOnSuccess(success -> {
|
return device
|
||||||
JSONObject json = new JSONObject();
|
.setConfig("coap-token", newToken)
|
||||||
json.put("token", newToken);
|
.doOnSuccess(success -> {
|
||||||
response.accept(json.toJSONString());
|
JSONObject json = new JSONObject();
|
||||||
});
|
json.put("token", newToken);
|
||||||
})
|
response.accept(json.toJSONString());
|
||||||
|
});
|
||||||
|
}))
|
||||||
.then(Mono.empty());
|
.then(Mono.empty());
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(token)) {
|
if (StringUtils.isEmpty(token)) {
|
||||||
|
@ -66,22 +80,28 @@ public class JetLinksCoapDTLSDeviceMessageCodec implements DeviceMessageCodec {
|
||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
}
|
}
|
||||||
return context
|
return context
|
||||||
.getDevice()
|
.getDevice(deviceId)
|
||||||
.getConfig("coap-token")
|
.flatMapMany(device -> device
|
||||||
.switchIfEmpty(Mono.fromRunnable(() -> response.accept(CoAP.ResponseCode.UNAUTHORIZED)))
|
.getSelfConfig("coap-token")
|
||||||
.flatMapMany(value -> {
|
.switchIfEmpty(Mono.fromRunnable(() -> response.accept(CoAP.ResponseCode.UNAUTHORIZED)))
|
||||||
String tk = value.asString();
|
.flatMapMany(value -> {
|
||||||
if (!token.equals(tk)) {
|
String tk = value.asString();
|
||||||
response.accept(CoAP.ResponseCode.UNAUTHORIZED);
|
if (!token.equals(tk)) {
|
||||||
return Mono.empty();
|
response.accept(CoAP.ResponseCode.UNAUTHORIZED);
|
||||||
}
|
return Mono.empty();
|
||||||
return TopicMessageCodec
|
}
|
||||||
.decode(objectMapper, TopicMessageCodec.removeProductPath(path), payload)
|
return TopicMessageCodec
|
||||||
.switchIfEmpty(Mono.fromRunnable(() -> response.accept(CoAP.ResponseCode.BAD_REQUEST)));
|
.decode(objectMapper, TopicMessageCodec.removeProductPath(path), payload)
|
||||||
})
|
//如果不能直接解码,可能是其他设备功能
|
||||||
.doOnComplete(() -> {
|
.switchIfEmpty(FunctionalTopicHandlers
|
||||||
response.accept(CoAP.ResponseCode.CREATED);
|
.handle(device,
|
||||||
})
|
path.split("/"),
|
||||||
|
payload,
|
||||||
|
objectMapper,
|
||||||
|
reply -> Mono.fromRunnable(() -> response.accept(reply.getPayload()))));
|
||||||
|
}))
|
||||||
|
|
||||||
|
.doOnComplete(() -> response.accept(CoAP.ResponseCode.CREATED))
|
||||||
.doOnError(error -> {
|
.doOnError(error -> {
|
||||||
log.error("decode coap message error", error);
|
log.error("decode coap message error", error);
|
||||||
response.accept(CoAP.ResponseCode.BAD_REQUEST);
|
response.accept(CoAP.ResponseCode.BAD_REQUEST);
|
||||||
|
@ -90,28 +110,6 @@ public class JetLinksCoapDTLSDeviceMessageCodec implements DeviceMessageCodec {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
@Override
|
|
||||||
public Flux<DeviceMessage> decode(@Nonnull MessageDecodeContext context) {
|
|
||||||
if (context.getMessage() instanceof CoapExchangeMessage) {
|
|
||||||
CoapExchangeMessage exchangeMessage = ((CoapExchangeMessage) context.getMessage());
|
|
||||||
return decode(exchangeMessage, context, resp -> {
|
|
||||||
if (resp instanceof CoAP.ResponseCode) {
|
|
||||||
exchangeMessage.getExchange().respond(((CoAP.ResponseCode) resp));
|
|
||||||
}
|
|
||||||
if (resp instanceof String) {
|
|
||||||
exchangeMessage.getExchange().respond(((String) resp));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (context.getMessage() instanceof CoapMessage) {
|
|
||||||
return decode(((CoapMessage) context.getMessage()), context, resp -> {
|
|
||||||
log.info("skip response coap request:{}", resp);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Flux.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean verifySign(String secureKey, String deviceId, byte[] payload, String sign) {
|
protected boolean verifySign(String secureKey, String deviceId, byte[] payload, String sign) {
|
||||||
//验证签名
|
//验证签名
|
||||||
|
@ -125,9 +123,5 @@ public class JetLinksCoapDTLSDeviceMessageCodec implements DeviceMessageCodec {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
@Override
|
|
||||||
public Mono<? extends EncodedMessage> encode(@Nonnull MessageEncodeContext context) {
|
|
||||||
return Mono.empty();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +1,34 @@
|
||||||
package org.jetlinks.protocol.official;
|
package org.jetlinks.protocol.official;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.californium.core.coap.CoAP;
|
import org.eclipse.californium.core.coap.CoAP;
|
||||||
import org.eclipse.californium.core.coap.OptionNumberRegistry;
|
import org.eclipse.californium.core.coap.OptionNumberRegistry;
|
||||||
import org.eclipse.californium.core.server.resources.CoapExchange;
|
|
||||||
import org.jetlinks.core.Value;
|
import org.jetlinks.core.Value;
|
||||||
import org.jetlinks.core.message.DeviceMessage;
|
import org.jetlinks.core.message.DeviceMessage;
|
||||||
import org.jetlinks.core.message.codec.*;
|
import org.jetlinks.core.message.codec.CoapMessage;
|
||||||
|
import org.jetlinks.core.message.codec.DefaultTransport;
|
||||||
|
import org.jetlinks.core.message.codec.MessageDecodeContext;
|
||||||
|
import org.jetlinks.core.message.codec.Transport;
|
||||||
import org.jetlinks.protocol.official.cipher.Ciphers;
|
import org.jetlinks.protocol.official.cipher.Ciphers;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class JetLinksCoapDeviceMessageCodec implements DeviceMessageCodec {
|
public class JetLinksCoapDeviceMessageCodec extends AbstractCoapDeviceMessageCodec {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Transport getSupportTransport() {
|
public Transport getSupportTransport() {
|
||||||
return DefaultTransport.CoAP;
|
return DefaultTransport.CoAP;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Flux<DeviceMessage> decode(CoapMessage message, MessageDecodeContext context) {
|
protected Flux<DeviceMessage> decode(CoapMessage message, MessageDecodeContext context, Consumer<Object> response) {
|
||||||
String path = message.getPath();
|
String path = getPath(message);
|
||||||
|
String deviceId = getDeviceId(message);
|
||||||
boolean cbor = message
|
boolean cbor = message
|
||||||
.getStringOption(OptionNumberRegistry.CONTENT_FORMAT)
|
.getStringOption(OptionNumberRegistry.CONTENT_FORMAT)
|
||||||
.map(MediaType::valueOf)
|
.map(MediaType::valueOf)
|
||||||
|
@ -36,60 +36,29 @@ public class JetLinksCoapDeviceMessageCodec implements DeviceMessageCodec {
|
||||||
.orElse(false);
|
.orElse(false);
|
||||||
ObjectMapper objectMapper = cbor ? ObjectMappers.CBOR_MAPPER : ObjectMappers.JSON_MAPPER;
|
ObjectMapper objectMapper = cbor ? ObjectMappers.CBOR_MAPPER : ObjectMappers.JSON_MAPPER;
|
||||||
return context
|
return context
|
||||||
.getDevice()
|
.getDevice(deviceId)
|
||||||
.getConfigs("encAlg", "secureKey")
|
.flatMapMany(device -> device
|
||||||
.flatMapMany(configs -> {
|
.getConfigs("encAlg", "secureKey")
|
||||||
Ciphers ciphers = configs
|
.flatMapMany(configs -> {
|
||||||
.getValue("encAlg")
|
Ciphers ciphers = configs
|
||||||
.map(Value::asString)
|
.getValue("encAlg")
|
||||||
.flatMap(Ciphers::of)
|
.map(Value::asString)
|
||||||
.orElse(Ciphers.AES);
|
.flatMap(Ciphers::of)
|
||||||
String secureKey = configs.getValue("secureKey").map(Value::asString).orElse(null);
|
.orElse(Ciphers.AES);
|
||||||
ByteBuf byteBuf = message.getPayload();
|
String secureKey = configs.getValue("secureKey").map(Value::asString).orElse(null);
|
||||||
byte[] req = new byte[byteBuf.readableBytes()];
|
byte[] payload = ciphers.decrypt(message.payloadAsBytes(), secureKey);
|
||||||
byteBuf.readBytes(req);
|
//解码
|
||||||
byteBuf.resetReaderIndex();
|
return TopicMessageCodec
|
||||||
byte[] payload = ciphers.decrypt(req, secureKey);
|
.decode(objectMapper, TopicMessageCodec.removeProductPath(path), payload)
|
||||||
//解码
|
//如果不能直接解码,可能是其他设备功能
|
||||||
return TopicMessageCodec.decode(objectMapper, TopicMessageCodec.removeProductPath(path), payload);
|
.switchIfEmpty(FunctionalTopicHandlers
|
||||||
});
|
.handle(device,
|
||||||
|
path.split("/"),
|
||||||
|
payload,
|
||||||
|
objectMapper,
|
||||||
|
reply -> Mono.fromRunnable(() -> response.accept(reply.getPayload()))));
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Flux<DeviceMessage> decode(CoapExchangeMessage message, MessageDecodeContext context) {
|
|
||||||
CoapExchange exchange = message.getExchange();
|
|
||||||
return decode(((CoapMessage) message), context)
|
|
||||||
.doOnComplete(() -> {
|
|
||||||
exchange.respond(CoAP.ResponseCode.CREATED);
|
|
||||||
exchange.accept();
|
|
||||||
})
|
|
||||||
.switchIfEmpty(Mono.fromRunnable(() -> {
|
|
||||||
exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
|
|
||||||
}))
|
|
||||||
.doOnError(error -> {
|
|
||||||
log.error("decode coap message error", error);
|
|
||||||
exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
@Override
|
|
||||||
public Flux<DeviceMessage> decode(@Nonnull MessageDecodeContext context) {
|
|
||||||
return Flux.defer(() -> {
|
|
||||||
log.debug("handle coap message:\n{}", context.getMessage());
|
|
||||||
if (context.getMessage() instanceof CoapExchangeMessage) {
|
|
||||||
return decode(((CoapExchangeMessage) context.getMessage()), context);
|
|
||||||
}
|
|
||||||
if (context.getMessage() instanceof CoapMessage) {
|
|
||||||
return decode(((CoapMessage) context.getMessage()), context);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Flux.empty();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
@Override
|
|
||||||
public Mono<? extends EncodedMessage> encode(@Nonnull MessageEncodeContext context) {
|
|
||||||
return Mono.empty();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package org.jetlinks.protocol.official;
|
package org.jetlinks.protocol.official;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jetlinks.core.device.DeviceConfigKey;
|
import org.jetlinks.core.device.DeviceConfigKey;
|
||||||
|
@ -13,6 +15,7 @@ import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <pre>
|
* <pre>
|
||||||
|
@ -54,8 +57,11 @@ public class JetLinksMqttDeviceMessageCodec implements DeviceMessageCodec {
|
||||||
|
|
||||||
private final Transport transport;
|
private final Transport transport;
|
||||||
|
|
||||||
|
private final ObjectMapper mapper;
|
||||||
|
|
||||||
public JetLinksMqttDeviceMessageCodec(Transport transport) {
|
public JetLinksMqttDeviceMessageCodec(Transport transport) {
|
||||||
this.transport = transport;
|
this.transport = transport;
|
||||||
|
this.mapper = ObjectMappers.JSON_MAPPER;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JetLinksMqttDeviceMessageCodec() {
|
public JetLinksMqttDeviceMessageCodec() {
|
||||||
|
@ -81,15 +87,14 @@ public class JetLinksMqttDeviceMessageCodec implements DeviceMessageCodec {
|
||||||
if (message instanceof DeviceMessage) {
|
if (message instanceof DeviceMessage) {
|
||||||
DeviceMessage deviceMessage = ((DeviceMessage) message);
|
DeviceMessage deviceMessage = ((DeviceMessage) message);
|
||||||
|
|
||||||
TopicPayload convertResult = TopicMessageCodec.encode(ObjectMappers.JSON_MAPPER, deviceMessage);
|
TopicPayload convertResult = TopicMessageCodec.encode(mapper, deviceMessage);
|
||||||
if (convertResult == null) {
|
if (convertResult == null) {
|
||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
}
|
}
|
||||||
return Mono
|
return Mono
|
||||||
.justOrEmpty(deviceMessage.getHeader("productId").map(String::valueOf))
|
.justOrEmpty(deviceMessage.getHeader("productId").map(String::valueOf))
|
||||||
.switchIfEmpty(context.getDevice(deviceMessage.getDeviceId())
|
.switchIfEmpty(context.getDevice(deviceMessage.getDeviceId())
|
||||||
.flatMap(device -> device
|
.flatMap(device -> device.getSelfConfig(DeviceConfigKey.productId))
|
||||||
.getConfig(DeviceConfigKey.productId))
|
|
||||||
)
|
)
|
||||||
.defaultIfEmpty("null")
|
.defaultIfEmpty("null")
|
||||||
.map(productId -> SimpleMqttMessage
|
.map(productId -> SimpleMqttMessage
|
||||||
|
@ -109,11 +114,42 @@ public class JetLinksMqttDeviceMessageCodec implements DeviceMessageCodec {
|
||||||
@Override
|
@Override
|
||||||
public Flux<DeviceMessage> decode(@Nonnull MessageDecodeContext context) {
|
public Flux<DeviceMessage> decode(@Nonnull MessageDecodeContext context) {
|
||||||
MqttMessage message = (MqttMessage) context.getMessage();
|
MqttMessage message = (MqttMessage) context.getMessage();
|
||||||
|
|
||||||
byte[] payload = message.payloadAsBytes();
|
byte[] payload = message.payloadAsBytes();
|
||||||
|
|
||||||
return TopicMessageCodec
|
return TopicMessageCodec
|
||||||
.decode(ObjectMappers.JSON_MAPPER, TopicMessageCodec.removeProductPath(message.getTopic()), payload);
|
.decode(mapper, TopicMessageCodec.removeProductPath(message.getTopic()), payload)
|
||||||
|
//如果不能直接解码,可能是其他设备功能
|
||||||
|
.switchIfEmpty(FunctionalTopicHandlers
|
||||||
|
.handle(context.getDevice(),
|
||||||
|
message.getTopic().split("/"),
|
||||||
|
payload,
|
||||||
|
mapper,
|
||||||
|
reply -> doReply(context, reply)))
|
||||||
|
;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mono<Void> doReply(MessageCodecContext context, TopicPayload reply) {
|
||||||
|
|
||||||
|
if (context instanceof FromDeviceMessageContext) {
|
||||||
|
return ((FromDeviceMessageContext) context)
|
||||||
|
.getSession()
|
||||||
|
.send(SimpleMqttMessage
|
||||||
|
.builder()
|
||||||
|
.topic(reply.getTopic())
|
||||||
|
.payload(reply.getPayload())
|
||||||
|
.build())
|
||||||
|
.then();
|
||||||
|
} else if (context instanceof ToDeviceMessageContext) {
|
||||||
|
return ((ToDeviceMessageContext) context)
|
||||||
|
.sendToDevice(SimpleMqttMessage
|
||||||
|
.builder()
|
||||||
|
.topic(reply.getTopic())
|
||||||
|
.payload(reply.getPayload())
|
||||||
|
.build())
|
||||||
|
.then();
|
||||||
|
}
|
||||||
|
return Mono.empty();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package org.jetlinks.protocol.official.functional;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class TimeSyncRequest {
|
||||||
|
private String messageId;
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package org.jetlinks.protocol.official.functional;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor(staticName = "of")
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class TimeSyncResponse {
|
||||||
|
private String messageId;
|
||||||
|
private long timestamp;
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import org.eclipse.californium.core.CoapClient;
|
||||||
import org.eclipse.californium.core.CoapResource;
|
import org.eclipse.californium.core.CoapResource;
|
||||||
import org.eclipse.californium.core.CoapResponse;
|
import org.eclipse.californium.core.CoapResponse;
|
||||||
import org.eclipse.californium.core.CoapServer;
|
import org.eclipse.californium.core.CoapServer;
|
||||||
|
import org.eclipse.californium.core.coap.Option;
|
||||||
import org.eclipse.californium.core.coap.Request;
|
import org.eclipse.californium.core.coap.Request;
|
||||||
import org.eclipse.californium.core.network.CoapEndpoint;
|
import org.eclipse.californium.core.network.CoapEndpoint;
|
||||||
import org.eclipse.californium.core.network.Endpoint;
|
import org.eclipse.californium.core.network.Endpoint;
|
||||||
|
@ -19,26 +20,37 @@ import org.jetlinks.core.message.Message;
|
||||||
import org.jetlinks.core.message.codec.CoapExchangeMessage;
|
import org.jetlinks.core.message.codec.CoapExchangeMessage;
|
||||||
import org.jetlinks.core.message.codec.EncodedMessage;
|
import org.jetlinks.core.message.codec.EncodedMessage;
|
||||||
import org.jetlinks.core.message.codec.MessageDecodeContext;
|
import org.jetlinks.core.message.codec.MessageDecodeContext;
|
||||||
|
import org.jetlinks.core.message.event.EventMessage;
|
||||||
import org.jetlinks.protocol.official.cipher.Ciphers;
|
import org.jetlinks.protocol.official.cipher.Ciphers;
|
||||||
|
import org.junit.After;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
public class JetLinksCoapDeviceMessageCodecTest {
|
public class JetLinksCoapDeviceMessageCodecTest {
|
||||||
|
|
||||||
|
|
||||||
JetLinksCoapDeviceMessageCodec codec = new JetLinksCoapDeviceMessageCodec();
|
JetLinksCoapDeviceMessageCodec codec = new JetLinksCoapDeviceMessageCodec();
|
||||||
|
|
||||||
DeviceOperator device;
|
DeviceOperator device;
|
||||||
|
|
||||||
private String key = RandomUtil.randomChar(16);
|
private final String key = RandomUtil.randomChar(16);
|
||||||
|
|
||||||
|
private TestDeviceRegistry registry;
|
||||||
|
AtomicReference<Message> messageRef = new AtomicReference<>();
|
||||||
|
|
||||||
|
private CoapServer server;
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void shutdown(){
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
@Before
|
@Before
|
||||||
public void init() {
|
public void init() {
|
||||||
TestDeviceRegistry registry = new TestDeviceRegistry(new CompositeProtocolSupports(), new StandaloneDeviceMessageBroker());
|
registry = new TestDeviceRegistry(new CompositeProtocolSupports(), new StandaloneDeviceMessageBroker());
|
||||||
device = registry
|
device = registry
|
||||||
.register(DeviceInfo.builder()
|
.register(DeviceInfo.builder()
|
||||||
.id("test")
|
.id("test")
|
||||||
|
@ -46,14 +58,8 @@ public class JetLinksCoapDeviceMessageCodecTest {
|
||||||
.build())
|
.build())
|
||||||
.flatMap(operator -> operator.setConfig("secureKey", key).thenReturn(operator))
|
.flatMap(operator -> operator.setConfig("secureKey", key).thenReturn(operator))
|
||||||
.block();
|
.block();
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
server = new CoapServer() {
|
||||||
@SneakyThrows
|
|
||||||
public void test() {
|
|
||||||
AtomicReference<Message> messageRef = new AtomicReference<>();
|
|
||||||
|
|
||||||
CoapServer server = new CoapServer() {
|
|
||||||
@Override
|
@Override
|
||||||
protected Resource createRoot() {
|
protected Resource createRoot() {
|
||||||
return new CoapResource("/", true) {
|
return new CoapResource("/", true) {
|
||||||
|
@ -72,6 +78,11 @@ public class JetLinksCoapDeviceMessageCodecTest {
|
||||||
public DeviceOperator getDevice() {
|
public DeviceOperator getDevice() {
|
||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<DeviceOperator> getDevice(String deviceId) {
|
||||||
|
return registry.getDevice(deviceId);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.doOnNext(messageRef::set)
|
.doOnNext(messageRef::set)
|
||||||
.doOnError(Throwable::printStackTrace)
|
.doOnError(Throwable::printStackTrace)
|
||||||
|
@ -90,11 +101,14 @@ public class JetLinksCoapDeviceMessageCodecTest {
|
||||||
.setPort(12341).build();
|
.setPort(12341).build();
|
||||||
server.addEndpoint(endpoint);
|
server.addEndpoint(endpoint);
|
||||||
server.start();
|
server.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SneakyThrows
|
||||||
|
public void test() {
|
||||||
|
|
||||||
CoapClient coapClient = new CoapClient();
|
CoapClient coapClient = new CoapClient();
|
||||||
|
|
||||||
|
|
||||||
Request request = Request.newPost();
|
Request request = Request.newPost();
|
||||||
String payload = "{\"data\":1}";
|
String payload = "{\"data\":1}";
|
||||||
|
|
||||||
|
@ -104,11 +118,31 @@ public class JetLinksCoapDeviceMessageCodecTest {
|
||||||
|
|
||||||
CoapResponse response = coapClient.advanced(request);
|
CoapResponse response = coapClient.advanced(request);
|
||||||
Assert.assertTrue(response.isSuccess());
|
Assert.assertTrue(response.isSuccess());
|
||||||
Thread.sleep(1000);
|
|
||||||
Assert.assertNotNull(messageRef.get());
|
|
||||||
|
|
||||||
|
Assert.assertNotNull(messageRef.get());
|
||||||
|
Assert.assertTrue(messageRef.get() instanceof EventMessage);
|
||||||
System.out.println(messageRef.get());
|
System.out.println(messageRef.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SneakyThrows
|
||||||
|
public void testTimeSync() {
|
||||||
|
|
||||||
|
CoapClient coapClient = new CoapClient();
|
||||||
|
|
||||||
|
Request request = Request.newPost();
|
||||||
|
String payload = "{\"messageId\":1}";
|
||||||
|
|
||||||
|
request.setURI("coap://localhost:12341/test/test/time-sync");
|
||||||
|
request.setPayload(Ciphers.AES.encrypt(payload.getBytes(), key));
|
||||||
|
// request.getOptions().setContentFormat(MediaTypeRegistry.APPLICATION_JSON);
|
||||||
|
|
||||||
|
CoapResponse response = coapClient.advanced(request);
|
||||||
|
Assert.assertTrue(response.isSuccess());
|
||||||
|
|
||||||
|
|
||||||
|
Assert.assertTrue(response.getResponseText().contains("timestamp"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -15,11 +15,14 @@ import org.jetlinks.core.message.firmware.UpgradeFirmwareMessage;
|
||||||
import org.jetlinks.core.message.function.FunctionInvokeMessage;
|
import org.jetlinks.core.message.function.FunctionInvokeMessage;
|
||||||
import org.jetlinks.core.message.function.FunctionInvokeMessageReply;
|
import org.jetlinks.core.message.function.FunctionInvokeMessageReply;
|
||||||
import org.jetlinks.core.message.property.*;
|
import org.jetlinks.core.message.property.*;
|
||||||
|
import org.jetlinks.core.server.session.DeviceSession;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -31,19 +34,21 @@ public class JetLinksMqttDeviceMessageCodecTest {
|
||||||
|
|
||||||
TestDeviceRegistry registry;
|
TestDeviceRegistry registry;
|
||||||
|
|
||||||
|
private EncodedMessage currentReply;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() {
|
public void init() {
|
||||||
registry = new TestDeviceRegistry(new CompositeProtocolSupports(), new StandaloneDeviceMessageBroker());
|
registry = new TestDeviceRegistry(new CompositeProtocolSupports(), new StandaloneDeviceMessageBroker());
|
||||||
|
|
||||||
registry.register(ProductInfo.builder()
|
registry.register(ProductInfo.builder()
|
||||||
.id("product1")
|
.id("product1")
|
||||||
.protocol("jetlinks")
|
.protocol("jetlinks")
|
||||||
.build())
|
.build())
|
||||||
.flatMap(product -> registry.register(DeviceInfo.builder()
|
.flatMap(product -> registry.register(DeviceInfo.builder()
|
||||||
.id("device1")
|
.id("device1")
|
||||||
.productId("product1")
|
.productId("product1")
|
||||||
.build()))
|
.build()))
|
||||||
.subscribe();
|
.block();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,9 +88,10 @@ public class JetLinksMqttDeviceMessageCodecTest {
|
||||||
@Test
|
@Test
|
||||||
public void testReadPropertyReply() {
|
public void testReadPropertyReply() {
|
||||||
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
||||||
.topic("/product1/device1/properties/read/reply")
|
.topic("/product1/device1/properties/read/reply")
|
||||||
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"properties\":{\"sn\":\"test\"}}".getBytes()))
|
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"properties\":{\"sn\":\"test\"}}"
|
||||||
.build())).blockFirst();
|
.getBytes()))
|
||||||
|
.build())).blockFirst();
|
||||||
|
|
||||||
Assert.assertTrue(message instanceof ReadPropertyMessageReply);
|
Assert.assertTrue(message instanceof ReadPropertyMessageReply);
|
||||||
ReadPropertyMessageReply reply = ((ReadPropertyMessageReply) message);
|
ReadPropertyMessageReply reply = ((ReadPropertyMessageReply) message);
|
||||||
|
@ -99,17 +105,19 @@ public class JetLinksMqttDeviceMessageCodecTest {
|
||||||
@Test
|
@Test
|
||||||
public void testChildReadPropertyReply() {
|
public void testChildReadPropertyReply() {
|
||||||
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
||||||
.topic("/product1/device1/child/test/properties/read/reply")
|
.topic("/product1/device1/child/test/properties/read/reply")
|
||||||
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"properties\":{\"sn\":\"test\"}}".getBytes()))
|
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"properties\":{\"sn\":\"test\"}}"
|
||||||
.build())).blockFirst();
|
.getBytes()))
|
||||||
|
.build())).blockFirst();
|
||||||
|
|
||||||
Assert.assertTrue(message instanceof ChildDeviceMessage);
|
Assert.assertTrue(message instanceof ChildDeviceMessage);
|
||||||
ChildDeviceMessage childReply = ((ChildDeviceMessage) message);
|
ChildDeviceMessage childReply = ((ChildDeviceMessage) message);
|
||||||
|
|
||||||
Assert.assertEquals(childReply.getDeviceId(),"device1");
|
Assert.assertEquals(childReply.getDeviceId(), "device1");
|
||||||
Assert.assertEquals(childReply.getMessageId(),"test");
|
Assert.assertEquals(childReply.getMessageId(), "test");
|
||||||
|
|
||||||
ReadPropertyMessageReply reply = (ReadPropertyMessageReply)childReply.getChildDeviceMessage();;
|
ReadPropertyMessageReply reply = (ReadPropertyMessageReply) childReply.getChildDeviceMessage();
|
||||||
|
;
|
||||||
Assert.assertTrue(reply.isSuccess());
|
Assert.assertTrue(reply.isSuccess());
|
||||||
Assert.assertEquals(reply.getDeviceId(), "test");
|
Assert.assertEquals(reply.getDeviceId(), "test");
|
||||||
Assert.assertEquals(reply.getMessageId(), "test");
|
Assert.assertEquals(reply.getMessageId(), "test");
|
||||||
|
@ -155,9 +163,10 @@ public class JetLinksMqttDeviceMessageCodecTest {
|
||||||
@Test
|
@Test
|
||||||
public void testWritePropertyReply() {
|
public void testWritePropertyReply() {
|
||||||
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
||||||
.topic("/product1/device1/properties/write/reply")
|
.topic("/product1/device1/properties/write/reply")
|
||||||
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"properties\":{\"sn\":\"test\"}}".getBytes()))
|
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"properties\":{\"sn\":\"test\"}}"
|
||||||
.build())).blockFirst();
|
.getBytes()))
|
||||||
|
.build())).blockFirst();
|
||||||
|
|
||||||
Assert.assertTrue(message instanceof WritePropertyMessageReply);
|
Assert.assertTrue(message instanceof WritePropertyMessageReply);
|
||||||
WritePropertyMessageReply reply = ((WritePropertyMessageReply) message);
|
WritePropertyMessageReply reply = ((WritePropertyMessageReply) message);
|
||||||
|
@ -169,21 +178,22 @@ public class JetLinksMqttDeviceMessageCodecTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWriteChildPropertyReply() {
|
public void testWriteChildPropertyReply() {
|
||||||
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
||||||
.topic("/product1/device1/child/test/properties/write/reply")
|
.topic("/product1/device1/child/test/properties/write/reply")
|
||||||
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"properties\":{\"sn\":\"test\"}}".getBytes()))
|
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"properties\":{\"sn\":\"test\"}}"
|
||||||
.build())).blockFirst();
|
.getBytes()))
|
||||||
|
.build())).blockFirst();
|
||||||
|
|
||||||
Assert.assertTrue(message instanceof ChildDeviceMessage);
|
Assert.assertTrue(message instanceof ChildDeviceMessage);
|
||||||
ChildDeviceMessage childReply = ((ChildDeviceMessage) message);
|
ChildDeviceMessage childReply = ((ChildDeviceMessage) message);
|
||||||
|
|
||||||
Assert.assertEquals(childReply.getDeviceId(),"device1");
|
Assert.assertEquals(childReply.getDeviceId(), "device1");
|
||||||
Assert.assertEquals(childReply.getMessageId(),"test");
|
Assert.assertEquals(childReply.getMessageId(), "test");
|
||||||
|
|
||||||
WritePropertyMessageReply reply = (WritePropertyMessageReply)childReply.getChildDeviceMessage();;
|
WritePropertyMessageReply reply = (WritePropertyMessageReply) childReply.getChildDeviceMessage();
|
||||||
|
;
|
||||||
Assert.assertTrue(reply.isSuccess());
|
Assert.assertTrue(reply.isSuccess());
|
||||||
Assert.assertEquals(reply.getDeviceId(), "test");
|
Assert.assertEquals(reply.getDeviceId(), "test");
|
||||||
Assert.assertEquals(reply.getMessageId(), "test");
|
Assert.assertEquals(reply.getMessageId(), "test");
|
||||||
|
@ -227,9 +237,10 @@ public class JetLinksMqttDeviceMessageCodecTest {
|
||||||
@Test
|
@Test
|
||||||
public void testInvokeFunctionReply() {
|
public void testInvokeFunctionReply() {
|
||||||
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
||||||
.topic("/product1/device1/function/invoke/reply")
|
.topic("/product1/device1/function/invoke/reply")
|
||||||
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"output\":\"ok\"}".getBytes()))
|
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"output\":\"ok\"}"
|
||||||
.build())).blockFirst();
|
.getBytes()))
|
||||||
|
.build())).blockFirst();
|
||||||
|
|
||||||
Assert.assertTrue(message instanceof FunctionInvokeMessageReply);
|
Assert.assertTrue(message instanceof FunctionInvokeMessageReply);
|
||||||
FunctionInvokeMessageReply reply = ((FunctionInvokeMessageReply) message);
|
FunctionInvokeMessageReply reply = ((FunctionInvokeMessageReply) message);
|
||||||
|
@ -243,17 +254,19 @@ public class JetLinksMqttDeviceMessageCodecTest {
|
||||||
@Test
|
@Test
|
||||||
public void testInvokeChildFunctionReply() {
|
public void testInvokeChildFunctionReply() {
|
||||||
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
||||||
.topic("/product1/device1/child/test/function/invoke/reply")
|
.topic("/product1/device1/child/test/function/invoke/reply")
|
||||||
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"output\":\"ok\"}".getBytes()))
|
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"output\":\"ok\"}"
|
||||||
.build())).blockFirst();
|
.getBytes()))
|
||||||
|
.build())).blockFirst();
|
||||||
|
|
||||||
Assert.assertTrue(message instanceof ChildDeviceMessage);
|
Assert.assertTrue(message instanceof ChildDeviceMessage);
|
||||||
ChildDeviceMessage childReply = ((ChildDeviceMessage) message);
|
ChildDeviceMessage childReply = ((ChildDeviceMessage) message);
|
||||||
|
|
||||||
Assert.assertEquals(childReply.getDeviceId(),"device1");
|
Assert.assertEquals(childReply.getDeviceId(), "device1");
|
||||||
Assert.assertEquals(childReply.getMessageId(),"test");
|
Assert.assertEquals(childReply.getMessageId(), "test");
|
||||||
|
|
||||||
FunctionInvokeMessageReply reply = (FunctionInvokeMessageReply)childReply.getChildDeviceMessage();;
|
FunctionInvokeMessageReply reply = (FunctionInvokeMessageReply) childReply.getChildDeviceMessage();
|
||||||
|
;
|
||||||
Assert.assertTrue(reply.isSuccess());
|
Assert.assertTrue(reply.isSuccess());
|
||||||
Assert.assertEquals(reply.getDeviceId(), "test");
|
Assert.assertEquals(reply.getDeviceId(), "test");
|
||||||
Assert.assertEquals(reply.getMessageId(), "test");
|
Assert.assertEquals(reply.getMessageId(), "test");
|
||||||
|
@ -264,9 +277,10 @@ public class JetLinksMqttDeviceMessageCodecTest {
|
||||||
@Test
|
@Test
|
||||||
public void testEvent() {
|
public void testEvent() {
|
||||||
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
||||||
.topic("/product1/device1/event/temp")
|
.topic("/product1/device1/event/temp")
|
||||||
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"data\":100}".getBytes()))
|
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"data\":100}"
|
||||||
.build())).blockFirst();
|
.getBytes()))
|
||||||
|
.build())).blockFirst();
|
||||||
|
|
||||||
Assert.assertTrue(message instanceof EventMessage);
|
Assert.assertTrue(message instanceof EventMessage);
|
||||||
EventMessage reply = ((EventMessage) message);
|
EventMessage reply = ((EventMessage) message);
|
||||||
|
@ -279,9 +293,10 @@ public class JetLinksMqttDeviceMessageCodecTest {
|
||||||
@Test
|
@Test
|
||||||
public void testChildEvent() {
|
public void testChildEvent() {
|
||||||
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
||||||
.topic("/product1/device1/child/test/event/temp")
|
.topic("/product1/device1/child/test/event/temp")
|
||||||
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"data\":100}".getBytes()))
|
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"data\":100}"
|
||||||
.build())).blockFirst();
|
.getBytes()))
|
||||||
|
.build())).blockFirst();
|
||||||
|
|
||||||
Assert.assertTrue(message instanceof ChildDeviceMessage);
|
Assert.assertTrue(message instanceof ChildDeviceMessage);
|
||||||
|
|
||||||
|
@ -295,9 +310,10 @@ public class JetLinksMqttDeviceMessageCodecTest {
|
||||||
@Test
|
@Test
|
||||||
public void testPropertiesReport() {
|
public void testPropertiesReport() {
|
||||||
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
||||||
.topic("/product1/device1/properties/report")
|
.topic("/product1/device1/properties/report")
|
||||||
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"properties\":{\"sn\":\"test\"}}".getBytes()))
|
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"properties\":{\"sn\":\"test\"}}"
|
||||||
.build())).blockFirst();
|
.getBytes()))
|
||||||
|
.build())).blockFirst();
|
||||||
|
|
||||||
Assert.assertTrue(message instanceof ReportPropertyMessage);
|
Assert.assertTrue(message instanceof ReportPropertyMessage);
|
||||||
ReportPropertyMessage reply = ((ReportPropertyMessage) message);
|
ReportPropertyMessage reply = ((ReportPropertyMessage) message);
|
||||||
|
@ -311,9 +327,10 @@ public class JetLinksMqttDeviceMessageCodecTest {
|
||||||
@Test
|
@Test
|
||||||
public void testChildPropertiesReport() {
|
public void testChildPropertiesReport() {
|
||||||
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
||||||
.topic("/product1/device1/child/test/properties/report")
|
.topic("/product1/device1/child/test/properties/report")
|
||||||
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"properties\":{\"sn\":\"test\"}}".getBytes()))
|
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"properties\":{\"sn\":\"test\"}}"
|
||||||
.build())).blockFirst();
|
.getBytes()))
|
||||||
|
.build())).blockFirst();
|
||||||
|
|
||||||
Assert.assertTrue(message instanceof ChildDeviceMessage);
|
Assert.assertTrue(message instanceof ChildDeviceMessage);
|
||||||
|
|
||||||
|
@ -328,9 +345,10 @@ public class JetLinksMqttDeviceMessageCodecTest {
|
||||||
@Test
|
@Test
|
||||||
public void testMetadataDerived() {
|
public void testMetadataDerived() {
|
||||||
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
||||||
.topic("/product1/device1/metadata/derived")
|
.topic("/product1/device1/metadata/derived")
|
||||||
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"metadata\":\"1\"}".getBytes()))
|
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"metadata\":\"1\"}"
|
||||||
.build())).blockFirst();
|
.getBytes()))
|
||||||
|
.build())).blockFirst();
|
||||||
|
|
||||||
Assert.assertTrue(message instanceof DerivedMetadataMessage);
|
Assert.assertTrue(message instanceof DerivedMetadataMessage);
|
||||||
DerivedMetadataMessage reply = ((DerivedMetadataMessage) message);
|
DerivedMetadataMessage reply = ((DerivedMetadataMessage) message);
|
||||||
|
@ -342,10 +360,12 @@ public class JetLinksMqttDeviceMessageCodecTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testChildMetadataDerived() {
|
public void testChildMetadataDerived() {
|
||||||
Message message = codec.decode(createMessageContext(SimpleMqttMessage.builder()
|
Message message = codec.decode(createMessageContext(SimpleMqttMessage
|
||||||
.topic("/product1/device1/child/test/metadata/derived")
|
.builder()
|
||||||
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"metadata\":\"1\"}".getBytes()))
|
.topic("/product1/device1/child/test/metadata/derived")
|
||||||
.build())).blockFirst();
|
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\",\"metadata\":\"1\"}"
|
||||||
|
.getBytes()))
|
||||||
|
.build())).blockFirst();
|
||||||
|
|
||||||
Assert.assertTrue(message instanceof ChildDeviceMessage);
|
Assert.assertTrue(message instanceof ChildDeviceMessage);
|
||||||
|
|
||||||
|
@ -356,6 +376,25 @@ public class JetLinksMqttDeviceMessageCodecTest {
|
||||||
System.out.println(reply);
|
System.out.println(reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTimeSync() {
|
||||||
|
Message message = codec.decode(createMessageContext(SimpleMqttMessage
|
||||||
|
.builder()
|
||||||
|
.topic("/product1/device1/time-sync")
|
||||||
|
.payload(Unpooled.wrappedBuffer("{\"messageId\":\"test\"}"
|
||||||
|
.getBytes()))
|
||||||
|
.build()))
|
||||||
|
.blockFirst();
|
||||||
|
|
||||||
|
Assert.assertNull(message);
|
||||||
|
|
||||||
|
Assert.assertNotNull(currentReply);
|
||||||
|
|
||||||
|
Assert.assertEquals(((MqttMessage) currentReply).getTopic(),"/product1/device1/time-sync/reply");
|
||||||
|
Assert.assertTrue(currentReply.payloadAsString().contains("timestamp"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public MessageEncodeContext createMessageContext(Message message) {
|
public MessageEncodeContext createMessageContext(Message message) {
|
||||||
System.out.println(message.toString());
|
System.out.println(message.toString());
|
||||||
return new MessageEncodeContext() {
|
return new MessageEncodeContext() {
|
||||||
|
@ -369,25 +408,99 @@ public class JetLinksMqttDeviceMessageCodecTest {
|
||||||
public DeviceOperator getDevice() {
|
public DeviceOperator getDevice() {
|
||||||
return registry.getDevice("device1").block();
|
return registry.getDevice("device1").block();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<DeviceOperator> getDevice(String deviceId) {
|
||||||
|
return registry.getDevice(deviceId);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public MessageDecodeContext createMessageContext(EncodedMessage message) {
|
public MessageDecodeContext createMessageContext(EncodedMessage message) {
|
||||||
System.out.println(message.toString());
|
System.out.println(message.toString());
|
||||||
return new MessageDecodeContext() {
|
return new FromDeviceMessageContext() {
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public EncodedMessage getMessage() {
|
public EncodedMessage getMessage() {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeviceSession getSession() {
|
||||||
|
return new MockSession();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DeviceOperator getDevice() {
|
public DeviceOperator getDevice() {
|
||||||
return registry.getDevice("device1").block();
|
return registry.getDevice("device1").block();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<DeviceOperator> getDevice(String deviceId) {
|
||||||
|
return registry.getDevice(deviceId);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MockSession implements DeviceSession {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "device1";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDeviceId() {
|
||||||
|
return "device1";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public DeviceOperator getOperator() {
|
||||||
|
return registry.getDevice("device1").block();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long lastPingTime() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long connectTime() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> send(EncodedMessage encodedMessage) {
|
||||||
|
currentReply = encodedMessage;
|
||||||
|
return Mono.just(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Transport getTransport() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void ping() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAlive() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose(Runnable call) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue