From 2d8ded3e77b22e44985265ca4063102662e452c1 Mon Sep 17 00:00:00 2001
From: sean.zhou <sean.zhou@dji.com>
Date: Mon, 12 Dec 2022 18:32:19 +0800
Subject: [PATCH] initial v1.3.1

---
 src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java |  402 ++++++++++++++++++++++++++++++++++++++++-----------------
 1 files changed, 282 insertions(+), 120 deletions(-)

diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java
index dbfbf77..25b2689 100644
--- a/src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java
+++ b/src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java
@@ -6,6 +6,7 @@
 import com.dji.sample.common.error.CommonErrorEnum;
 import com.dji.sample.common.model.Pagination;
 import com.dji.sample.common.model.PaginationData;
+import com.dji.sample.common.model.ResponseResult;
 import com.dji.sample.component.mqtt.model.*;
 import com.dji.sample.component.mqtt.service.IMessageSenderService;
 import com.dji.sample.component.mqtt.service.IMqttTopicService;
@@ -19,19 +20,20 @@
 import com.dji.sample.manage.dao.IDeviceMapper;
 import com.dji.sample.manage.model.dto.*;
 import com.dji.sample.manage.model.entity.DeviceEntity;
-import com.dji.sample.manage.model.enums.DeviceDomainEnum;
-import com.dji.sample.manage.model.enums.IconUrlEnum;
-import com.dji.sample.manage.model.enums.UserTypeEnum;
+import com.dji.sample.manage.model.enums.*;
+import com.dji.sample.manage.model.param.DeviceOtaCreateParam;
 import com.dji.sample.manage.model.param.DeviceQueryParam;
 import com.dji.sample.manage.model.receiver.*;
 import com.dji.sample.manage.service.*;
 import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.integration.annotation.ServiceActivator;
 import org.springframework.integration.mqtt.support.MqttHeaders;
+import org.springframework.messaging.Message;
 import org.springframework.messaging.MessageHeaders;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -84,54 +86,46 @@
     private ObjectMapper objectMapper;
 
     @Autowired
-    private RedisOpsUtils redisOps;
+    private IWebSocketManageService webSocketManageService;
 
     @Autowired
-    private IWebSocketManageService webSocketManageService;
+    private IDeviceFirmwareService deviceFirmwareService;
 
     @Autowired
     @Qualifier("gatewayOSDServiceImpl")
     private ITSAService tsaService;
 
+    private static final List<String> INIT_TOPICS_SUFFIX = List.of(
+            OSD_SUF, STATE_SUF, SERVICES_SUF + _REPLY_SUF, EVENTS_SUF, PROPERTY_SUF + SET_SUF + _REPLY_SUF);
+
     @Override
