Browse Source

Merge 5c8030732a into b390ebe579

pull/46/merge
Vincent Chan 1 year ago committed by GitHub
parent
commit
40a9a72741
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      src/main/java/com/dji/sample/component/ApplicationBootInitial.java
  2. 2
      src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java
  3. 3
      src/main/java/com/dji/sample/control/service/impl/ControlServiceImpl.java
  4. 3
      src/main/java/com/dji/sample/control/service/impl/DrcServiceImpl.java
  5. 3
      src/main/java/com/dji/sample/manage/service/impl/DeviceLogsServiceImpl.java
  6. 3
      src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java
  7. 3
      src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java
  8. 3
      src/main/java/com/dji/sample/manage/service/impl/SDKDeviceService.java
  9. 3
      src/main/java/com/dji/sample/wayline/service/impl/FlightTaskServiceImpl.java
  10. 2
      src/main/java/com/dji/sdk/cloudapi/control/FlyToPointRequest.java
  11. 2
      src/main/java/com/dji/sdk/cloudapi/control/TakeoffToPointRequest.java
  12. 7
      src/main/java/com/dji/sdk/cloudapi/debug/RemoteDebugStepKeyEnum.java
  13. 2
      src/main/java/com/dji/sdk/cloudapi/device/CameraModeEnum.java
  14. 3
      src/main/java/com/dji/sdk/cloudapi/device/DockLiveErrorStatus.java
  15. 5
      src/main/java/com/dji/sdk/cloudapi/device/DroneModeCodeEnum.java
  16. 4
      src/main/java/com/dji/sdk/cloudapi/device/api/AbstractDeviceService.java
  17. 3
      src/main/java/com/dji/sdk/cloudapi/media/UploadFlighttaskMediaPrioritize.java
  18. 11
      src/main/java/com/dji/sdk/cloudapi/wayline/ExecutionStepEnum.java
  19. 20
      src/main/java/com/dji/sdk/cloudapi/wayline/FlighttaskBreakPoint.java
  20. 5
      src/main/java/com/dji/sdk/cloudapi/wayline/FlighttaskExecuteRequest.java
  21. 3
      src/main/java/com/dji/sdk/cloudapi/wayline/FlighttaskPrepareRequest.java
  22. 5
      src/main/java/com/dji/sdk/cloudapi/wayline/FlighttaskUndoRequest.java
  23. 89
      src/main/java/com/dji/sdk/common/JDKLockBarrierImpl.java
  24. 65
      src/main/java/com/dji/sdk/common/LocalCacheSDKManager.java
  25. 26
      src/main/java/com/dji/sdk/common/PublishBarrier.java
  26. 41
      src/main/java/com/dji/sdk/common/PublishBarrierResult.java
  27. 88
      src/main/java/com/dji/sdk/common/PublishConfiguration.java
  28. 57
      src/main/java/com/dji/sdk/common/PublishOption.java
  29. 20
      src/main/java/com/dji/sdk/common/PublishRequest.java
  30. 17
      src/main/java/com/dji/sdk/common/PublishResult.java
  31. 17
      src/main/java/com/dji/sdk/common/ReadonlyPublishConfiguration.java
  32. 36
      src/main/java/com/dji/sdk/common/SDKManager.java
  33. 80
      src/main/java/com/dji/sdk/config/DefaultBeanConfiguration.java
  34. 53
      src/main/java/com/dji/sdk/mqtt/ChanBarrierAdapter.java
  35. 51
      src/main/java/com/dji/sdk/mqtt/FlowTransformWrapper.java
  36. 24
      src/main/java/com/dji/sdk/mqtt/GlobalPublishOption.java
  37. 3
      src/main/java/com/dji/sdk/mqtt/InboundMessageRouter.java
  38. 100
      src/main/java/com/dji/sdk/mqtt/MqttGatewayPublish.java
  39. 21
      src/main/java/com/dji/sdk/mqtt/osd/OsdRouter.java
  40. 7
      src/main/java/com/dji/sdk/mqtt/osd/OsdSubscribe.java
  41. 34
      src/main/java/com/dji/sdk/mqtt/property/PropertySetPublish.java
  42. 17
      src/main/java/com/dji/sdk/mqtt/property/PropertySetReplyHandler.java
  43. 44
      src/main/java/com/dji/sdk/mqtt/services/ServicesPublish.java
  44. 17
      src/main/java/com/dji/sdk/mqtt/services/ServicesReplyHandler.java
  45. 8
      src/main/java/com/dji/sdk/mqtt/state/DockStateDataKeyEnum.java
  46. 7
      src/main/java/com/dji/sdk/mqtt/state/RcStateDataKeyEnum.java
  47. 62
      src/main/java/com/dji/sdk/mqtt/state/StateRouter.java
  48. 7
      src/main/java/com/dji/sdk/mqtt/state/StateSubscribe.java
  49. 7
      src/main/java/com/dji/sdk/mqtt/status/StatusSubscribe.java

6
src/main/java/com/dji/sample/component/ApplicationBootInitial.java