-    public Boolean deviceOffline(String gatewaySn) {
+    public Boolean deviceOffline(StatusGatewayReceiver gateway) {
+        String gatewaySn = gateway.getSn();
         this.subscribeTopicOnline(gatewaySn);
 
         // Only the remote controller is logged in and the aircraft is not connected.
         String key = RedisConst.DEVICE_ONLINE_PREFIX + gatewaySn;
 
-        boolean exist = redisOps.checkExist(key);
+        boolean exist = RedisOpsUtils.checkExist(key);
         if (!exist) {
             Optional<DeviceDTO> gatewayOpt = this.getDeviceBySn(gatewaySn);
             if (gatewayOpt.isPresent()) {
                 DeviceDTO value = gatewayOpt.get();
-                value.setChildDeviceSn(value.getDeviceSn());
                 value.setBoundTime(null);
                 value.setLoginTime(null);
-                redisOps.setWithExpire(key, value, RedisConst.DEVICE_ALIVE_SECOND);
+                RedisOpsUtils.setWithExpire(key, value, RedisConst.DEVICE_ALIVE_SECOND);
                 this.pushDeviceOnlineTopo(value.getWorkspaceId(), gatewaySn, gatewaySn);
                 return true;
             }
-            DeviceDTO gateway = DeviceDTO.builder()
-                    .deviceSn(gatewaySn)
-                    .childDeviceSn(gatewaySn)
-                    .domain(DeviceDomainEnum.GATEWAY.getDesc())
-                    .build();
-            gatewayOpt.map(DeviceDTO::getWorkspaceId).ifPresent(gateway::setWorkspaceId);
-            redisOps.setWithExpire(key, gateway, RedisConst.DEVICE_ALIVE_SECOND);
-            this.pushDeviceOnlineTopo(gateway.getWorkspaceId(), gatewaySn, gatewaySn);
-            return true;
+
+            // When connecting for the first time
+            DeviceEntity gatewayDevice = deviceGatewayConvertToDeviceEntity(gateway);
+            return firstSaveDevice(gatewayDevice, null);
         }
 
-        long expire = redisOps.getExpire(key);
-        // If the key about the device in redis has expired, the remote control is considered to be offline.
-        if (expire <= 0) {
-            log.debug("The remote control is already offline.");
-            return true;
-        }
-
-        String deviceSn = ((DeviceDTO)(redisOps.get(key))).getChildDeviceSn();
-        if (deviceSn.equals(gatewaySn)) {
+        DeviceDTO deviceDTO = (DeviceDTO) (RedisOpsUtils.get(key));
+        String deviceSn = deviceDTO.getChildDeviceSn();
+        if (!StringUtils.hasText(deviceSn)) {
             return true;
         }
 
@@ -140,21 +134,23 @@
 
     @Override
     public Boolean subDeviceOffline(String deviceSn) {
+
+        // If no information about this device exists in the cache, the drone is considered to be offline.
+        String key = RedisConst.DEVICE_ONLINE_PREFIX + deviceSn;
+        if (!RedisOpsUtils.checkExist(key) || RedisOpsUtils.getExpire(key) <= 0) {
+            log.debug("The drone is already offline.");
+            return true;
+        }
+        DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(key);
         // Cancel drone-related subscriptions.
         this.unsubscribeTopicOffline(deviceSn);
 
         payloadService.deletePayloadsByDeviceSn(new ArrayList<>(List.of(deviceSn)));
-        // If no information about this gateway device exists in the database, the drone is considered to be offline.
-        String key = RedisConst.DEVICE_ONLINE_PREFIX + deviceSn;
-        if (!redisOps.checkExist(key) || redisOps.getExpire(key) <= 0) {
-            log.debug("The drone is already offline.");
-            return true;
-        }
-        DeviceDTO device = (DeviceDTO) redisOps.get(key);
         // Publish the latest device topology information in the current workspace.
         this.pushDeviceOfflineTopo(device.getWorkspaceId(), deviceSn);
 
-        redisOps.del(key);
+        RedisOpsUtils.del(key);
+        RedisOpsUtils.del(RedisConst.OSD_PREFIX + device.getDeviceSn());
         log.debug("{} offline.", deviceSn);
         return true;
     }
@@ -164,12 +160,11 @@
         String deviceSn = deviceGateway.getSubDevices().get(0).getSn();
         String key = RedisConst.DEVICE_ONLINE_PREFIX + deviceSn;
         // change log:  Use redis instead of
-        long time = redisOps.getExpire(key);
-        long now = System.currentTimeMillis();
+        long time = RedisOpsUtils.getExpire(key);
+        long gatewayTime = RedisOpsUtils.getExpire(RedisConst.DEVICE_ONLINE_PREFIX + deviceGateway.getSn());
 
-        if (time > 0) {
-            redisOps.expireKey(RedisConst.DEVICE_ONLINE_PREFIX + deviceGateway.getSn(), RedisConst.DEVICE_ALIVE_SECOND);
-            redisOps.expireKey(key, RedisConst.DEVICE_ALIVE_SECOND);
+        if (time > 0 && gatewayTime > 0) {
+            RedisOpsUtils.expireKey(key, RedisConst.DEVICE_ALIVE_SECOND);
             DeviceDTO device = DeviceDTO.builder().loginTime(LocalDateTime.now()).deviceSn(deviceSn).build();
             DeviceDTO gateway = DeviceDTO.builder()
                     .loginTime(LocalDateTime.now())
@@ -177,7 +172,7 @@
                     .childDeviceSn(deviceSn).build();
             this.updateDevice(gateway);
             this.updateDevice(device);
-            String workspaceId = ((DeviceDTO)(redisOps.get(key))).getWorkspaceId();
+            String workspaceId = ((DeviceDTO)(RedisOpsUtils.get(key))).getWorkspaceId();
             if (StringUtils.hasText(workspaceId)) {
                 this.subscribeTopicOnline(deviceSn);
                 this.subscribeTopicOnline(deviceGateway.getSn());
@@ -198,61 +193,37 @@
                     this.updateDevice(gateway);
                 });
 
-
         DeviceEntity gateway = deviceGatewayConvertToDeviceEntity(deviceGateway);
-        gateway.setChildSn(deviceSn);
-        // Set the icon of the gateway device displayed in the pilot's map, required in the TSA module.
-        gateway.setUrlNormal(IconUrlEnum.NORMAL_PERSON.getUrl());
-        // Set the icon of the gateway device displayed in the pilot's map when it is selected, required in the TSA module.
-        gateway.setUrlSelect(IconUrlEnum.SELECT_PERSON.getUrl());
-        gateway.setLoginTime(now);
-
         DeviceEntity subDevice = subDeviceConvertToDeviceEntity(deviceGateway.getSubDevices().get(0));
-        // Set the icon of the drone device displayed in the pilot's map when it is selected, required in the TSA module.
-        subDevice.setUrlNormal(IconUrlEnum.NORMAL_EQUIPMENT.getUrl());
-        // Set the icon of the drone device displayed in the pilot's map, required in the TSA module.
-        subDevice.setUrlSelect(IconUrlEnum.SELECT_EQUIPMENT.getUrl());
-        subDevice.setLoginTime(now);
+        boolean isSave = firstSaveDevice(gateway, deviceSn) && firstSaveDevice(subDevice, null);
+        if (!isSave) {
+            return false;
+        }
 
         // dock go online
         if (deviceGateway.getDomain() != null && DeviceDomainEnum.DOCK.getVal() == deviceGateway.getDomain()) {
             Optional<DeviceDTO> deviceOpt = this.getDeviceBySn(deviceGateway.getSn());
             if (deviceOpt.isEmpty()) {
-                log.info("The dock is not bound and cannot go online.");
+                log.info("The dock is not bound and cannot go online. Please refer to the Cloud API document video for binding.");
                 return false;
             }
             gateway.setNickname(null);
             subDevice.setNickname(null);
         }
 
-        Optional<DeviceEntity> gatewayOpt = this.saveDevice(gateway);
-        String workspaceId = this.saveDevice(subDevice).orElse(subDevice).getWorkspaceId();
+        String workspaceId = subDevice.getWorkspaceId();
 
-        redisOps.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + deviceSn,
-                DeviceDTO.builder()
-                        .deviceSn(deviceSn)
-                        .domain(DeviceDomainEnum.SUB_DEVICE.getDesc())
-                        .workspaceId(workspaceId)
-                        .build(),
-                RedisConst.DEVICE_ALIVE_SECOND);
-        redisOps.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + gateway.getDeviceSn(),
-                DeviceDTO.builder()
-                        .deviceSn(gateway.getDeviceSn())
-                        .workspaceId(gatewayOpt.orElse(gateway).getWorkspaceId())
-                        .childDeviceSn(deviceSn)
-                        .domain(deviceGateway.getDomain() != null ?
-                                DeviceDomainEnum.getDesc(deviceGateway.getDomain()) :
-                                DeviceDomainEnum.GATEWAY.getDesc())
-                        .build(),
-                RedisConst.DEVICE_ALIVE_SECOND);
-        log.debug("{} online.", subDevice.getDeviceSn());
-
-        if (StringUtils.hasText(workspaceId)) {
-            this.pushDeviceOnlineTopo(workspaceId, deviceGateway.getSn(), deviceSn);
+        this.subscribeTopicOnline(deviceGateway.getSn());
+        if (!StringUtils.hasText(workspaceId)) {
+            log.info("The drone is not bound and cannot go online. Please refer to the Cloud API document video for binding.");
+            return true;
         }
+
         // Subscribe to topic related to drone devices.
         this.subscribeTopicOnline(deviceSn);
-        this.subscribeTopicOnline(deviceGateway.getSn());
+        this.pushDeviceOnlineTopo(workspaceId, deviceGateway.getSn(), deviceSn);
+
+        log.debug("{} online.", subDevice.getDeviceSn());
         return true;
     }
 
@@ -265,20 +236,14 @@
                 return;
             }
         }
-        topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + OSD_SUF);
-        topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + STATE_SUF);
-        topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + SERVICES_SUF + _REPLY_SUF);
-        topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + REQUESTS_SUF);
-        topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + EVENTS_SUF);
+        String prefix = THING_MODEL_PRE + PRODUCT + sn;
+        INIT_TOPICS_SUFFIX.forEach(suffix -> topicService.subscribe(prefix + suffix));
     }
 
     @Override
     public void unsubscribeTopicOffline(String sn) {
-        topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + OSD_SUF);
-        topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + STATE_SUF);
-        topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + SERVICES_SUF + _REPLY_SUF);
-        topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + REQUESTS_SUF);
-        topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + EVENTS_SUF);
+        String prefix = THING_MODEL_PRE + PRODUCT + sn;
+        INIT_TOPICS_SUFFIX.forEach(suffix -> topicService.unsubscribe(prefix + suffix));
     }
 
     @Override
@@ -346,7 +311,7 @@
         devicesList.forEach(device -> {
             this.spliceDeviceTopo(device);
             device.setWorkspaceId(workspaceId);
-            device.setStatus(redisOps.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn()));
+            device.setStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn()));
         });
         return devicesList;
     }
@@ -372,6 +337,9 @@
 
     @Override
     public Optional<TopologyDeviceDTO> getDeviceTopoForPilot(String sn) {
+        if (sn.isBlank()) {
+            return Optional.empty();
+        }
         List<TopologyDeviceDTO> topologyDeviceList = this.getDevicesByParams(
                 DeviceQueryParam.builder()
                         .deviceSn(sn)
@@ -397,7 +365,7 @@
 
         this.getDeviceTopoForPilot(sn)
                 .ifPresent(pilotMessage::setData);
-        boolean exist = redisOps.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + sn);
+        boolean exist = RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + sn);
         pilotMessage.getData().setOnlineStatus(exist);
         pilotMessage.getData().setGatewaySn(gatewaySn.equals(sn) ? "" : gatewaySn);
 