@ -11,6 +11,7 @@ import org.springframework.beans.factory.annotation.Autowired; @@ -11,6 +11,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Optional;
/**
@ -27,6 +28,9 @@ public class ApplicationBootInitial implements CommandLineRunner { @@ -27,6 +28,9 @@ public class ApplicationBootInitial implements CommandLineRunner {
@Autowired
private IDeviceRedisService deviceRedisService;
@Resource
SDKManager sdkManager;
/**
* Subscribe to the devices that exist in the redis when the program starts,
* to prevent the data from being different from the pilot side due to program interruptions.
@ -44,7 +48,7 @@ public class ApplicationBootInitial implements CommandLineRunner { @@ -44,7 +48,7 @@ public class ApplicationBootInitial implements CommandLineRunner {
.map(Optional::get)
.filter(device -> DeviceDomainEnum.DRONE != device.getDomain())
.forEach(device -> deviceService.subDeviceOnlineSubscribeTopic(
SDKManager.registerDevice(device.getDeviceSn(), device.getChildDeviceSn(), device.getDomain(),
sdkManager.registerDevice(device.getDeviceSn(), device.getChildDeviceSn(), device.getDomain(),
device.getType(), device.getSubType(), device.getThingVersion(),
deviceRedisService.getDeviceOnline(device.getChildDeviceSn()).map(DeviceDTO::getThingVersion).orElse(null))));

2
src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java

@ -86,6 +86,7 @@ public class AliyunOssServiceImpl implements IOssService { @@ -86,6 +86,7 @@ public class AliyunOssServiceImpl implements IOssService {
@Override
public InputStream getObject(String bucket, String objectKey) {
//bug: ossClient.getObject -> OSSObject need to close manually. otherwise will hang up main thread to wait available connection. by witcom @2023.12.08
return ossClient.getObject(bucket, objectKey).getObjectContent();
}
@ -94,6 +95,7 @@ public class AliyunOssServiceImpl implements IOssService { @@ -94,6 +95,7 @@ public class AliyunOssServiceImpl implements IOssService {
if (ossClient.doesObjectExist(bucket, objectKey)) {
throw new RuntimeException("The filename already exists.");
}
//bug: PutObjectResult need to close manually. otherwise will hang up main thread to wait available connection. by witcom @2023.12.08
PutObjectResult objectResult = ossClient.putObject(new PutObjectRequest(bucket, objectKey, input, new ObjectMetadata()));
log.info("Upload FlighttaskCreateFile: {}", objectResult.getETag());
}

3
src/main/java/com/dji/sample/control/service/impl/ControlServiceImpl.java

@ -68,6 +68,9 @@ public class ControlServiceImpl implements IControlService { @@ -68,6 +68,9 @@ public class ControlServiceImpl implements IControlService {
@Qualifier("SDKWaylineService")
private AbstractWaylineService abstractWaylineService;
@Autowired
SDKManager SDKManager;
private RemoteDebugHandler checkDebugCondition(String sn, RemoteDebugParam param, RemoteDebugMethodEnum controlMethodEnum) {
RemoteDebugHandler handler = Objects.nonNull(controlMethodEnum.getClazz()) ?
mapper.convertValue(Objects.nonNull(param) ? param : new Object(), controlMethodEnum.getClazz())

3
src/main/java/com/dji/sample/control/service/impl/DrcServiceImpl.java

@ -84,6 +84,9 @@ public class DrcServiceImpl implements IDrcService { @@ -84,6 +84,9 @@ public class DrcServiceImpl implements IDrcService {
@Autowired
private AbstractControlService abstractControlService;
@Autowired
SDKManager SDKManager;
@Override
public void setDrcModeInRedis(String dockSn, String clientId) {
RedisOpsUtils.setWithExpire(RedisConst.DRC_PREFIX + dockSn, clientId, RedisConst.DRC_MODE_ALIVE_SECOND);

3
src/main/java/com/dji/sample/manage/service/impl/DeviceLogsServiceImpl.java

@ -85,6 +85,9 @@ public class DeviceLogsServiceImpl extends AbstractLogService implements IDevice @@ -85,6 +85,9 @@ public class DeviceLogsServiceImpl extends AbstractLogService implements IDevice
@Autowired
private AbstractLogService abstractLogService;
@Autowired
SDKManager SDKManager;
@Override
public PaginationData<DeviceLogsDTO> getUploadedLogs(String deviceSn, DeviceLogsQueryParam param) {
LambdaQueryWrapper<DeviceLogsEntity> queryWrapper = new LambdaQueryWrapper<DeviceLogsEntity>()

3
src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java

@ -124,6 +124,9 @@ public class DeviceServiceImpl implements IDeviceService { @@ -124,6 +124,9 @@ public class DeviceServiceImpl implements IDeviceService {
@Autowired
private AbstractFirmwareService abstractFirmwareService;
@Autowired
SDKManager SDKManager;
@Override
public void subDeviceOffline(String deviceSn) {
// If no information about this device exists in the cache, the drone is considered to be offline.

3
src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java

@ -46,6 +46,9 @@ public class LiveStreamServiceImpl implements ILiveStreamService { @@ -46,6 +46,9 @@ public class LiveStreamServiceImpl implements ILiveStreamService {
@Autowired
private AbstractLivestreamService abstractLivestreamService;
@Autowired
SDKManager SDKManager;
@Override
public List<CapacityDeviceDTO> getLiveCapacity(String workspaceId) {

3
src/main/java/com/dji/sample/manage/service/impl/SDKDeviceService.java

@ -57,6 +57,9 @@ public class SDKDeviceService extends AbstractDeviceService { @@ -57,6 +57,9 @@ public class SDKDeviceService extends AbstractDeviceService {
@Autowired
private IDevicePayloadService devicePayloadService;
@Autowired
SDKManager SDKManager;
@Override
public TopicStatusResponse<MqttReply> updateTopoOnline(TopicStatusRequest<UpdateTopo> request, MessageHeaders headers) {
UpdateTopoSubDevice updateTopoSubDevice = request.getData().getSubDevices().get(0);

3
src/main/java/com/dji/sample/wayline/service/impl/FlightTaskServiceImpl.java

@ -62,6 +62,9 @@ public class FlightTaskServiceImpl extends AbstractWaylineService implements IFl @@ -62,6 +62,9 @@ public class FlightTaskServiceImpl extends AbstractWaylineService implements IFl
@Autowired
private ObjectMapper mapper;
@Autowired
SDKManager SDKManager;
@Autowired
private IWebSocketMessageService websocketMessageService;

2
src/main/java/com/dji/sdk/cloudapi/control/FlyToPointRequest.java

@ -13,7 +13,7 @@ import java.util.List; @@ -13,7 +13,7 @@ import java.util.List;
*/
public class FlyToPointRequest extends BaseModel {
@Pattern(regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
//@Pattern(regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
@NotNull
private String flyToId;

2
src/main/java/com/dji/sdk/cloudapi/control/TakeoffToPointRequest.java

@ -19,7 +19,7 @@ import javax.validation.constraints.Pattern; @@ -19,7 +19,7 @@ import javax.validation.constraints.Pattern;
*/
public class TakeoffToPointRequest extends BaseModel {
@Pattern(regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
//@Pattern(regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
@NotNull
private String flightId;

7
src/main/java/com/dji/sdk/cloudapi/debug/RemoteDebugStepKeyEnum.java

@ -59,7 +59,9 @@ public enum RemoteDebugStepKeyEnum { @@ -59,7 +59,9 @@ public enum RemoteDebugStepKeyEnum {
FREE_PUTTER("free_putter", "Free Putter"),
STOP_CHARGE("stop_charge", "Stop charging");
STOP_CHARGE("stop_charge", "Stop charging"),
UNKNOWN("unknown","Unknown");
private final String stepKey;
@ -79,10 +81,11 @@ public enum RemoteDebugStepKeyEnum { @@ -79,10 +81,11 @@ public enum RemoteDebugStepKeyEnum {
return message;
}
//fix: 提供unknown取代异常 witcom@2023.10.30
@JsonCreator
public static RemoteDebugStepKeyEnum find(String stepKey) {
return Arrays.stream(values()).filter(stepKeyEnum -> stepKeyEnum.stepKey.equals(stepKey)).findAny()
.orElseThrow(() -> new CloudSDKException(RemoteDebugStepKeyEnum.class,stepKey));
.orElse(UNKNOWN);
}
}

2
src/main/java/com/dji/sdk/cloudapi/device/CameraModeEnum.java

@ -13,6 +13,8 @@ import java.util.Arrays; @@ -13,6 +13,8 @@ import java.util.Arrays;
*/
public enum CameraModeEnum {
//fix: Cannot construct instance of `com.dji.sdk.cloudapi.device.CameraModeEnum`, problem: com.dji.sdk.cloudapi.device.CameraModeEnum has unknown data: [-1] vincent @ 2023.12.07
UNKNOWN(-1),
PHOTO(0),
VIDEO(1);

3
src/main/java/com/dji/sdk/cloudapi/device/DockLiveErrorStatus.java

@ -40,11 +40,14 @@ public class DockLiveErrorStatus { @@ -40,11 +40,14 @@ public class DockLiveErrorStatus {
}
public String getMessage() {
if(success){ return "success";}
return errorCode.getMessage();
}
@JsonValue
public Integer getCode() {
//witcom: errorCode.getCode() will cause npe 2023.10.30
if(success){ return 0; }
return source.getSource() * MOD + errorCode.getCode();
}

5
src/main/java/com/dji/sdk/cloudapi/device/DroneModeCodeEnum.java

@ -45,7 +45,10 @@ public enum DroneModeCodeEnum { @@ -45,7 +45,10 @@ public enum DroneModeCodeEnum {
APAS(15),
VIRTUAL_JOYSTICK(16);
VIRTUAL_JOYSTICK(16),
//witcom: 飞机报这个号上来报异常
UNKNOWN_1(17);
private final int code;

4
src/main/java/com/dji/sdk/cloudapi/device/api/AbstractDeviceService.java

@ -169,7 +169,9 @@ public class AbstractDeviceService { @@ -169,7 +169,9 @@ public class AbstractDeviceService {
* @param request data
* @param headers The headers for a {@link Message}.
*/
@ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_DOCK_WPMZ_VERSION)
//witcom: wrong ChannelName. is INBOUND_STATE_DOCK_PAYLOAD??
// @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_DOCK_WPMZ_VERSION)
@ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_DOCK_PAYLOAD)
public void dockPayload(TopicStateRequest<DockPayload> request, MessageHeaders headers) {
throw new UnsupportedOperationException("dockPayload not implemented");
}

3
src/main/java/com/dji/sdk/cloudapi/media/UploadFlighttaskMediaPrioritize.java

@ -12,8 +12,9 @@ import javax.validation.constraints.Pattern; @@ -12,8 +12,9 @@ import javax.validation.constraints.Pattern;
*/
public class UploadFlighttaskMediaPrioritize extends BaseModel {
/* fix: flightId是应用层提供的,应用层有自己的id构建规则,如果不是设备原因必须指定格式,最好就不要控制flightId的格式了 by witcom@2023.09.22 */
@NotNull
@Pattern(regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
//@Pattern(regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
private String flightId;
public UploadFlighttaskMediaPrioritize() {

11
src/main/java/com/dji/sdk/cloudapi/wayline/ExecutionStepEnum.java

@ -52,15 +52,16 @@ public enum ExecutionStepEnum { @@ -52,15 +52,16 @@ public enum ExecutionStepEnum {
return msg;
}
//witcom: 这个函数跑不进来,跑了下面的函数,导致FlightTaskProgress报不上来
@JsonCreator
public static ExecutionStepEnum find(int step) {
return Arrays.stream(values()).filter(stepEnum -> stepEnum.min <= step && stepEnum.max >= step).findAny()
.orElseThrow(() -> new CloudSDKException(ExecutionStepEnum.class, step));
}
@JsonCreator
public static ExecutionStepEnum find(String msg) {
return Arrays.stream(values()).filter(stepEnum -> stepEnum.msg.equals(msg)).findAny()
.orElseThrow(() -> new CloudSDKException(ExecutionStepEnum.class, msg));
}
// @JsonCreator
// public static ExecutionStepEnum find(String msg) {
// return Arrays.stream(values()).filter(stepEnum -> stepEnum.msg.equals(msg)).findAny()
// .orElseThrow(() -> new CloudSDKException(ExecutionStepEnum.class, msg));
// }
}

20
src/main/java/com/dji/sdk/cloudapi/wayline/FlighttaskBreakPoint.java

@ -24,12 +24,10 @@ public class FlighttaskBreakPoint { @@ -24,12 +24,10 @@ public class FlighttaskBreakPoint {
@NotNull
private BreakpointStateEnum state;
/**
* Current wayline segment process
*/
@NotNull
@Min(0)
@Max(1)
// @NotNull
// @Min(0)
// @Max(1)
private Float progress;
/**
@ -37,10 +35,14 @@ public class FlighttaskBreakPoint { @@ -37,10 +35,14 @@ public class FlighttaskBreakPoint {
*/
private Integer waylineID;
/**
* Current wayline segment process
* bug: Cannot deserialize value of type `com.dji.sdk.cloudapi.wayline.FlighttaskBreakReasonEnum` from number 1281: index value outside legal index range [0..-1] mark by witcom 2023.11.09
*/
/**
* Break reason
*/
private FlighttaskBreakReasonEnum breakReason;
private Integer breakReason;
/**
* Breakpoint latitude
@ -116,11 +118,11 @@ public class FlighttaskBreakPoint { @@ -116,11 +118,11 @@ public class FlighttaskBreakPoint {
return this;
}
public FlighttaskBreakReasonEnum getBreakReason() {
public Integer getBreakReason() {
return breakReason;
}
public FlighttaskBreakPoint setBreakReason(FlighttaskBreakReasonEnum breakReason) {
public FlighttaskBreakPoint setBreakReason(Integer breakReason) {
this.breakReason = breakReason;
return this;
}

5
src/main/java/com/dji/sdk/cloudapi/wayline/FlighttaskExecuteRequest.java

@ -12,8 +12,11 @@ import javax.validation.constraints.Pattern; @@ -12,8 +12,11 @@ import javax.validation.constraints.Pattern;
*/
public class FlighttaskExecuteRequest extends BaseModel {
/**
* fix: flightId是应用层提供的,应用层有自己的id构建规则,如果不是设备原因必须指定格式,最好就不要控制flightId的格式了 by witcom@2023.09.22
*/
@NotNull
@Pattern(regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
//@Pattern(regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
private String flightId;
public FlighttaskExecuteRequest() {

3
src/main/java/com/dji/sdk/cloudapi/wayline/FlighttaskPrepareRequest.java

@ -20,9 +20,10 @@ public class FlighttaskPrepareRequest extends BaseModel { @@ -20,9 +20,10 @@ public class FlighttaskPrepareRequest extends BaseModel {
/**
* Task ID
* fix: flightId是应用层提供的,应用层有自己的id构建规则,如果不是设备原因必须指定格式,最好就不要控制flightId的格式了 by witcom@2023.09.22
*/
@NotNull
@Pattern(regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
//@Pattern(regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
private String flightId;
/**

5
src/main/java/com/dji/sdk/cloudapi/wayline/FlighttaskUndoRequest.java

@ -14,9 +14,12 @@ import java.util.List; @@ -14,9 +14,12 @@ import java.util.List;
*/
public class FlighttaskUndoRequest extends BaseModel {
/**
* fix: flightId是应用层提供的,应用层有自己的id构建规则,如果不是设备原因必须指定格式,最好就不要控制flightId的格式了 by witcom@2023.09.22
*/
@NotNull
@Size(min = 1)
private List<@Pattern(regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$") String> flightIds;
private List</*@Pattern(regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$") */String> flightIds;
public FlighttaskUndoRequest() {
}

89
src/main/java/com/dji/sdk/common/JDKLockBarrierImpl.java

@ -0,0 +1,89 @@ @@ -0,0 +1,89 @@
/*************************************************
* @copyright 2017 Flision Corporation Inc.
* @author: Vincent Chan @ Canton
* @date: 2023年09月22日
* @version: 1.0.0
* @description:
**************************************************/
package com.dji.sdk.common;
import com.dji.sdk.mqtt.Chan;
import com.dji.sdk.mqtt.CommonTopicRequest;
import com.dji.sdk.mqtt.CommonTopicResponse;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
public class JDKLockBarrierImpl implements PublishBarrier{
/**
* 在我的实现中是采用一个定期清理的TimedCache储存请求
*/
private final ConcurrentHashMap<String, JDKHolder> container = new ConcurrentHashMap<>();
@Override
public String generateIdentity(CommonTopicRequest requestData) {
return requestData.getTid();
}
@Override
public String generateIdentity(CommonTopicResponse receiveData) {
return receiveData.getTid();
}
@Override
public void put(String identity, CommonTopicResponse receiveData) {
if(hasIdentity(identity)){
container.get(identity).setData(receiveData);
}
}
@Override
public void registerRequest(String identity, CommonTopicRequest requestData) {
container.put(identity,new JDKHolder());
}
@Override
public PublishBarrierResult await(String identity, long timeout) {
JDKHolder jdkHolder = container.get(identity);
if(Objects.isNull(jdkHolder)){
throw new RuntimeException("等待指令返回前未注册指令到栅栏");
}
jdkHolder.await(timeout);
return jdkHolder.getResult();
}
@Override
public boolean hasIdentity(String identity) {
return container.containsKey(identity);
}
public static class JDKHolder{
volatile Object locker = new Object();
CommonTopicResponse response = null;
public void await(long timeout) {
synchronized (locker){
try {
locker.wait(timeout);
}catch (InterruptedException e){}
}
}
public void setData(CommonTopicResponse receiveData) {
this.response = receiveData;
synchronized (locker){
locker.notify();
}
}
public PublishBarrierResult getResult() {
if(Objects.nonNull(response)){
return PublishBarrierResult.ok(response);
}else{
return PublishBarrierResult.EMPTY;
}
}
}
}

65
src/main/java/com/dji/sdk/common/LocalCacheSDKManager.java

@ -0,0 +1,65 @@ @@ -0,0 +1,65 @@
/*************************************************
* @copyright 2017 Flision Corporation Inc.
* @author: Vincent Chan @ Canton
* @date: 2023年09月25日
* @version: 1.0.0
* @description:
**************************************************/
package com.dji.sdk.common;
import com.dji.sdk.cloudapi.device.DeviceDomainEnum;
import com.dji.sdk.cloudapi.device.DeviceEnum;
import com.dji.sdk.cloudapi.device.DeviceSubTypeEnum;
import com.dji.sdk.cloudapi.device.DeviceTypeEnum;
import com.dji.sdk.exception.CloudSDKErrorEnum;
import com.dji.sdk.exception.CloudSDKException;
import org.springframework.stereotype.Component;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class LocalCacheSDKManager implements SDKManager{
final ConcurrentHashMap<String, GatewayManager> SDK_MAP = new ConcurrentHashMap<>(16);
@Override
public GatewayManager getDeviceSDK(String gatewaySn) {
if (SDK_MAP.containsKey(gatewaySn)) {
return SDK_MAP.get(gatewaySn);
}
throw new CloudSDKException(CloudSDKErrorEnum.NOT_REGISTERED,
"The device has not been registered, please call the 'SDKManager.registerDevice()' method to register the device first.");
}
@Override
public Optional<GatewayManager> findDeviceSDK(String gatewaySn) {
if(SDK_MAP.containsKey(gatewaySn)){
return Optional.of(SDK_MAP.get(gatewaySn));
}else {
return Optional.empty();
}
}
@Override
public GatewayManager registerDevice(String gatewaySn, String droneSn, DeviceDomainEnum domain, DeviceTypeEnum type, DeviceSubTypeEnum subType, String gatewayThingVersion, String droneThingVersion) {
return registerDevice(gatewaySn, droneSn, GatewayTypeEnum.find(DeviceEnum.find(domain, type, subType)), gatewayThingVersion, droneThingVersion);
}
@Override
public GatewayManager registerDevice(String gatewaySn, String droneSn, GatewayTypeEnum type, String gatewayThingVersion, String droneThingVersion) {
return registerDevice(new GatewayManager(Objects.requireNonNull(gatewaySn), droneSn, type, gatewayThingVersion, droneThingVersion));
}
@Override
public GatewayManager registerDevice(GatewayManager gateway) {
SDK_MAP.put(gateway.getGatewaySn(), gateway);
return gateway;
}
@Override
public void logoutDevice(String gatewaySn) {
SDK_MAP.remove(gatewaySn);
}
}

26
src/main/java/com/dji/sdk/common/PublishBarrier.java

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
/*************************************************
* @copyright 2017 Flision Corporation Inc.
* @author: Vincent Chan @ Canton
* @date: 2023年09月22日
* @version: 1.0.0
* @description:
**************************************************/
package com.dji.sdk.common;
import com.dji.sdk.mqtt.CommonTopicRequest;
import com.dji.sdk.mqtt.CommonTopicResponse;
public interface PublishBarrier {
//构建栅栏标识方法
String generateIdentity(CommonTopicRequest requestData);
String generateIdentity(CommonTopicResponse receiveData);
void put(String identity, CommonTopicResponse receiveData);
void registerRequest(String identity, CommonTopicRequest requestData);
PublishBarrierResult await(String identity,long timeout);
boolean hasIdentity(String identity);
}

41
src/main/java/com/dji/sdk/common/PublishBarrierResult.java

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
/*************************************************
* @copyright 2017 Flision Corporation Inc.
* @author: Vincent Chan @ Canton
* @date: 2023年09月22日
* @version: 1.0.0
* @description:
**************************************************/
package com.dji.sdk.common;
import com.dji.sdk.mqtt.CommonTopicResponse;
public class PublishBarrierResult implements PublishResult {
public static PublishBarrierResult EMPTY = new PublishBarrierResult();
public static PublishBarrierResult ok(CommonTopicResponse data){
return new PublishBarrierResult().setData(data);
}
boolean timeout = true;
CommonTopicResponse data;
private PublishBarrierResult() {
}
private PublishBarrierResult setData(CommonTopicResponse data) {
this.data = data;
this.timeout = false;
return this;
}
public boolean isTimeout(){
return timeout;
}
public CommonTopicResponse getData(){
return data;
}
}

88
src/main/java/com/dji/sdk/common/PublishConfiguration.java

@ -0,0 +1,88 @@ @@ -0,0 +1,88 @@
/*************************************************
* @copyright 2017 Flision Corporation Inc.
* @author: Vincent Chan @ Canton
* @date: 2023年09月22日
* @version: 1.0.0
* @description:
**************************************************/
package com.dji.sdk.common;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class PublishConfiguration implements ReadonlyPublishConfiguration {
String bid;
String tid;
//默认超时
int timeout = 3;
//请求发送前调用
Consumer<PublishRequest> beforePublishHook = null;
//收到请求回信后调用
BiConsumer<PublishRequest, PublishBarrierResult> afterPublishHook = null;
public String getBid() {
return bid;
}
public String getTid() {
return tid;
}
public long getTimeout() {
return timeout * 1000;
}
public void setBizId(String bid) {
this.bid = bid;
}
public void setTransactionId(String tid) {
this.tid = tid;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public void setBeforePublishHook(Consumer<PublishRequest> callback) {
beforePublishHook = callback;
}
public void setAfterPublishReplyHook(BiConsumer<PublishRequest, PublishBarrierResult> callback) {
afterPublishHook = callback;
}
public void invokeBeforePublishHook(PublishRequest req){
if(Objects.nonNull(beforePublishHook)){
try {
beforePublishHook.accept(req);
}catch (Throwable ex){
//do nothing
//业务层的异常不理会
}
}
}
public void invokeAfterPublishReplyHook(PublishRequest req, PublishBarrierResult result){
if(Objects.nonNull(afterPublishHook)){
try{
afterPublishHook.accept(req,result);
}catch (Throwable ex){
//do nothing
//业务层的异常不理会
}
}
}
public boolean noneBeforePublishHook() {
return Objects.isNull(beforePublishHook);
}
public boolean noneAfterPublishHook() {
return Objects.isNull(afterPublishHook);
}
}

57
src/main/java/com/dji/sdk/common/PublishOption.java

@ -0,0 +1,57 @@ @@ -0,0 +1,57 @@
/*************************************************
* @copyright 2017 Flision Corporation Inc.
* @author: Vincent Chan @ Canton
* @date: 2023年09月22日
* @version: 1.0.0
* @description:
**************************************************/
package com.dji.sdk.common;
import com.google.common.base.Strings;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class PublishOption {
public static Consumer<PublishOption> DEFAULT = (cfg)->{};
final PublishConfiguration configuration;
public PublishOption(PublishConfiguration configuration) {
this.configuration = configuration;
}
public PublishOption withBizId(String bid){
if(!Strings.isNullOrEmpty(bid)){
configuration.setBizId(bid);
}
return this;
}
public PublishOption withTransactionId(String tid){
if(!Strings.isNullOrEmpty(tid)){
configuration.setTransactionId(tid);
}
return this;
}
public PublishOption timeout(int second){
configuration.setTimeout(second);
return this;
}
public PublishOption beforePublish(Consumer<PublishRequest> callback){
if(Objects.nonNull(callback)){
configuration.setBeforePublishHook(callback);
}
return this;
}
public PublishOption afterPublishReply(BiConsumer<PublishRequest, PublishBarrierResult> callback){
if(Objects.nonNull(callback)){
configuration.setAfterPublishReplyHook(callback);
}
return this;
}
}

20
src/main/java/com/dji/sdk/common/PublishRequest.java

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
/*************************************************
* @copyright 2017 Flision Corporation Inc.
* @author: Vincent Chan @ Canton
* @date: 2023年09月25日
* @version: 1.0.0
* @description:
**************************************************/
package com.dji.sdk.common;
import com.dji.sdk.common.ReadonlyPublishConfiguration;
import com.dji.sdk.mqtt.CommonTopicRequest;
public interface PublishRequest {
String getTopic();
CommonTopicRequest getOriginRequest();
ReadonlyPublishConfiguration getConfiguration();
}

17
src/main/java/com/dji/sdk/common/PublishResult.java

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
/*************************************************
* @copyright 2017 Flision Corporation Inc.
* @author: Vincent Chan @ Canton
* @date: 2023年09月25日
* @version: 1.0.0
* @description:
**************************************************/
package com.dji.sdk.common;
import com.dji.sdk.mqtt.CommonTopicResponse;
public interface PublishResult {
boolean isTimeout();
CommonTopicResponse getData();
}

17
src/main/java/com/dji/sdk/common/ReadonlyPublishConfiguration.java

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
/*************************************************
* @copyright 2017 Flision Corporation Inc.
* @author: Vincent Chan @ Canton
* @date: 2023年09月25日
* @version: 1.0.0
* @description:
**************************************************/
package com.dji.sdk.common;
public interface ReadonlyPublishConfiguration {
String getBid();
String getTid();
long getTimeout();
}

36
src/main/java/com/dji/sdk/common/SDKManager.java

@ -8,15 +8,39 @@ import com.dji.sdk.exception.CloudSDKErrorEnum; @@ -8,15 +8,39 @@ import com.dji.sdk.exception.CloudSDKErrorEnum;
import com.dji.sdk.exception.CloudSDKException;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author sean
* @version 1.7
* @date 2023/5/19
*
* fix: 改接口,由客户决定Gateway管理策略 witcom@2023.09.25
*/
public class SDKManager {
public interface SDKManager {
default GatewayManager getDeviceSDK(String gatewaySn){
return findDeviceSDK(gatewaySn)
.orElseThrow(()-> new CloudSDKException(CloudSDKErrorEnum.NOT_REGISTERED,
"The device has not been registered, please call the 'SDKManager.registerDevice()' method to register the device first."));
}
Optional<GatewayManager> findDeviceSDK(String gatewaySn);
default GatewayManager registerDevice(String gatewaySn, String droneSn,
DeviceDomainEnum domain, DeviceTypeEnum type, DeviceSubTypeEnum subType, String gatewayThingVersion, String droneThingVersion){
return registerDevice(gatewaySn, droneSn, GatewayTypeEnum.find(DeviceEnum.find(domain, type, subType)), gatewayThingVersion, droneThingVersion);
}
default GatewayManager registerDevice(String gatewaySn, String droneSn, GatewayTypeEnum type, String gatewayThingVersion, String droneThingVersion){
return registerDevice(new GatewayManager(Objects.requireNonNull(gatewaySn), droneSn, type, gatewayThingVersion, droneThingVersion));
}
GatewayManager registerDevice(GatewayManager gateway);
void logoutDevice(String gatewaySn);
/*
private SDKManager() {
}
@ -30,6 +54,14 @@ public class SDKManager { @@ -30,6 +54,14 @@ public class SDKManager {
"The device has not been registered, please call the 'SDKManager.registerDevice()' method to register the device first.");
}
public static Optional<GatewayManager> findDeviceSDK(String gatewaySn) {
if(SDK_MAP.containsKey(gatewaySn)){
return Optional.of(SDK_MAP.get(gatewaySn));
}else {
return Optional.empty();
}
}
public static GatewayManager registerDevice(String gatewaySn, String droneSn,
DeviceDomainEnum domain, DeviceTypeEnum type, DeviceSubTypeEnum subType, String gatewayThingVersion, String droneThingVersion) {
return registerDevice(gatewaySn, droneSn, GatewayTypeEnum.find(DeviceEnum.find(domain, type, subType)), gatewayThingVersion, droneThingVersion);
@ -47,4 +79,6 @@ public class SDKManager { @@ -47,4 +79,6 @@ public class SDKManager {
public static void logoutDevice(String gatewaySn) {
SDK_MAP.remove(gatewaySn);
}
*/
}

80
src/main/java/com/dji/sdk/config/DefaultBeanConfiguration.java

@ -0,0 +1,80 @@ @@ -0,0 +1,80 @@
/*************************************************
* @copyright 2017 Flision Corporation Inc.
* @author: Vincent Chan @ Canton
* @date: 2023年09月22日
* @version: 1.0.0
* @description:
**************************************************/
package com.dji.sdk.config;
import com.dji.sdk.common.*;
import com.dji.sdk.mqtt.ChanBarrierAdapter;
import com.dji.sdk.mqtt.GlobalPublishOption;
import com.dji.sdk.common.PublishRequest;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
@Configuration
public class DefaultBeanConfiguration {
/**
* 全局发送默认设置
* @return
*/
@Bean
@ConditionalOnMissingBean(GlobalPublishOption.class)
public GlobalPublishOption defaultPublishOption(){
return new GlobalPublishOption() {
@Override
public Supplier<String> defaultTransactionId() {
return ()-> UUID.randomUUID().toString();
}
@Override
public Supplier<String> defaultBizId() {
return ()-> UUID.randomUUID().toString();
}
@Override
public Consumer<PublishRequest> defaultBeforePublishHook() {
return null;
}
@Override
public BiConsumer<PublishRequest, PublishBarrierResult> defaultAfterPublishHook() {
return null;
}
};
}
@Bean
@ConditionalOnMissingBean(SDKManager.class)
public SDKManager localCacheSDKManager(){
return new LocalCacheSDKManager();
}
/**
* 使用者可以自定义PublishBarrier的实现默认采用Chan实现
*/
@Bean
@ConditionalOnMissingBean(PublishBarrier.class)
public PublishBarrier chanBarrier(){
/** 原Chan实现 */
return new ChanBarrierAdapter();
}
/**
* PublishBarrier 另一个实现, 采用同步锁
*/
// @Bean
// @ConditionalOnMissingBean(PublishBarrier.class)
// public PublishBarrier jdkBarrier(){
// return new JDKLockBarrierImpl();
// }
}

53
src/main/java/com/dji/sdk/mqtt/ChanBarrierAdapter.java

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
/*************************************************
* @copyright 2017 Flision Corporation Inc.
* @author: Vincent Chan @ Canton
* @date: 2023年09月22日
* @version: 1.0.0
* @description:
**************************************************/
package com.dji.sdk.mqtt;
import com.dji.sdk.common.PublishBarrier;
import com.dji.sdk.common.PublishBarrierResult;
import java.util.Objects;
public class ChanBarrierAdapter implements PublishBarrier {
@Override
public String generateIdentity(CommonTopicRequest requestData) {
return requestData.getTid();
}
@Override
public String generateIdentity(CommonTopicResponse receiveData) {
return receiveData.getTid();
}
@Override
public void put(String identity, CommonTopicResponse receiveData) {
Chan instance = Chan.getInstance(identity, false);
if(Objects.nonNull(instance)){
instance.put(receiveData);
}
}
@Override
public void registerRequest(String identity, CommonTopicRequest requestData) {
Chan.getInstance(identity, true);
}
@Override
public PublishBarrierResult await(String identity,long timeout) {
Chan instance = Chan.getInstance(identity, false);
CommonTopicResponse response = instance.get(identity, timeout);
return Objects.nonNull(response) ? PublishBarrierResult.ok(response) : PublishBarrierResult.EMPTY;
}
@Override
public boolean hasIdentity(String identity) {
Chan instance = Chan.getInstance(identity, false);
return Objects.nonNull(instance);
}
}

51
src/main/java/com/dji/sdk/mqtt/FlowTransformWrapper.java

@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
/*************************************************
* @copyright 2017 Flision Corporation Inc.
* @author: Vincent Chan @ Canton
* @date: 2023年10月09日
* @version: 1.0.0
* @description:
**************************************************/
package com.dji.sdk.mqtt;
public class FlowTransformWrapper {
public final static String DEFAULT_ERROR_MSG = "null";
public static FlowTransformWrapper error(){
return new FlowTransformWrapper(DEFAULT_ERROR_MSG);
}
public static FlowTransformWrapper ok(CommonTopicRequest request){
return new FlowTransformWrapper(request);
}
CommonTopicRequest request;
boolean bError;
String errorMessage;
private FlowTransformWrapper(CommonTopicRequest request){
this.request = request;
this.bError = false;
}
private FlowTransformWrapper(String errorMessage){
this.bError = true;
this.errorMessage = errorMessage;
}
public CommonTopicRequest getRequest() {
return request;
}
public boolean hasError() {
return bError;
}
public boolean continuee(){
return !hasError();
}
public String getErrorMessage() {
return errorMessage;
}
}

24
src/main/java/com/dji/sdk/mqtt/GlobalPublishOption.java

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
/*************************************************
* @copyright 2017 Flision Corporation Inc.
* @author: Vincent Chan @ Canton
* @date: 2023年09月25日
* @version: 1.0.0
* @description: 全局发送默认配置
**************************************************/
package com.dji.sdk.mqtt;
import com.dji.sdk.common.PublishBarrierResult;
import com.dji.sdk.common.PublishRequest;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
public interface GlobalPublishOption {
Supplier<String> defaultTransactionId();
Supplier<String> defaultBizId();
Consumer<PublishRequest> defaultBeforePublishHook();
BiConsumer<PublishRequest, PublishBarrierResult> defaultAfterPublishHook();
}

3
src/main/java/com/dji/sdk/mqtt/InboundMessageRouter.java

@ -37,7 +37,10 @@ public class InboundMessageRouter extends AbstractMessageRouter { @@ -37,7 +37,10 @@ public class InboundMessageRouter extends AbstractMessageRouter {
String topic = headers.get(MqttHeaders.RECEIVED_TOPIC).toString();
byte[] payload = (byte[])message.getPayload();
//fix: 修复未启动debug时仍然需要构造debug参数的问题 by witcom@2023.09.22
if(log.isDebugEnabled()) {
log.debug("received topic: {} \t payload =>{}", topic, new String(payload));
}
CloudApiTopicEnum topicEnum = CloudApiTopicEnum.find(topic);
MessageChannel bean = (MessageChannel) SpringBeanUtils.getBean(topicEnum.getBeanName());

100
src/main/java/com/dji/sdk/mqtt/MqttGatewayPublish.java

@ -1,9 +1,10 @@ @@ -1,9 +1,10 @@
package com.dji.sdk.mqtt;
import com.dji.sdk.common.Common;
import com.dji.sdk.common.*;
import com.dji.sdk.exception.CloudSDKErrorEnum;
import com.dji.sdk.exception.CloudSDKException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.TypeMismatchException;
import org.springframework.integration.mqtt.support.MqttHeaders;
@ -14,7 +15,9 @@ import org.springframework.util.StringUtils; @@ -14,7 +15,9 @@ import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
/**
* @author sean.zhou
@ -32,9 +35,17 @@ public class MqttGatewayPublish { @@ -32,9 +35,17 @@ public class MqttGatewayPublish {
@Resource
private IMqttMessageGateway messageGateway;
@Resource
private PublishBarrier publishBarrier;
@Resource
private GlobalPublishOption globalOptions;
public void publish(String topic, int qos, CommonTopicRequest request) {
try {
if(log.isDebugEnabled()) {
log.debug("send topic: {}, payload: {}", topic, request.toString());
}
byte[] payload = Common.getObjectMapper().writeValueAsBytes(request);
messageGateway.publish(topic, payload, qos);
} catch (JsonProcessingException e) {
@ -45,7 +56,9 @@ public class MqttGatewayPublish { @@ -45,7 +56,9 @@ public class MqttGatewayPublish {
public void publish(String topic, int qos, CommonTopicResponse response) {
try {
if(log.isDebugEnabled()) {
log.debug("send topic: {}, payload: {}", topic, response.toString());
}
byte[] payload = Common.getObjectMapper().writeValueAsBytes(response);
messageGateway.publish(topic, payload, qos);
} catch (JsonProcessingException e) {
@ -73,7 +86,9 @@ public class MqttGatewayPublish { @@ -73,7 +86,9 @@ public class MqttGatewayPublish {
AtomicInteger time = new AtomicInteger(0);
boolean hasBid = StringUtils.hasText(request.getBid());
request.setBid(hasBid ? request.getBid() : UUID.randomUUID().toString());
// Retry
// Is Retry necessary? why not use Spring @Retryable instead?
while (time.getAndIncrement() <= retryCount) {
this.publish(topic, request);
@ -97,5 +112,88 @@ public class MqttGatewayPublish { @@ -97,5 +112,88 @@ public class MqttGatewayPublish {
throw new CloudSDKException(CloudSDKErrorEnum.MQTT_PUBLISH_ABNORMAL, "No message reply received.");
}
public <T> CompletableFuture<CommonTopicResponse<T>> publishWithReply(Class<T> clazz, String topic, CommonTopicRequest request, Consumer<PublishOption> options){
PublishConfiguration config = prepareConfiguration(options);
request.setBid(config.getBid());
request.setTid(config.getTid());
//use to log request data or the last chance to change some data
//CommonTopicRequest丢失了一些需要记录的内容,把这些内容封到PublishRequest交出去
PublishRequest wrapRequest = new CommonTopicRequestWrapper<T>(clazz,topic, request, config);
config.invokeBeforePublishHook(wrapRequest);
//注册barrier
String identity = publishBarrier.generateIdentity(request); //提供栅栏标识
publishBarrier.registerRequest(identity, request);
return CompletableFuture.supplyAsync(()->{
this.publish(topic, request);
if(log.isDebugEnabled()){ log.debug("等待{}指令返回",identity); };
PublishBarrierResult result = publishBarrier.await(identity,config.getTimeout());
config.invokeAfterPublishReplyHook(wrapRequest, result);
if(result.isTimeout()){
throw new CloudSDKException("Timeout"); //TODO: 换个更明确的异常更好
}
if(log.isDebugEnabled()){ log.debug("{}指令已返回",identity); }
return result.getData();
});
}
private PublishConfiguration prepareConfiguration(Consumer<PublishOption> options){
PublishConfiguration config = new PublishConfiguration();
PublishOption option = new PublishOption(config);
options.accept(option);
if(Strings.isNullOrEmpty(config.getBid())){
config.setBizId(globalOptions.defaultBizId().get());
}
if(Strings.isNullOrEmpty(config.getTid())){
config.setTransactionId(globalOptions.defaultTransactionId().get());
}
if(config.noneBeforePublishHook()){
config.setBeforePublishHook(globalOptions.defaultBeforePublishHook());
}
if(config.noneAfterPublishHook()){
config.setAfterPublishReplyHook(globalOptions.defaultAfterPublishHook());
}
return config;
}
static class CommonTopicRequestWrapper<T> implements PublishRequest{
final CommonTopicRequest request;
final String topic;
final Class<T> clazz;
final ReadonlyPublishConfiguration config;
public CommonTopicRequestWrapper(Class<T> clazz, String topic,CommonTopicRequest request, PublishConfiguration config) {
this.clazz = clazz;
this.request = request;
this.topic = topic;
this.config = config;
}
@Override
public String getTopic() {
return topic;
}
@Override
public CommonTopicRequest getOriginRequest() {
return request;
}
@Override
public ReadonlyPublishConfiguration getConfiguration() {
return config;
}
}
}

21
src/main/java/com/dji/sdk/mqtt/osd/OsdRouter.java

@ -14,11 +14,9 @@ import org.springframework.integration.dsl.IntegrationFlows; @@ -14,11 +14,9 @@ import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.Message;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.*;
import static com.dji.sdk.mqtt.TopicConst.*;
@ -32,7 +30,7 @@ import static com.dji.sdk.mqtt.TopicConst.*; @@ -32,7 +30,7 @@ import static com.dji.sdk.mqtt.TopicConst.*;
public class OsdRouter {
@Bean
public IntegrationFlow osdRouterFlow() {
public IntegrationFlow osdRouterFlow(SDKManager sdkManager) {
return IntegrationFlows
.from(ChannelName.INBOUND_OSD)
.transform(Message.class, source -> {
@ -45,7 +43,12 @@ public class OsdRouter { @@ -45,7 +43,12 @@ public class OsdRouter {
}
}, null)
.<TopicOsdRequest>handle((response, headers) -> {
GatewayManager gateway = SDKManager.getDeviceSDK(response.getGateway());
// fix: getDeviceSDK抛出异常导致在设备未注册的情况下报osd时产生大量日志 witcom@2023.09.22
//GatewayManager gateway = SDKManager.getDeviceSDK(response.getGateway());
return sdkManager.findDeviceSDK(response.getGateway())
.map(gateway-> {
OsdDeviceTypeEnum typeEnum = OsdDeviceTypeEnum.find(gateway.getType(), response.getFrom().equals(response.getGateway()));
Map<String, Object> data = (Map<String, Object>) response.getData();
if (!typeEnum.isGateway()) {
@ -56,8 +59,12 @@ public class OsdRouter { @@ -56,8 +59,12 @@ public class OsdRouter {
}
return response.setData(Common.getObjectMapper().convertValue(data, typeEnum.getClassType()));
})
.orElse(null);
})
.<TopicOsdRequest>filter(Objects::nonNull)
.<TopicOsdRequest, OsdDeviceTypeEnum>route(response -> OsdDeviceTypeEnum.find(response.getData().getClass()),
mapping -> Arrays.stream(OsdDeviceTypeEnum.values()).forEach(key -> mapping.channelMapping(key, key.getChannelName())))
mapping -> Arrays.stream(OsdDeviceTypeEnum.values())
.forEach(key -> mapping.channelMapping(key, key.getChannelName())))
.get();
}

7
src/main/java/com/dji/sdk/mqtt/osd/OsdSubscribe.java

@ -22,8 +22,11 @@ public class OsdSubscribe { @@ -22,8 +22,11 @@ public class OsdSubscribe {
@Resource
private IMqttTopicService topicService;
@Resource
SDKManager sdkManager;
public void subscribe(GatewayManager gateway, boolean unsubscribeSubDevice) {
SDKManager.registerDevice(gateway);
sdkManager.registerDevice(gateway);
topicService.subscribe(String.format(TOPIC, gateway.getGatewaySn()));
if (unsubscribeSubDevice) {
topicService.unsubscribe(String.format(TOPIC, gateway.getDroneSn()));
@ -35,7 +38,7 @@ public class OsdSubscribe { @@ -35,7 +38,7 @@ public class OsdSubscribe {
}
public void unsubscribe(GatewayManager gateway) {
SDKManager.logoutDevice(gateway.getGatewaySn());
sdkManager.logoutDevice(gateway.getGatewaySn());
topicService.unsubscribe(String.format(TOPIC, gateway.getGatewaySn()));
if (null != gateway.getDroneSn()) {
topicService.unsubscribe(String.format(TOPIC, gateway.getDroneSn()));

34
src/main/java/com/dji/sdk/mqtt/property/PropertySetPublish.java

@ -1,5 +1,8 @@ @@ -1,5 +1,8 @@
package com.dji.sdk.mqtt.property;
import com.dji.sdk.common.PublishOption;
import com.dji.sdk.mqtt.CommonTopicRequest;
import com.dji.sdk.mqtt.CommonTopicResponse;
import com.dji.sdk.mqtt.MqttGatewayPublish;
import com.dji.sdk.mqtt.TopicConst;
import org.springframework.stereotype.Component;
@ -7,6 +10,8 @@ import org.springframework.stereotype.Component; @@ -7,6 +10,8 @@ import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
/**
* @author sean
@ -29,12 +34,33 @@ public class PropertySetPublish { @@ -29,12 +34,33 @@ public class PropertySetPublish {
public PropertySetReplyResultEnum publish(String sn, Object data, int retryCount, long timeout) {
String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + Objects.requireNonNull(sn) + TopicConst.PROPERTY_SUF + TopicConst.SET_SUF;
return gatewayPublish.publishWithReply(PropertySetReplyResultEnum.class,topic,
new CommonTopicRequest<>()
.setTimestamp(System.currentTimeMillis())
.setData(Objects.requireNonNull(data)),
ops->ops.timeout((int)(timeout/1000)))
.join()
.getData();
// return gatewayPublish.publishWithReply(
// PropertySetReplyResultEnum.class, topic, new TopicPropertySetRequest<>()
// .setTid(UUID.randomUUID().toString())
// .setBid(null)
// .setTimestamp(System.currentTimeMillis())
// .setData(Objects.requireNonNull(data)), retryCount, timeout).getData();
}
public CompletableFuture<CommonTopicResponse<PropertySetReplyResultEnum>>
publish(String sn, Object data, Consumer<PublishOption> options){
String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + Objects.requireNonNull(sn) + TopicConst.PROPERTY_SUF + TopicConst.SET_SUF;
return gatewayPublish.publishWithReply(
PropertySetReplyResultEnum.class, topic, new TopicPropertySetRequest<>()
.setTid(UUID.randomUUID().toString())
.setBid(null)
PropertySetReplyResultEnum.class,
topic,
new CommonTopicRequest<>()
.setTimestamp(System.currentTimeMillis())
.setData(Objects.requireNonNull(data)), retryCount, timeout).getData();
.setData(Objects.requireNonNull(data)),
options);
}
}

17
src/main/java/com/dji/sdk/mqtt/property/PropertySetReplyHandler.java

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
package com.dji.sdk.mqtt.property;
import com.dji.sdk.common.Common;
import com.dji.sdk.common.PublishBarrier;
import com.dji.sdk.mqtt.Chan;
import com.dji.sdk.mqtt.ChannelName;
import com.fasterxml.jackson.core.type.TypeReference;
@ -9,6 +10,7 @@ import org.springframework.integration.annotation.ServiceActivator; @@ -9,6 +10,7 @@ import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.Objects;
@ -20,6 +22,9 @@ import java.util.Objects; @@ -20,6 +22,9 @@ import java.util.Objects;
@Component
public class PropertySetReplyHandler {
@Resource
PublishBarrier barrier;
private static final String RESULT_KEY = "result";
/**
@ -32,13 +37,19 @@ public class PropertySetReplyHandler { @@ -32,13 +37,19 @@ public class PropertySetReplyHandler {
byte[] payload = (byte[])message.getPayload();
TopicPropertySetResponse receiver = Common.getObjectMapper().readValue(payload, new TypeReference<TopicPropertySetResponse>() {});
Chan chan = Chan.getInstance(receiver.getTid(), false);
if (Objects.isNull(chan)) {
//fix: use Barrier instead witcom@2023.09.22
//Chan chan = Chan.getInstance(receiver.getTid(), false);
// if (Objects.isNull(chan)) {
// return;
// }
String identity = barrier.generateIdentity(receiver);
if(!barrier.hasIdentity(identity)) {
return;
}
receiver.setData(PropertySetReplyResultEnum.find(
Common.getObjectMapper().convertValue(receiver.getData(), JsonNode.class).findValue(RESULT_KEY).intValue()));
// Put the message to the chan object.
chan.put(receiver);
//chan.put(receiver);
barrier.put(identity, receiver);
}
}

44
src/main/java/com/dji/sdk/mqtt/services/ServicesPublish.java

@ -1,6 +1,8 @@ @@ -1,6 +1,8 @@
package com.dji.sdk.mqtt.services;
import com.dji.sdk.common.Common;
import com.dji.sdk.common.PublishOption;
import com.dji.sdk.mqtt.CommonTopicResponse;
import com.dji.sdk.mqtt.MqttGatewayPublish;
import com.dji.sdk.mqtt.TopicConst;
import com.fasterxml.jackson.core.type.TypeReference;
@ -10,6 +12,8 @@ import org.springframework.stereotype.Component; @@ -10,6 +12,8 @@ import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
/**
* @author sean
@ -60,6 +64,8 @@ public class ServicesPublish { @@ -60,6 +64,8 @@ public class ServicesPublish {
public <T> TopicServicesResponse<ServicesReplyData<T>> publish(
TypeReference<T> clazz, String sn, String method, Object data, String bid, int retryCount, long timeout) {
return this.publish(clazz, sn, method,data,ops-> ops.withBizId(bid).timeout((int)(timeout / 1000))).join();
/*
String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + Objects.requireNonNull(sn) + TopicConst.SERVICES_SUF;
TopicServicesResponse response = (TopicServicesResponse) gatewayPublish.publishWithReply(
ServicesReplyReceiver.class, topic, new TopicServicesRequest<>()
@ -84,6 +90,44 @@ public class ServicesPublish { @@ -84,6 +90,44 @@ public class ServicesPublish {
reply.setOutput(mapper.convertValue(replyReceiver.getOutput(), clazz));
}
return response.setData(reply);
*/
}
/**
* Remark by witcom@2023.09.22
* TODO: 在贡献的版本到这里就不再往下修改了,需要修改所有AbstractXXXService和AOP改动量很大
* 主要思想是
* 1.提供异步支持
* 2.Chan的实现可由用户自定义
* 3.提供发送选项参数
* 4.提供发送前接收后钩子用于记录请求和返回接近最原始的记录
*/
public <T> CompletableFuture<TopicServicesResponse<ServicesReplyData<T>>> publish(TypeReference<T> clazz, String sn, String method, Object data, Consumer<PublishOption> options){
String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + Objects.requireNonNull(sn) + TopicConst.SERVICES_SUF;
return gatewayPublish.publishWithReply(ServicesReplyReceiver.class, topic, new TopicServicesRequest<>()
.setTimestamp(System.currentTimeMillis())
.setMethod(method)
.setData(Objects.requireNonNullElse(data, "")), options)
.thenApply(response->(TopicServicesResponse)response)
.thenApply(response->{
ServicesReplyReceiver replyReceiver = (ServicesReplyReceiver) response.getData();
ServicesReplyData<T> reply = new ServicesReplyData<T>().setResult(replyReceiver.getResult());
if (Objects.isNull(clazz)) {
reply.setOutput((T) Objects.requireNonNullElse(
replyReceiver.getOutput(), Objects.requireNonNullElse(replyReceiver.getInfo(), "")));
return response.setData(reply);
}
// put together in "output"
ObjectMapper mapper = Common.getObjectMapper();
if (Objects.nonNull(replyReceiver.getInfo())) {
reply.setOutput(mapper.convertValue(replyReceiver.getInfo(), clazz));
}
if (Objects.nonNull(replyReceiver.getOutput())) {
reply.setOutput(mapper.convertValue(replyReceiver.getOutput(), clazz));
}
return response.setData(reply);
});
}
}

17
src/main/java/com/dji/sdk/mqtt/services/ServicesReplyHandler.java

@ -3,6 +3,7 @@ package com.dji.sdk.mqtt.services; @@ -3,6 +3,7 @@ package com.dji.sdk.mqtt.services;
import com.dji.sdk.cloudapi.log.FileUploadListResponse;
import com.dji.sdk.cloudapi.log.LogMethodEnum;
import com.dji.sdk.common.Common;
import com.dji.sdk.common.PublishBarrier;
import com.dji.sdk.mqtt.Chan;
import com.dji.sdk.mqtt.ChannelName;
import com.fasterxml.jackson.core.type.TypeReference;
@ -10,6 +11,7 @@ import org.springframework.integration.annotation.ServiceActivator; @@ -10,6 +11,7 @@ import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.Objects;
@ -21,6 +23,9 @@ import java.util.Objects; @@ -21,6 +23,9 @@ import java.util.Objects;
@Component
public class ServicesReplyHandler {
@Resource
PublishBarrier barrier;
/**
* Handle the reply message from topic "/services_reply".
* @param message reply message
@ -32,14 +37,20 @@ public class ServicesReplyHandler { @@ -32,14 +37,20 @@ public class ServicesReplyHandler {
TopicServicesResponse<ServicesReplyReceiver> receiver = Common.getObjectMapper()
.readValue(payload, new TypeReference<TopicServicesResponse<ServicesReplyReceiver>>() {});
Chan chan = Chan.getInstance(receiver.getTid(), false);
if (Objects.isNull(chan)) {
//fix: use Barrier instead witcom@2023.09.22
// Chan chan = Chan.getInstance(receiver.getTid(), false);
// if (Objects.isNull(chan)) {
// return;
// }
String identity = barrier.generateIdentity(receiver);
if(!barrier.hasIdentity(identity)){
return;
}
if (LogMethodEnum.FILE_UPLOAD_LIST.getMethod().equals(receiver.getMethod())) {
receiver.getData().setOutput(Common.getObjectMapper().convertValue(receiver.getData(),
new TypeReference<FileUploadListResponse>() {}));
}
chan.put(receiver);
barrier.put(identity, receiver);
//chan.put(receiver);
}
}

8
src/main/java/com/dji/sdk/mqtt/state/DockStateDataKeyEnum.java

@ -6,6 +6,7 @@ import com.dji.sdk.exception.CloudSDKException; @@ -6,6 +6,7 @@ import com.dji.sdk.exception.CloudSDKException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
/**
@ -47,9 +48,10 @@ public enum DockStateDataKeyEnum { @@ -47,9 +48,10 @@ public enum DockStateDataKeyEnum {
return keys;
}
public static DockStateDataKeyEnum find(Set<String> keys) {
return Arrays.stream(values()).filter(keyEnum -> !Collections.disjoint(keys, keyEnum.keys)).findAny()
.orElseThrow(() -> new CloudSDKException(DockStateDataKeyEnum.class, keys));
public static Optional<DockStateDataKeyEnum> find(Set<String> keys) {
// com.dji.sdk.mqtt.state.DockStateDataKeyEnum has unknown data: [[mode_code_reason]]
return Arrays.stream(values()).filter(keyEnum -> !Collections.disjoint(keys, keyEnum.keys)).findAny();
// .orElseThrow(() -> new CloudSDKException(DockStateDataKeyEnum.class, keys));
}
}

7
src/main/java/com/dji/sdk/mqtt/state/RcStateDataKeyEnum.java

@ -6,6 +6,7 @@ import com.dji.sdk.exception.CloudSDKException; @@ -6,6 +6,7 @@ import com.dji.sdk.exception.CloudSDKException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
/**
@ -45,9 +46,9 @@ public enum RcStateDataKeyEnum { @@ -45,9 +46,9 @@ public enum RcStateDataKeyEnum {
return keys;
}
public static RcStateDataKeyEnum find(Set<String> keys) {
return Arrays.stream(values()).filter(keyEnum -> !Collections.disjoint(keys, keyEnum.keys)).findAny()
.orElseThrow(() -> new CloudSDKException(RcStateDataKeyEnum.class, keys));
public static Optional<RcStateDataKeyEnum> find(Set<String> keys) {
return Arrays.stream(values()).filter(keyEnum -> !Collections.disjoint(keys, keyEnum.keys)).findAny();
//.orElseThrow(() -> new CloudSDKException(RcStateDataKeyEnum.class, keys));
}
}

62
src/main/java/com/dji/sdk/mqtt/state/StateRouter.java

@ -1,11 +1,15 @@ @@ -1,11 +1,15 @@
package com.dji.sdk.mqtt.state;
import com.dji.sdk.common.Common;
import com.dji.sdk.common.GatewayTypeEnum;
import com.dji.sdk.common.SDKManager;
import com.dji.sdk.exception.CloudSDKErrorEnum;
import com.dji.sdk.exception.CloudSDKException;
import com.dji.sdk.mqtt.ChannelName;
import com.dji.sdk.mqtt.FlowTransformWrapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.dsl.IntegrationFlow;
@ -13,51 +17,79 @@ import org.springframework.integration.dsl.IntegrationFlows; @@ -13,51 +17,79 @@ import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.Message;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.*;
import static com.dji.sdk.mqtt.TopicConst.*;
/**
*
* @author sean.zhou
* @date 2021/11/17
* @version 0.1
* @date 2021/11/17
*/
@Slf4j
@Configuration
public class StateRouter {
@Resource
SDKManager sdkManager;
@Bean
public IntegrationFlow stateDataRouterFlow() {
ObjectMapper objectMapper = Common.getObjectMapper();
return IntegrationFlows
.from(ChannelName.INBOUND_STATE)
.transform(Message.class, source -> {
try {
TopicStateRequest response = Common.getObjectMapper().readValue((byte[]) source.getPayload(), new TypeReference<TopicStateRequest>() {});
TopicStateRequest response = objectMapper.readValue(
(byte[]) source.getPayload(),
new TypeReference<TopicStateRequest>() {});
String topic = String.valueOf(source.getHeaders().get(MqttHeaders.RECEIVED_TOPIC));
String from = topic.substring((THING_MODEL_PRE + PRODUCT).length(), topic.indexOf(STATE_SUF));
return response.setFrom(from)
.setData(Common.getObjectMapper().convertValue(response.getData(), getTypeReference(response.getGateway(), response.getData())));
} catch (IOException e) {
throw new CloudSDKException(e);
return FlowTransformWrapper.ok(response.setFrom(from));
} catch (Exception ex) {
log.warn("[StateRouter]"+ex.getMessage());
return FlowTransformWrapper.error();
}
}, null)
.filter(FlowTransformWrapper::continuee)
.<FlowTransformWrapper>handle((wrapper, headers) -> {
TopicStateRequest response = (TopicStateRequest)wrapper.getRequest();
//fix: 修复设备未注册前设备推送state导致产生大量日志的问题 witcom@2023.10.08
try {
return getTypeReference(response.getGateway(), response.getData())
.map(clazz -> response.setData(objectMapper.convertValue(response.getData(), clazz)))
.orElse(null);
}catch (CloudSDKException ex){
log.warn("[StateRouter]"+ex.getMessage());
return null;
}
})
.filter(Objects::nonNull)
.<TopicStateRequest, StateDataKeyEnum>route(response -> StateDataKeyEnum.find(response.getData().getClass()),
mapping -> Arrays.stream(StateDataKeyEnum.values()).forEach(key -> mapping.channelMapping(key, key.getChannelName())))
.get();
}
private Class getTypeReference(String gatewaySn, Object data) {
private Optional<Class> getTypeReference(String gatewaySn, Object data) {
Set<String> keys = ((Map<String, Object>) data).keySet();
switch (SDKManager.getDeviceSDK(gatewaySn).getType()) {
//fix: 捕捉数据流发现在注册前设备可能会推送state主题导致产生大量日志 witcom@2023.10.08
//GatewayTypeEnum type = sdkManager.getDeviceSDK(gatewaySn).getType();
return sdkManager.findDeviceSDK(gatewaySn)
.flatMap(gw -> {
GatewayTypeEnum type = gw.getType();
switch (type) {
case RC:
return RcStateDataKeyEnum.find(keys).getClassType();
return RcStateDataKeyEnum.find(keys).map(v->v.getClassType());
case DOCK:
return DockStateDataKeyEnum.find(keys).getClassType();
return DockStateDataKeyEnum.find(keys).map(v->v.getClassType());
default:
throw new CloudSDKException(CloudSDKErrorEnum.WRONG_DATA, "Unexpected value: " + SDKManager.getDeviceSDK(gatewaySn).getType());
throw new CloudSDKException(CloudSDKErrorEnum.WRONG_DATA, "Unexpected value: " + type);
}
});
}
}

7
src/main/java/com/dji/sdk/mqtt/state/StateSubscribe.java

@ -20,10 +20,13 @@ public class StateSubscribe { @@ -20,10 +20,13 @@ public class StateSubscribe {
@Resource
private IMqttTopicService topicService;
@Resource
SDKManager sdkManager;
public static final String TOPIC = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + "%s" + TopicConst.STATE_SUF;
public void subscribe(GatewayManager gateway, boolean unsubscribeSubDevice) {
SDKManager.registerDevice(gateway);
sdkManager.registerDevice(gateway);
topicService.subscribe(String.format(TOPIC, gateway.getGatewaySn()));
if (unsubscribeSubDevice) {
topicService.unsubscribe(String.format(TOPIC, gateway.getDroneSn()));
@ -35,7 +38,7 @@ public class StateSubscribe { @@ -35,7 +38,7 @@ public class StateSubscribe {
}
public void unsubscribe(GatewayManager gateway) {
SDKManager.logoutDevice(gateway.getGatewaySn());
sdkManager.logoutDevice(gateway.getGatewaySn());
topicService.unsubscribe(String.format(TOPIC, gateway.getGatewaySn()));
if (null != gateway.getDroneSn()) {
topicService.unsubscribe(String.format(TOPIC, gateway.getDroneSn()));

7
src/main/java/com/dji/sdk/mqtt/status/StatusSubscribe.java

@ -22,8 +22,11 @@ public class StatusSubscribe { @@ -22,8 +22,11 @@ public class StatusSubscribe {
@Resource
private IMqttTopicService topicService;
@Resource
SDKManager sdkManager;
public void subscribe(GatewayManager gateway) {
SDKManager.registerDevice(gateway);
sdkManager.registerDevice(gateway);
topicService.subscribe(String.format(TOPIC, gateway.getGatewaySn()));
}
@ -32,7 +35,7 @@ public class StatusSubscribe { @@ -32,7 +35,7 @@ public class StatusSubscribe {
}
public void unsubscribe(GatewayManager gateway) {
SDKManager.logoutDevice(gateway.getGatewaySn());
sdkManager.logoutDevice(gateway.getGatewaySn());
topicService.unsubscribe(String.format(TOPIC, gateway.getGatewaySn()));
}

Loading…
Cancel
Save