@@ -422,7 +390,7 @@
                             .key(domain + "-" + type + "-" + subType)
                             .build())
                     .iconUrls(device.getIconUrl())
-                    .onlineStatus(redisOps.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn()))
+                    .onlineStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn()))
                     .boundStatus(device.getBoundStatus())
                     .model(device.getDeviceName())
                     .userId(device.getUserId())
@@ -457,19 +425,33 @@
     }
 
     @Override
-    public void handleOSD(String topic, byte[] payload) {
+    @ServiceActivator(inputChannel = ChannelName.INBOUND_OSD)
+    public void handleOSD(Message<?> message) {
+        String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
+        byte[] payload = (byte[])message.getPayload();
         CommonTopicReceiver receiver;
         try {
             String from = topic.substring((THING_MODEL_PRE + PRODUCT).length(),
                     topic.indexOf(OSD_SUF));
 
             // Real-time update of device status in memory
-            redisOps.expireKey(RedisConst.DEVICE_ONLINE_PREFIX + from, RedisConst.DEVICE_ALIVE_SECOND);
+            RedisOpsUtils.expireKey(RedisConst.DEVICE_ONLINE_PREFIX + from, RedisConst.DEVICE_ALIVE_SECOND);
 
-            DeviceDTO device = (DeviceDTO) redisOps.get(RedisConst.DEVICE_ONLINE_PREFIX + from);
+            DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + from);
 
-            if (device == null || !StringUtils.hasText(device.getWorkspaceId())) {
-                return;
+            if (device == null) {
+                Optional<DeviceDTO> deviceOpt = this.getDeviceBySn(from);
+                if (deviceOpt.isEmpty()) {
+                    return;
+                }
+                device = deviceOpt.get();
+                if (!StringUtils.hasText(device.getWorkspaceId())) {
+                    this.unsubscribeTopicOffline(from);
+                    return;
+                }
+                RedisOpsUtils.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + from, device,
+                        RedisConst.DEVICE_ALIVE_SECOND);
+                this.subscribeTopicOnline(from);
             }
 
             receiver = objectMapper.readValue(payload, CommonTopicReceiver.class);
@@ -556,7 +538,9 @@
 
         // Query the model information of this gateway device.
         Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService
-                .getOneDictionaryInfoByTypeSubType(gateway.getType(), gateway.getSubType());
+                .getOneDictionaryInfoByTypeSubType(Objects.nonNull(gateway.getDomain()) ?
+                                gateway.getDomain() : DeviceDomainEnum.GATEWAY.getVal(),
+                        gateway.getType(), gateway.getSubType());
 
         dictionaryOpt.ifPresent(entity ->
                 builder.deviceName(entity.getDeviceName())
@@ -586,7 +570,7 @@
 
         // Query the model information of this drone device.
         Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService
-                .getOneDictionaryInfoByTypeSubType(device.getType(), device.getSubType());
+                .getOneDictionaryInfoByTypeSubType(DeviceDomainEnum.SUB_DEVICE.getVal(), device.getType(), device.getSubType());
 
         dictionaryOpt.ifPresent(dictionary ->
                 builder.deviceName(dictionary.getDeviceName())
@@ -599,8 +583,7 @@
                 .subType(device.getSubType())
                 .version(device.getVersion())
                 .deviceIndex(device.getIndex())
-                .domain(device.getDomain() != null ?
-                        device.getDomain() : DeviceDomainEnum.SUB_DEVICE.getVal())
+                .domain(DeviceDomainEnum.SUB_DEVICE.getVal())
                 .build();
     }
 
@@ -613,7 +596,7 @@
         if (entity == null) {
             return null;
         }
-        return DeviceDTO.builder()
+        DeviceDTO.DeviceDTOBuilder deviceDTOBuilder = DeviceDTO.builder()
                 .deviceSn(entity.getDeviceSn())
                 .childDeviceSn(entity.getChildSn())
                 .deviceName(entity.getDeviceName())
@@ -638,8 +621,31 @@
                 .firmwareVersion(entity.getFirmwareVersion())
                 .workspaceName(entity.getWorkspaceId() != null ?
                         workspaceService.getWorkspaceByWorkspaceId(entity.getWorkspaceId())
-                        .map(WorkspaceDTO::getWorkspaceName).orElse("") : "")
-                .build();
+                                .map(WorkspaceDTO::getWorkspaceName).orElse("") : "");
+
+        if (!StringUtils.hasText(entity.getFirmwareVersion())) {
+            return deviceDTOBuilder.firmwareStatus(DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal()).build();
+        }
+        // Query whether the device is updating firmware.
+        Object progress = RedisOpsUtils.get(RedisConst.FIRMWARE_UPGRADING_PREFIX + entity.getDeviceSn());
+        if (Objects.nonNull(progress)) {
+            return deviceDTOBuilder.firmwareStatus(DeviceFirmwareStatusEnum.UPGRADING.getVal()).firmwareProgress((int)progress).build();
+        }
+
+        // First query the latest firmware version of the device model and compare it with the current firmware version
+        // to see if it needs to be upgraded.
+        Optional<DeviceFirmwareNoteDTO> firmwareReleaseNoteOpt = deviceFirmwareService.getLatestFirmwareReleaseNote(entity.getDeviceName());
+        if (firmwareReleaseNoteOpt.isPresent()) {
+            DeviceFirmwareNoteDTO firmwareNoteDTO = firmwareReleaseNoteOpt.get();
+            if (firmwareNoteDTO.getProductVersion().equals(entity.getFirmwareVersion())) {
+                return deviceDTOBuilder.firmwareStatus(entity.getCompatibleStatus() ?
+                        DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal() :
+                        DeviceFirmwareStatusEnum.CONSISTENT_UPGRADE.getVal()).build();
+            }
+
+            return deviceDTOBuilder.firmwareStatus(DeviceFirmwareStatusEnum.NORMAL_UPGRADE.getVal()).build();
+        }
+        return deviceDTOBuilder.firmwareStatus(DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal()).build();
     }
 
     @Override
@@ -655,17 +661,17 @@
         device.setBoundTime(LocalDateTime.now());
 
         boolean isUpd = this.saveDevice(this.deviceDTO2Entity(device)).isPresent();
-        if (DeviceDomainEnum.DOCK.getDesc().equals(device.getDomain())) {
-            return isUpd;
-        }
         if (!isUpd) {
             return false;
         }
 
         String key = RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn();
-        DeviceDTO redisDevice = (DeviceDTO)redisOps.get(key);
+        DeviceDTO redisDevice = (DeviceDTO)RedisOpsUtils.get(key);
+        if (Objects.isNull(redisDevice)) {
+            return false;
+        }
         redisDevice.setWorkspaceId(device.getWorkspaceId());
-        redisOps.setWithExpire(key, redisDevice, RedisConst.DEVICE_ALIVE_SECOND);
+        RedisOpsUtils.setWithExpire(key, redisDevice, RedisConst.DEVICE_ALIVE_SECOND);
 
         if (DeviceDomainEnum.GATEWAY.getDesc().equals(redisDevice.getDomain())) {
             this.pushDeviceOnlineTopo(webSocketManageService.getValueWithWorkspace(device.getWorkspaceId()),
@@ -732,8 +738,8 @@
 
         assert dock != null;
 
-        Optional<DeviceEntity> dockEntityOpt = this.bindDevice2Entity(dock);
-        Optional<DeviceEntity> droneEntityOpt = this.bindDevice2Entity(drone);
+        Optional<DeviceEntity> dockEntityOpt = this.bindDevice2Entity(DeviceDomainEnum.DOCK.getVal(), dock);
+        Optional<DeviceEntity> droneEntityOpt = this.bindDevice2Entity(DeviceDomainEnum.SUB_DEVICE.getVal(), drone);
 
         List<ErrorInfoReply> bindResult = new ArrayList<>();
 
@@ -777,12 +783,12 @@
                         .eq(DeviceEntity::getBoundStatus, true));
         List<DeviceDTO> devicesList = pagination.getRecords().stream().map(this::deviceEntityConvertToDTO)
                 .peek(device -> {
-                    device.setStatus(redisOps.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn()));
+                    device.setStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn()));
                     if (StringUtils.hasText(device.getChildDeviceSn())) {
                         Optional<DeviceDTO> childOpt = this.getDeviceBySn(device.getChildDeviceSn());
                         childOpt.ifPresent(child -> {
                             child.setStatus(
-                                    redisOps.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + child.getDeviceSn()));
+                                    RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + child.getDeviceSn()));
                             child.setWorkspaceName(device.getWorkspaceName());
                             device.setChildren(child);
                         });
@@ -795,9 +801,9 @@
     @Override
     public void unbindDevice(String deviceSn) {
         String key = RedisConst.DEVICE_ONLINE_PREFIX + deviceSn;
-        DeviceDTO redisDevice = (DeviceDTO) redisOps.get(key);
+        DeviceDTO redisDevice = (DeviceDTO) RedisOpsUtils.get(key);
         redisDevice.setWorkspaceId("");
-        redisOps.setWithExpire(key, redisDevice, RedisConst.DEVICE_ALIVE_SECOND);
+        RedisOpsUtils.setWithExpire(key, redisDevice, RedisConst.DEVICE_ALIVE_SECOND);
 
         DeviceDTO device = DeviceDTO.builder()
                 .deviceSn(deviceSn)
@@ -815,20 +821,138 @@
             return Optional.empty();
         }
         DeviceDTO device = devicesList.get(0);
-        device.setStatus(redisOps.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + sn));
+        device.setStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + sn));
         return Optional.of(device);
     }
 
     @Override
+    @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_FIRMWARE_VERSION)
     public void updateFirmwareVersion(FirmwareVersionReceiver receiver) {
+        // If the reported version is empty, it will not be processed to prevent misleading page.
+        if (!StringUtils.hasText(receiver.getFirmwareVersion())) {
+            return;
+        }
+
         if (receiver.getDomain() == DeviceDomainEnum.SUB_DEVICE) {
-            this.updateDevice(DeviceDTO.builder()
+            final DeviceDTO device = DeviceDTO.builder()
                     .deviceSn(receiver.getSn())
                     .firmwareVersion(receiver.getFirmwareVersion())
-                    .build());
+                    .firmwareStatus(receiver.getCompatibleStatus() == null ?
+                            null : DeviceFirmwareStatusEnum.CompatibleStatusEnum.INCONSISTENT.getVal() != receiver.getCompatibleStatus() ?
+                            DeviceFirmwareStatusEnum.UNKNOWN.getVal() : DeviceFirmwareStatusEnum.CONSISTENT_UPGRADE.getVal())
+                    .build();
+            this.updateDevice(device);
             return;
         }
         payloadService.updateFirmwareVersion(receiver);
+    }
+
+    @Override
+    public ResponseResult createDeviceOtaJob(String workspaceId, List<DeviceFirmwareUpgradeDTO> upgradeDTOS) {
+        List<DeviceOtaCreateParam> deviceOtaFirmwares = deviceFirmwareService.getDeviceOtaFirmware(upgradeDTOS);
+        if (deviceOtaFirmwares.isEmpty()) {
+            return ResponseResult.error();
+        }
+
+        DeviceOtaCreateParam deviceOtaFirmware = deviceOtaFirmwares.get(0);
+        List<DeviceDTO> devices = getDevicesByParams(DeviceQueryParam.builder().childSn(deviceOtaFirmware.getSn()).build());
+        String gatewaySn = devices.isEmpty() ? deviceOtaFirmware.getSn() : devices.get(0).getDeviceSn();
+
+        String topic = THING_MODEL_PRE + PRODUCT + gatewaySn + SERVICES_SUF;
+
+        // The bids in the progress messages reported subsequently are the same.
+        String bid = UUID.randomUUID().toString();
+        ServiceReply serviceReply = messageSender.publishWithReply(
+                topic, CommonTopicResponse.<Map<String, List<DeviceOtaCreateParam>>>builder()
+                        .tid(UUID.randomUUID().toString())
+                        .bid(bid)
+                        .timestamp(System.currentTimeMillis())
+                        .method(FirmwareMethodEnum.OTA_CREATE.getMethod())
+                        .data(Map.of(MapKeyConst.DEVICES, deviceOtaFirmwares))
+                        .build());
+        if (serviceReply.getResult() != ResponseResult.CODE_SUCCESS) {
+            return ResponseResult.error(serviceReply.getResult(), "Firmware Error Code: " + serviceReply.getResult());
+        }
+
+        // Record the device state that needs to be updated.
+        deviceOtaFirmwares.forEach(deviceOta -> RedisOpsUtils.setWithExpire(
+                RedisConst.FIRMWARE_UPGRADING_PREFIX + deviceOta.getSn(),
+                bid,
+                RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND));
+        return ResponseResult.success();
+    }
+
+    @Override
+    public void devicePropertySet(String workspaceId, String dockSn, DeviceSetPropertyEnum propertyEnum, JsonNode param) {
+        boolean dockOnline = this.checkDeviceOnline(dockSn);
+        if (!dockOnline) {
+            throw new RuntimeException("Dock is offline.");
+        }
+        DeviceDTO deviceDTO = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + dockSn);
+        boolean deviceOnline = this.checkDeviceOnline(deviceDTO.getChildDeviceSn());
+        if (!deviceOnline) {
+            throw new RuntimeException("Device is offline.");
+        }
+
+        // Make sure the data is valid.
+        BasicDeviceProperty basicDeviceProperty = objectMapper.convertValue(param, propertyEnum.getClazz());
+        boolean valid = basicDeviceProperty.valid();
+        if (!valid) {
+            throw new IllegalArgumentException(CommonErrorEnum.ILLEGAL_ARGUMENT.getErrorMsg());
+        }
+
+        String topic = THING_MODEL_PRE + PRODUCT + dockSn + PROPERTY_SUF + SET_SUF;
+//        OsdSubDeviceReceiver osd = (OsdSubDeviceReceiver) RedisOpsUtils.get(RedisConst.OSD_PREFIX + deviceDTO.getChildDeviceSn());
+        if (!param.isObject()) {
+            this.deviceOnePropertySet(topic, propertyEnum, Map.entry(propertyEnum.getProperty(), param));
+            return;
+        }
+        // If there are multiple parameters, set them separately.
+        for (Iterator<Map.Entry<String, JsonNode>> filed = param.fields(); filed.hasNext(); ) {
+            Map.Entry<String, JsonNode> node = filed.next();
+            boolean isPublish = basicDeviceProperty.canPublish(node.getKey(), null);
+            if (!isPublish) {
+                continue;
+            }
+            this.deviceOnePropertySet(topic, propertyEnum, Map.entry(propertyEnum.getProperty(), node));
+        }
+
+    }
+
+    @Override
+    public void deviceOnePropertySet(String topic, DeviceSetPropertyEnum propertyEnum, Map.Entry<String, Object> value) {
+        if (Objects.isNull(value) || Objects.isNull(value.getValue())) {
+            throw new IllegalArgumentException(CommonErrorEnum.ILLEGAL_ARGUMENT.getErrorMsg());
+        }
+
+        Map reply = messageSender.publishWithReply(
+                Map.class, topic,
+                CommonTopicResponse.builder()
+                        .bid(UUID.randomUUID().toString())
+                        .tid(UUID.randomUUID().toString())
+                        .timestamp(System.currentTimeMillis())
+                        .data(value)
+                        .build(),
+                2);
+
+        while (true) {
+            reply = (Map<String, Object>) reply.get(value.getKey());
+            if (value.getValue() instanceof JsonNode) {
+                break;
+            }
+            value = (Map.Entry) value.getValue();
+        }
+
+        SetReply setReply = objectMapper.convertValue(reply, SetReply.class);
+        if (SetReplyStatusResultEnum.SUCCESS.getVal() != setReply.getResult()) {
+            throw new RuntimeException("Failed to set " + value.getKey() + "; Error Code: " + setReply.getResult());
+        }
+
+    }
+
+    public Boolean checkDeviceOnline(String sn) {
+        String key = RedisConst.DEVICE_ONLINE_PREFIX + sn;
+        return RedisOpsUtils.checkExist(key) && RedisOpsUtils.getExpire(key) > 0;
     }
 
     /**
@@ -854,20 +978,24 @@
                 .childSn(dto.getChildDeviceSn())
                 .domain(StringUtils.hasText(dto.getDomain()) ? DeviceDomainEnum.getVal(dto.getDomain()) : null)
                 .firmwareVersion(dto.getFirmwareVersion())
+                .compatibleStatus(dto.getFirmwareStatus() == null ? null :
+                        DeviceFirmwareStatusEnum.CONSISTENT_UPGRADE != DeviceFirmwareStatusEnum.find(dto.getFirmwareStatus()))
                 .build();
     }
 
     /**
      * Convert device binding data object into database entity object.
+     *
+     * @param domain
      * @param receiver
      * @return
      */
-    private Optional<DeviceEntity> bindDevice2Entity(BindDeviceReceiver receiver) {
+    private Optional<DeviceEntity> bindDevice2Entity(Integer domain, BindDeviceReceiver receiver) {
         if (receiver == null) {
             return Optional.empty();
         }
         int[] droneKey = Arrays.stream(receiver.getDeviceModelKey().split("-")).mapToInt(Integer::parseInt).toArray();
-        Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService.getOneDictionaryInfoByTypeSubType(droneKey[1], droneKey[2]);
+        Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService.getOneDictionaryInfoByTypeSubType(domain, droneKey[1], droneKey[2]);
         DeviceEntity.DeviceEntityBuilder builder = DeviceEntity.builder();
 
         dictionaryOpt.ifPresent(entity ->
@@ -912,4 +1040,38 @@
                 .organizationName(device.getWorkspaceName())
                 .build();
     }
+
+    private Boolean firstSaveDevice(DeviceEntity device, String deviceSn) {
+
+        Optional<DeviceDTO> deviceOpt = this.getDeviceBySn(device.getDeviceSn());
+        if (deviceOpt.isEmpty()) {
+            // Set the icon of the gateway device displayed in the pilot's map, required in the TSA module.
+            device.setUrlNormal(IconUrlEnum.NORMAL_PERSON.getUrl());
+            // Set the icon of the gateway device displayed in the pilot's map when it is selected, required in the TSA module.
+            device.setUrlSelect(IconUrlEnum.SELECT_PERSON.getUrl());
+        }
+
+        deviceOpt.ifPresent(oldDevice -> device.setNickname(oldDevice.getNickname()));
+        device.setChildSn(deviceSn);
+        device.setLoginTime(System.currentTimeMillis());
+
+        Optional<DeviceEntity> saveDeviceOpt = this.saveDevice(device);
+        if (saveDeviceOpt.isEmpty()) {
+            return false;
+        }
+        device.setWorkspaceId(saveDeviceOpt.get().getWorkspaceId());
+
+        RedisOpsUtils.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn(),
+                DeviceDTO.builder()
+                        .deviceSn(device.getDeviceSn())
+                        .workspaceId(device.getWorkspaceId())
+                        .childDeviceSn(deviceSn)
+                        .domain(DeviceDomainEnum.getDesc(device.getDomain()))
+                        .type(device.getDeviceType())
+                        .subType(device.getSubType())
+                        .build(),
+                RedisConst.DEVICE_ALIVE_SECOND);
+
+        return true;
+    }
 }
\ No newline at end of file

--
Gitblit v1.9.3