From 8d9a2d656e4ae007590c622e5f7c228adacdca49 Mon Sep 17 00:00:00 2001
From: rain <167982779@qq.com>
Date: Fri, 14 Jun 2024 10:11:36 +0800
Subject: [PATCH] 统一风格

---
 src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java | 1073 ++++++++++++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 870 insertions(+), 203 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 80e34df..1ddd9db 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
@@ -1,44 +1,54 @@
 package com.dji.sample.manage.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.dji.sample.component.mqtt.model.CommonTopicResponse;
-import com.dji.sample.component.mqtt.model.TopicStateReceiver;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+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;
+import com.dji.sample.component.redis.RedisConst;
+import com.dji.sample.component.redis.RedisOpsUtils;
 import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
 import com.dji.sample.component.websocket.model.BizCodeEnum;
 import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
-import com.dji.sample.component.websocket.model.WebSocketManager;
 import com.dji.sample.component.websocket.service.ISendMessageService;
+import com.dji.sample.component.websocket.service.IWebSocketManageService;
+import com.dji.sample.control.model.enums.DroneAuthorityEnum;
 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.StatusGatewayReceiver;
-import com.dji.sample.manage.model.receiver.StatusSubDeviceReceiver;
-import com.dji.sample.manage.service.IDeviceDictionaryService;
-import com.dji.sample.manage.service.IDevicePayloadService;
-import com.dji.sample.manage.service.IDeviceService;
-import com.dji.sample.manage.service.IWorkspaceService;
-import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.dji.sample.manage.model.receiver.*;
+import com.dji.sample.manage.service.*;
+import com.dji.sample.wayline.model.entity.WaylineJobEntity;
+import com.dji.sample.wayline.model.param.WaylineJobQueryParam;
+import com.dji.sample.wayline.service.IWaylineJobService;
+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;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
 
 import java.io.IOException;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
@@ -77,106 +87,152 @@
     private ISendMessageService sendMessageService;
 
     @Autowired
+    private ObjectMapper objectMapper;
+
+    @Autowired
+    private IWebSocketManageService webSocketManageService;
+
+    @Autowired
+    private IDeviceFirmwareService deviceFirmwareService;
+
+    @Autowired
+    private ICapacityCameraService capacityCameraService;
+
+    @Autowired
+    private IDeviceRedisService deviceRedisService;
+
+    @Autowired
+    private IWaylineJobService waylineJobService;
+
+    @Autowired
     @Qualifier("gatewayOSDServiceImpl")
-    private AbstractTSAService tsaService;
+    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) {
-        List<DeviceDTO> gatewaysList = this.getDevicesByParams(
-                DeviceQueryParam.builder()
-                        .deviceSn(gatewaySn)
-                        .build());
+    public Boolean deviceOffline(StatusGatewayReceiver gateway) {
+        String gatewaySn = gateway.getSn();
+        this.subscribeTopicOnline(gatewaySn);
 
-        // If no information about this gateway device exists in the database, the drone is considered to be offline.
-        if (gatewaysList.isEmpty()) {
-            log.debug("The drone is already offline.");
+        // Only the remote controller is logged in and the aircraft is not connected.
+        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(gatewaySn);
+        if (deviceOpt.isEmpty()) {
+            Optional<DeviceDTO> gatewayOpt = this.getDeviceBySn(gatewaySn);
+            if (gatewayOpt.isPresent()) {
+                DeviceDTO value = gatewayOpt.get();
+                value.setChildDeviceSn(null);
+                deviceRedisService.setDeviceOnline(value);
+                this.pushDeviceOnlineTopo(value.getWorkspaceId(), gatewaySn, gatewaySn);
+                return true;
+            }
+
+            // When connecting for the first time
+            DeviceEntity gatewayDevice = deviceGatewayConvertToDeviceEntity(gateway);
+            return onlineSaveDevice(gatewayDevice, null, null).isPresent();
+        }
+
+        DeviceDTO deviceDTO = deviceOpt.get();
+        String deviceSn = deviceDTO.getChildDeviceSn();
+        if (!StringUtils.hasText(deviceSn)) {
             return true;
         }
-        // Handle the drone connected to the gateway device offline.
-        return this.subDeviceOffline(gatewaysList.get(0).getChildDeviceSn());
+
+        return subDeviceOffline(deviceSn);
     }
 
     @Override
     public Boolean subDeviceOffline(String deviceSn) {
+
+        // If no information about this device exists in the cache, the drone is considered to be offline.
+        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(deviceSn);
+        if (deviceOpt.isEmpty()) {
+            log.debug("The drone is already offline.");
+            return true;
+        }
+        DeviceDTO device = deviceOpt.get();
         // Cancel drone-related subscriptions.
         this.unsubscribeTopicOffline(deviceSn);
 
-        List<DeviceDTO> devicesList = this.getDevicesByParams(
-                DeviceQueryParam.builder()
-                        .deviceSn(deviceSn)
-                        .build());
+        capacityCameraService.deleteCapacityCameraByDeviceSn(deviceSn);
+        deviceRedisService.delDeviceOnline(deviceSn);
+        RedisOpsUtils.del(RedisConst.OSD_PREFIX + deviceSn);
+        deviceRedisService.delHmsKeysBySn(deviceSn);
+        // Publish the latest device topology information in the current workspace.
+        this.pushDeviceOfflineTopo(device.getWorkspaceId(), deviceSn);
 
-        // If no information about this drone exists in the database, the drone is considered to be offline.
-        if (devicesList.isEmpty()) {
-            log.debug("{} is already offline.", deviceSn);
-            return true;
-        }
-
-        List<String> ids = devicesList.stream()
-                .map(DeviceDTO::getDeviceSn)
-                .collect(Collectors.toList());
-
-        // Delete all data related to the drone.
-        boolean isDel = this.delDeviceByDeviceSns(ids);
-        payloadService.deletePayloadsByDeviceSn(ids);
-
-        log.debug("{} offline status: {}.", deviceSn, isDel);
-        return isDel;
+        log.debug("{} offline.", deviceSn);
+        return true;
     }
 
     @Override
     public Boolean deviceOnline(StatusGatewayReceiver deviceGateway) {
         String deviceSn = deviceGateway.getSubDevices().get(0).getSn();
 
-        List<DeviceDTO> devicesList = this.getDevicesByParams(
-                DeviceQueryParam.builder()
-                        .deviceSn(deviceSn)
-                        .build());
-        // If the information about this drone exists in the database, the drone is considered to be online.
-        if (!devicesList.isEmpty()) {
+        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(deviceSn);
+        Optional<DeviceDTO> gatewayOpt = deviceRedisService.getDeviceOnline(deviceGateway.getSn());
+
+        if (deviceOpt.isPresent() && gatewayOpt.isPresent()) {
+            DeviceDTO device = DeviceDTO.builder().loginTime(LocalDateTime.now()).deviceSn(deviceSn).build();
+            DeviceDTO gateway = DeviceDTO.builder()
+                    .loginTime(LocalDateTime.now())
+                    .deviceSn(deviceGateway.getSn())
+                    .childDeviceSn(deviceSn).build();
+            this.updateDevice(gateway);
+            this.updateDevice(device);
+            String workspaceId = deviceOpt.get().getWorkspaceId();
+            if (StringUtils.hasText(workspaceId)) {
+                this.subscribeTopicOnline(deviceSn);
+                this.subscribeTopicOnline(deviceGateway.getSn());
+            }
             log.warn("{} is already online.", deviceSn);
-            // Subscribe to topic related to drone and gateway devices.
-            this.subscribeTopicOnline(deviceGateway.getSn());
-            this.subscribeTopicOnline(deviceSn);
             return true;
         }
 
-        // Delete the gateway device information that was previously bound to the drone.
-        this.delDeviceByDeviceSns(
-                this.getDevicesByParams(
-                        DeviceQueryParam.builder()
-                                .childSn(deviceSn)
-                                .build())
-                        .stream()
-                        .map(DeviceDTO::getDeviceSn)
-                        .collect(Collectors.toList()));
+        List<DeviceDTO> gatewaysList = this.getDevicesByParams(
+                DeviceQueryParam.builder()
+                        .childSn(deviceSn)
+                        .build());
+        gatewaysList.stream()
+                .filter(gateway -> !gateway.getDeviceSn().equals(deviceGateway.getSn()))
+                .findAny()
+                .ifPresent(gateway -> {
+                    gateway.setChildDeviceSn("");
+                    this.updateDevice(gateway);
+                });
 
         DeviceEntity gateway = deviceGatewayConvertToDeviceEntity(deviceGateway);
-        gateway.setWorkspaceId(WorkspaceDTO.DEFAULT_WORKSPACE_ID);
-        // 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());
+        Optional<DeviceEntity> gatewayEntityOpt = onlineSaveDevice(gateway, deviceSn, null);
+        if (gatewayEntityOpt.isEmpty()) {
+            log.error("Failed to go online, please check the status data or code logic.");
+            return false;
+        }
 
         DeviceEntity subDevice = subDeviceConvertToDeviceEntity(deviceGateway.getSubDevices().get(0));
-        subDevice.setWorkspaceId(WorkspaceDTO.DEFAULT_WORKSPACE_ID);
-        // Set the icon of the drone device displayed in the pilot's map, required in the TSA module.
-        subDevice.setUrlSelect(IconUrlEnum.SELECT_EQUIPMENT.getUrl());
-        // 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());
-
-        gateway.setChildSn(subDevice.getDeviceSn());
-
-        boolean isSave = this.saveDevice(gateway) > 0 && this.saveDevice(subDevice) > 0;
-
-        log.debug(subDevice.getDeviceSn() + " online status: {}", isSave);
-        if (isSave) {
-            // Subscribe to topic related to drone and gateway devices.
-            this.subscribeTopicOnline(subDevice.getDeviceSn());
-            this.subscribeTopicOnline(gateway.getDeviceSn());
-
+        Optional<DeviceEntity> subDeviceEntityOpt = onlineSaveDevice(subDevice, null, gateway.getDeviceSn());
+        if (subDeviceEntityOpt.isEmpty()) {
+            log.error("Failed to go online, please check the status data or code logic.");
+            return false;
         }
-        return isSave;
+
+        subDevice = subDeviceEntityOpt.get();
+        gateway = gatewayEntityOpt.get();
+
+        // dock go online
+        if (DeviceDomainEnum.DOCK.getVal() == deviceGateway.getDomain() && !subDevice.getBoundStatus()) {
+            // Directly bind the drone of the dock to the same workspace as the dock.
+            bindDevice(DeviceDTO.builder().deviceSn(deviceSn).workspaceId(gateway.getWorkspaceId()).build());
+            subDevice.setWorkspaceId(gateway.getWorkspaceId());
+        }
+
+        // Subscribe to topic related to drone devices.
+        this.subscribeTopicOnline(deviceGateway.getSn());
+        this.subscribeTopicOnline(deviceSn);
+        this.pushDeviceOnlineTopo(subDevice.getWorkspaceId(), deviceGateway.getSn(), deviceSn);
+
+        log.debug("{} online.", subDevice.getDeviceSn());
+        return true;
     }
 
     @Override
@@ -188,16 +244,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);
+        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);
+        String prefix = THING_MODEL_PRE + PRODUCT + sn;
+        INIT_TOPICS_SUFFIX.forEach(suffix -> topicService.unsubscribe(prefix + suffix));
     }
 
     @Override
@@ -231,18 +285,26 @@
     public List<DeviceDTO> getDevicesByParams(DeviceQueryParam param) {
         return mapper.selectList(
                 new LambdaQueryWrapper<DeviceEntity>()
+                        //设备编码
                         .eq(StringUtils.hasText(param.getDeviceSn()),
-                                    DeviceEntity::getDeviceSn, param.getDeviceSn())
+                                DeviceEntity::getDeviceSn, param.getDeviceSn())
+                        //设备类型
                         .eq(param.getDeviceType() != null,
                                 DeviceEntity::getDeviceType, param.getDeviceType())
+                        //负载类型
                         .eq(param.getSubType() != null,
                                 DeviceEntity::getSubType, param.getSubType())
                         .eq(StringUtils.hasText(param.getChildSn()),
                                 DeviceEntity::getChildSn, param.getChildSn())
-                        .eq(param.getDomain() != null,
-                                DeviceEntity::getDomain, param.getDomain())
+                        .and(!CollectionUtils.isEmpty(param.getDomains()), wrapper -> {
+                            for (Integer domain : param.getDomains()) {
+                                wrapper.eq(DeviceEntity::getDomain, domain).or();
+                            }
+                        })
                         .eq(StringUtils.hasText(param.getWorkspaceId()),
                                 DeviceEntity::getWorkspaceId, param.getWorkspaceId())
+                        .eq(param.getBoundStatus() != null, DeviceEntity::getBoundStatus, param.getBoundStatus())
+                        .eq(param.getReserveId() != null, DeviceEntity::getReserveId, param.getReserveId())
                         .orderBy(param.isOrderBy(),
                                 param.isAsc(), DeviceEntity::getId))
                 .stream()
@@ -251,42 +313,56 @@
     }
 
     @Override
-    public List<DeviceDTO> getDevicesTopoForWeb(String workspaceId) {
+    public List<DeviceDTO> getDevicesTopoForWeb(String workspaceId, String reserveId) {
         List<DeviceDTO> devicesList = this.getDevicesByParams(
                 DeviceQueryParam.builder()
                         .workspaceId(workspaceId)
-                        .domain(DeviceDomainEnum.SUB_DEVICE.getVal())
+                        .reserveId(reserveId)
+                        .domains(List.of(DeviceDomainEnum.GATEWAY.getVal(), DeviceDomainEnum.DOCK.getVal()))
                         .build());
 
-        devicesList.forEach(device -> {
-            this.spliceDeviceTopo(device);
-            device.setWorkspaceId(workspaceId);
+        devicesList.stream()
+                .filter(gateway -> DeviceDomainEnum.DOCK.getVal() == gateway.getDomain() ||
+                        deviceRedisService.checkDeviceOnline(gateway.getDeviceSn()))
+                .forEach(this::spliceDeviceTopo);
 
+        devicesList.forEach(deviceDTO -> {
+            WaylineJobQueryParam waylineJobQueryParam = new WaylineJobQueryParam();
+            waylineJobQueryParam.setDockSn(deviceDTO.getDeviceSn());
+            WaylineJobEntity latestJob = waylineJobService.getLatestJob(workspaceId, waylineJobQueryParam);
+            deviceDTO.setLatestWaylineJob(latestJob);
+            deviceDTO.setOnlineStatus(true);
+            OsdDockReceiver osdDockReceiver = (OsdDockReceiver) RedisOpsUtils.get("osd:" + deviceDTO.getDeviceSn());
+            deviceDTO.setLatitude(osdDockReceiver!=null? osdDockReceiver.getLatitude():null);
+            deviceDTO.setLongitude(osdDockReceiver!=null? osdDockReceiver.getLongitude():null);
         });
+
         return devicesList;
     }
 
     @Override
-    public void spliceDeviceTopo(DeviceDTO device) {
+    public void spliceDeviceTopo(DeviceDTO gateway) {
 
-        // remote controller
-        List<DeviceDTO> gatewaysList = getDevicesByParams(
-                DeviceQueryParam.builder()
-                        .childSn(device.getDeviceSn())
-                        .build());
+        gateway.setStatus(deviceRedisService.checkDeviceOnline(gateway.getDeviceSn()));
+
+        // sub device
+        if (!StringUtils.hasText(gateway.getChildDeviceSn())) {
+            return;
+        }
+
+        DeviceDTO subDevice = getDevicesByParams(DeviceQueryParam.builder().deviceSn(gateway.getChildDeviceSn()).build()).get(0);
+        subDevice.setStatus(deviceRedisService.checkDeviceOnline(subDevice.getDeviceSn()));
+        gateway.setChildren(subDevice);
 
         // payloads
-        List<DevicePayloadDTO> payloadsList = payloadService
-                .getDevicePayloadEntitiesByDeviceSn(device.getDeviceSn());
-
-
-        device.setGatewaysList(gatewaysList);
-        device.setPayloadsList(payloadsList);
-
+        subDevice.setPayloadsList(payloadService.getDevicePayloadEntitiesByDeviceSn(gateway.getChildDeviceSn()));
     }
 
     @Override
     public Optional<TopologyDeviceDTO> getDeviceTopoForPilot(String sn) {
+        if (sn.isBlank()) {
+            return Optional.empty();
+        }
         List<TopologyDeviceDTO> topologyDeviceList = this.getDevicesByParams(
                 DeviceQueryParam.builder()
                         .deviceSn(sn)
@@ -301,7 +377,7 @@
     }
 
     @Override
-    public void pushDeviceOnlineTopo(Collection<ConcurrentWebSocketSession> sessions, String sn) {
+    public void pushDeviceOnlineTopo(Collection<ConcurrentWebSocketSession> sessions, String sn, String gatewaySn) {
 
         CustomWebSocketMessage<TopologyDeviceDTO> pilotMessage =
                 CustomWebSocketMessage.<TopologyDeviceDTO>builder()
@@ -312,6 +388,8 @@
 
         this.getDeviceTopoForPilot(sn)
                 .ifPresent(pilotMessage::setData);
+        pilotMessage.getData().setOnlineStatus(deviceRedisService.checkDeviceOnline(sn));
+        pilotMessage.getData().setGatewaySn(gatewaySn.equals(sn) ? "" : gatewaySn);
 
         sendMessageService.sendBatch(sessions, pilotMessage);
     }
@@ -321,91 +399,91 @@
         TopologyDeviceDTO.TopologyDeviceDTOBuilder builder = TopologyDeviceDTO.builder();
 
         if (device != null) {
-            String domain = String.valueOf(DeviceDomainEnum.getVal(device.getDomain()));
-            String subType = String.valueOf(device.getSubType());
-            String type = String.valueOf(device.getType());
-
             builder.sn(device.getDeviceSn())
-                    .deviceCallsign(device.getDeviceName())
+                    .deviceCallsign(device.getNickname())
                     .deviceModel(DeviceModelDTO.builder()
-                            .domain(domain)
-                            .subType(subType)
-                            .type(type)
-                            .key(domain + "-" + type + "-" + subType)
+                            .domain(String.valueOf(device.getDomain()))
+                            .subType(String.valueOf(device.getSubType()))
+                            .type(String.valueOf(device.getType()))
+                            .key(device.getDomain() + "-" + device.getType() + "-" + device.getSubType())
                             .build())
                     .iconUrls(device.getIconUrl())
+                    .onlineStatus(deviceRedisService.checkDeviceOnline(device.getDeviceSn()))
+                    .boundStatus(device.getBoundStatus())
+                    .model(device.getDeviceName())
+                    .userId(device.getUserId())
+                    .domain(device.getDomain())
                     .build();
         }
         return builder.build();
     }
 
     @Override
-    public void pushDeviceOnlineTopo(String workspaceId, String deviceSn, String gatewaySn) {
+    public void pushDeviceOnlineTopo(String workspaceId, String gatewaySn, String deviceSn) {
 
-        // All connected accounts on the pilot side of this workspace.
-        Collection<ConcurrentWebSocketSession> pilotSessions = WebSocketManager
-                .getValueWithWorkspaceAndUserType(
-                        workspaceId, UserTypeEnum.PILOT.getVal());
+        // All connected accounts in this workspace.
+        Collection<ConcurrentWebSocketSession> allSessions = webSocketManageService.getValueWithWorkspace(workspaceId);
 
-        this.pushDeviceOnlineTopo(pilotSessions, deviceSn);
-        this.pushDeviceOnlineTopo(pilotSessions, gatewaySn);
-        this.pushDeviceUpdateTopo(pilotSessions, deviceSn);
-        this.pushDeviceUpdateTopo(pilotSessions, gatewaySn);
-    }
-
-    @Override
-    public void pushDeviceOfflineTopo(String workspaceId, String gatewaySn) {
-        // All connected accounts on the pilot side of this workspace.
-        Collection<ConcurrentWebSocketSession> pilotSessions = WebSocketManager
-                .getValueWithWorkspaceAndUserType(
-                        workspaceId, UserTypeEnum.PILOT.getVal());
-
-
-        List<DeviceDTO> gatewaysList = this.getDevicesByParams(
-                DeviceQueryParam.builder()
-                        .deviceSn(gatewaySn)
-                        .build());
-
-        if (!gatewaysList.isEmpty()) {
-            String deviceSn = gatewaysList.get(0).getChildDeviceSn();
-            this.pushDeviceOfflineTopo(pilotSessions, deviceSn);
-            this.pushDeviceUpdateTopo(pilotSessions, deviceSn);
+        if (!gatewaySn.equals(deviceSn)) {
+            this.pushDeviceOnlineTopo(allSessions, deviceSn, gatewaySn);
+            this.pushDeviceUpdateTopo(allSessions, deviceSn);
         }
-
-        this.pushDeviceOfflineTopo(pilotSessions, gatewaySn);
-        this.pushDeviceUpdateTopo(pilotSessions, gatewaySn);
+        this.pushDeviceOnlineTopo(allSessions, gatewaySn, gatewaySn);
+        this.pushDeviceUpdateTopo(allSessions, gatewaySn);
     }
 
     @Override
-    public void handleOSD(String topic, byte[] payload) {
-        TopicStateReceiver receiver;
+    public void pushDeviceOfflineTopo(String workspaceId, String sn) {
+        // All connected accounts of this workspace.
+        Collection<ConcurrentWebSocketSession> allSessions = webSocketManageService
+                .getValueWithWorkspace(workspaceId);
+
+        this.pushDeviceOfflineTopo(allSessions, sn);
+        this.pushDeviceUpdateTopo(allSessions, sn);
+    }
+
+    @Override
+    @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));
 
-            List<DeviceDTO> deviceList = this.getDevicesByParams(
-                    DeviceQueryParam.builder().deviceSn(from).build());
-            if (deviceList.isEmpty()) {
-                return;
+            Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(from);
+
+            if (deviceOpt.isEmpty()) {
+                deviceOpt = this.getDeviceBySn(from);
+                if (deviceOpt.isEmpty()) {
+                    log.error("请重新启动无人机.");
+                    return;
+                }
+
+                if (!StringUtils.hasText(deviceOpt.get().getWorkspaceId())) {
+                    this.unsubscribeTopicOffline(from);
+                    return;
+                }
+                deviceRedisService.setDeviceOnline(deviceOpt.get());
+                this.subscribeTopicOnline(from);
             }
+            DeviceDTO device = deviceOpt.get();
+            deviceRedisService.setDeviceOnline(device);
 
-            ObjectMapper mapper = new ObjectMapper();
-            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+            receiver = objectMapper.readValue(payload, CommonTopicReceiver.class);
 
-            receiver = mapper.readValue(payload, TopicStateReceiver.class);
+            CustomWebSocketMessage<TelemetryDTO> wsMessage = CustomWebSocketMessage.<TelemetryDTO>builder()
+                    .timestamp(System.currentTimeMillis())
+                    .data(TelemetryDTO.builder().sn(from).build())
+                    .build();
 
-            CustomWebSocketMessage wsMessage = CustomWebSocketMessage.builder()
-                    .timestamp(System.currentTimeMillis()).build();
-
-            JsonNode hostNode = mapper.readTree(payload).findPath("data");
-
-            String workspaceId = deviceList.get(0).getWorkspaceId();
-            Collection<ConcurrentWebSocketSession> webSessions = WebSocketManager
+            Collection<ConcurrentWebSocketSession> webSessions = webSocketManageService
                     .getValueWithWorkspaceAndUserType(
-                            workspaceId, UserTypeEnum.WEB.getVal());
+                            device.getWorkspaceId(), UserTypeEnum.WEB.getVal());
 
 
-            tsaService.handleOSD(receiver, from, workspaceId, hostNode, webSessions, wsMessage);
+            tsaService.handleOSD(receiver, device, webSessions, wsMessage);
 
         } catch (IOException e) {
             e.printStackTrace();
@@ -451,17 +529,48 @@
      * @param entity
      * @return
      */
-    private Integer saveDevice(DeviceEntity entity) {
+    private Optional<DeviceEntity> saveDevice(DeviceEntity entity) {
         DeviceEntity deviceEntity = mapper.selectOne(
                 new LambdaQueryWrapper<DeviceEntity>()
                         .eq(DeviceEntity::getDeviceSn, entity.getDeviceSn()));
         // Update the information directly if the device already exists.
         if (deviceEntity != null) {
+            if (deviceEntity.getDeviceName().equals(entity.getNickname())) {
+                entity.setNickname(null);
+            }
             entity.setId(deviceEntity.getId());
             mapper.updateById(entity);
-            return deviceEntity.getId();
+            fillNullField(entity, deviceEntity);
+            return Optional.of(entity);
         }
-        return mapper.insert(entity) > 0 ? entity.getId() : 0;
+        return mapper.insert(entity) > 0 ? Optional.of(entity) : Optional.empty();
+    }
+
+    private void fillNullField(DeviceEntity entity, DeviceEntity oldEntity) {
+        if (Objects.isNull(entity) || Objects.isNull(oldEntity)) {
+            return;
+        }
+        if (Objects.isNull(entity.getWorkspaceId())) {
+            entity.setWorkspaceId(oldEntity.getWorkspaceId());
+        }
+        if (Objects.isNull(entity.getUserId())) {
+            entity.setUserId(oldEntity.getUserId());
+        }
+        if (Objects.isNull(entity.getChildSn())) {
+            entity.setChildSn(oldEntity.getChildSn());
+        }
+        if (Objects.isNull(entity.getBoundStatus())) {
+            entity.setBoundStatus(oldEntity.getBoundStatus());
+        }
+        if (Objects.isNull(entity.getBoundTime())) {
+            entity.setBoundTime(oldEntity.getBoundTime());
+        }
+        if (Objects.isNull(entity.getFirmwareVersion())) {
+            entity.setFirmwareVersion(oldEntity.getFirmwareVersion());
+        }
+        if (Objects.isNull(entity.getDeviceIndex())) {
+            entity.setDeviceIndex(oldEntity.getDeviceIndex());
+        }
     }
 
     /**
@@ -477,19 +586,23 @@
 
         // Query the model information of this gateway device.
         Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService
-                .getOneDictionaryInfoByDomainTypeSubType(gateway.getDomain(),
+                .getOneDictionaryInfoByTypeSubType(Objects.nonNull(gateway.getDomain()) ?
+                                gateway.getDomain() : DeviceDomainEnum.GATEWAY.getVal(),
                         gateway.getType(), gateway.getSubType());
 
         dictionaryOpt.ifPresent(entity ->
                 builder.deviceName(entity.getDeviceName())
+                        .nickname(entity.getDeviceName())
                         .deviceDesc(entity.getDeviceDesc()));
 
         return builder
                 .deviceSn(gateway.getSn())
-                .domain(gateway.getDomain())
                 .subType(gateway.getSubType())
                 .deviceType(gateway.getType())
                 .version(gateway.getVersion())
+                .domain(gateway.getDomain() != null ?
+                        gateway.getDomain() : DeviceDomainEnum.GATEWAY.getVal())
+                .deviceIndex(gateway.getSubDevices().isEmpty() ? null : gateway.getSubDevices().get(0).getIndex())
                 .build();
     }
 
@@ -506,48 +619,602 @@
 
         // Query the model information of this drone device.
         Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService
-                .getOneDictionaryInfoByDomainTypeSubType(DeviceDomainEnum.SUB_DEVICE.getVal(),
-                        device.getType(), device.getSubType());
+                .getOneDictionaryInfoByTypeSubType(DeviceDomainEnum.SUB_DEVICE.getVal(), device.getType(), device.getSubType());
 
         dictionaryOpt.ifPresent(dictionary ->
                 builder.deviceName(dictionary.getDeviceName())
+                        .nickname(dictionary.getDeviceName())
                         .deviceDesc(dictionary.getDeviceDesc()));
 
         return builder
                 .deviceSn(device.getSn())
                 .deviceType(device.getType())
                 .subType(device.getSubType())
-                .domain(DeviceDomainEnum.SUB_DEVICE.getVal())
                 .version(device.getVersion())
-                .deviceIndex(device.getIndex())
+                .domain(DeviceDomainEnum.SUB_DEVICE.getVal())
                 .build();
     }
 
     /**
-     * Convert database entity objects into device data transfer object.
+     * Convert database entity object into device data transfer object.
      * @param entity
      * @return
      */
     private DeviceDTO deviceEntityConvertToDTO(DeviceEntity entity) {
-        DeviceDTO.DeviceDTOBuilder builder = DeviceDTO.builder();
-
-        if (entity != null) {
-            builder.deviceSn(entity.getDeviceSn())
-                    .childDeviceSn(entity.getChildSn())
-                    .deviceName(entity.getDeviceName())
-                    .deviceDesc(entity.getDeviceDesc())
-                    .deviceIndex(entity.getDeviceIndex())
-                    .workspaceId(entity.getWorkspaceId())
-                    .type(entity.getDeviceType())
-                    .subType(entity.getSubType())
-                    .domain(DeviceDomainEnum.getDesc(entity.getDomain()))
-                    .iconUrl(IconUrlDTO.builder()
-                            .normalUrl(entity.getUrlNormal())
-                            .selectUrl(entity.getUrlSelect())
-                            .build())
-                    .build();
+        if (entity == null) {
+            return null;
         }
-        return builder.build();
+        DeviceDTO deviceDTO = DeviceDTO.builder()
+                .deviceSn(entity.getDeviceSn())
+                .childDeviceSn(entity.getChildSn())
+                .deviceName(entity.getDeviceName())
+                .deviceDesc(entity.getDeviceDesc())
+                .controlSource(entity.getDeviceIndex())
+                .workspaceId(entity.getWorkspaceId())
+                .type(entity.getDeviceType())
+                .subType(entity.getSubType())
+                .domain(entity.getDomain())
+                .iconUrl(IconUrlDTO.builder()
+                        .normalUrl(entity.getUrlNormal())
+                        .selectUrl(entity.getUrlSelect())
+                        .build())
+                .boundStatus(entity.getBoundStatus())
+                .loginTime(entity.getLoginTime() != null ?
+                        LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getLoginTime()), ZoneId.systemDefault())
+                        : null)
+                .boundTime(entity.getBoundTime() != null ?
+                        LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getBoundTime()), ZoneId.systemDefault())
+                        : null)
+                .nickname(entity.getNickname())
+                .firmwareVersion(entity.getFirmwareVersion())
+                .workspaceName(entity.getWorkspaceId() != null ?
+                        workspaceService.getWorkspaceByWorkspaceId(entity.getWorkspaceId())
+                                .map(WorkspaceDTO::getWorkspaceName).orElse("") : "")
+                .firmwareStatus(DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal()).build();
+
+        addFirmwareStatus(deviceDTO, entity);
+        return deviceDTO;
     }
 
-}
\ No newline at end of file
+    private void addFirmwareStatus(DeviceDTO deviceDTO, DeviceEntity entity) {
+        if (!StringUtils.hasText(entity.getFirmwareVersion())) {
+            return;
+        }
+        // Query whether the device is updating firmware.
+        Optional<EventsReceiver<EventsOutputProgressReceiver<FirmwareProgressExtReceiver>>> progressOpt =
+                deviceRedisService.getFirmwareUpgradingProgress(entity.getDeviceSn());
+        if (progressOpt.isPresent()) {
+            deviceDTO.setFirmwareStatus(DeviceFirmwareStatusEnum.UPGRADING.getVal());
+            deviceDTO.setFirmwareProgress(progressOpt.map(EventsReceiver::getOutput)
+                            .map(EventsOutputProgressReceiver::getProgress)
+                            .map(OutputProgressReceiver::getPercent)
+                            .orElse(0));
+            return;
+        }
+
+        // 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.isEmpty()) {
+            deviceDTO.setFirmwareStatus(DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal());
+            return;
+        }
+        if (entity.getFirmwareVersion().equals(firmwareReleaseNoteOpt.get().getProductVersion())) {
+            deviceDTO.setFirmwareStatus(Objects.requireNonNullElse(entity.getCompatibleStatus(), true) ?
+                    DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal() :
+                    DeviceFirmwareStatusEnum.CONSISTENT_UPGRADE.getVal());
+            return;
+        }
+        deviceDTO.setFirmwareStatus(DeviceFirmwareStatusEnum.NORMAL_UPGRADE.getVal());
+    }
+
+    @Override
+    public Boolean updateDevice(DeviceDTO deviceDTO) {
+        int update = mapper.update(this.deviceDTO2Entity(deviceDTO),
+                new LambdaUpdateWrapper<DeviceEntity>().eq(DeviceEntity::getDeviceSn, deviceDTO.getDeviceSn()));
+        return update > 0;
+    }
+
+    @Override
+    public Boolean bindDevice(DeviceDTO device) {
+        device.setBoundStatus(true);
+        device.setBoundTime(LocalDateTime.now());
+
+        boolean isUpd = this.saveDevice(this.deviceDTO2Entity(device)).isPresent();
+        if (!isUpd) {
+            return false;
+        }
+
+        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(device.getDeviceSn());
+        if (deviceOpt.isEmpty()) {
+            return false;
+        }
+
+        DeviceDTO redisDevice = deviceOpt.get();
+        redisDevice.setWorkspaceId(device.getWorkspaceId());
+        deviceRedisService.setDeviceOnline(redisDevice);
+
+        if (DeviceDomainEnum.GATEWAY.getVal() == redisDevice.getDomain()) {
+            this.pushDeviceOnlineTopo(webSocketManageService.getValueWithWorkspace(device.getWorkspaceId()),
+                    device.getDeviceSn(), device.getDeviceSn());
+        }
+        if (DeviceDomainEnum.SUB_DEVICE.getVal() == redisDevice.getDomain()) {
+            DeviceDTO subDevice = this.getDevicesByParams(DeviceQueryParam.builder()
+                    .childSn(device.getChildDeviceSn())
+                    .build()).get(0);
+            this.pushDeviceOnlineTopo(webSocketManageService.getValueWithWorkspace(device.getWorkspaceId()),
+                    device.getDeviceSn(), subDevice.getDeviceSn());
+        }
+        this.subscribeTopicOnline(device.getDeviceSn());
+
+        return true;
+    }
+
+    /**
+     * Handle dock binding status requests.
+     * @param receiver
+     * @param headers
+     */
+    @ServiceActivator(inputChannel = ChannelName.INBOUND_REQUESTS_AIRPORT_BIND_STATUS, outputChannel = ChannelName.OUTBOUND)
+    public void bindStatus(CommonTopicReceiver receiver, MessageHeaders headers) {
+        List<Map<String, String>> data = ((Map<String, List<Map<String, String>>>) receiver.getData()).get(MapKeyConst.DEVICES);
+        String dockSn = data.get(0).get(MapKeyConst.SN);
+        String droneSn = data.size() > 1 ? data.get(1).get(MapKeyConst.SN) : "null";
+
+        Optional<DeviceDTO> dockOpt = this.getDeviceBySn(dockSn);
+        Optional<DeviceDTO> droneOpt = this.getDeviceBySn(droneSn);
+
+        List<BindStatusReceiver> bindStatusResult = new ArrayList<>();
+        bindStatusResult.add(dockOpt.isPresent() ? this.dto2BindStatus(dockOpt.get()) :
+                BindStatusReceiver.builder().sn(dockSn).isDeviceBindOrganization(false).build());
+        if (data.size() > 1) {
+            bindStatusResult.add(droneOpt.isPresent() ? this.dto2BindStatus(droneOpt.get()) :
+                    BindStatusReceiver.builder().sn(droneSn).isDeviceBindOrganization(false).build());
+        }
+
+        messageSender.publish(headers.get(MqttHeaders.RECEIVED_TOPIC) + _REPLY_SUF,
+                CommonTopicResponse.builder()
+                        .tid(receiver.getTid())
+                        .bid(receiver.getBid())
+                        .timestamp(System.currentTimeMillis())
+                        .method(RequestsMethodEnum.AIRPORT_BIND_STATUS.getMethod())
+                        .data(RequestsReply.success(Map.of(MapKeyConst.BIND_STATUS, bindStatusResult)))
+                        .build());
+
+    }
+
+    /**
+     * Handle dock binding requests.
+     * @param receiver
+     * @param headers
+     */
+    @ServiceActivator(inputChannel = ChannelName.INBOUND_REQUESTS_AIRPORT_ORGANIZATION_BIND)
+    public void bindDevice(CommonTopicReceiver receiver, MessageHeaders headers) {
+        Map<String, List<BindDeviceReceiver>> data = objectMapper.convertValue(receiver.getData(),
+                new TypeReference<Map<String, List<BindDeviceReceiver>>>() {});
+        List<BindDeviceReceiver> devices = data.get(MapKeyConst.BIND_DEVICES);
+        BindDeviceReceiver dock = null;
+        BindDeviceReceiver drone = null;
+        for (BindDeviceReceiver device : devices) {
+            int val = Integer.parseInt(device.getDeviceModelKey().split("-")[0]);
+            if (val == DeviceDomainEnum.DOCK.getVal()) {
+                dock = device;
+            }
+            if (val == DeviceDomainEnum.SUB_DEVICE.getVal()) {
+                drone = device;
+            }
+        }
+
+        Optional<DeviceEntity> dockEntityOpt = this.bindDevice2Entity(DeviceDomainEnum.DOCK.getVal(), dock);
+        Optional<DeviceEntity> droneEntityOpt = this.bindDevice2Entity(DeviceDomainEnum.SUB_DEVICE.getVal(), drone);
+
+        List<ErrorInfoReply> bindResult = new ArrayList<>();
+
+        droneEntityOpt.ifPresent(droneEntity -> {
+            dockEntityOpt.get().setChildSn(droneEntity.getDeviceSn());
+            Optional<DeviceEntity> deviceEntityOpt = this.saveDevice(droneEntity);
+            bindResult.add(
+                    deviceEntityOpt.isPresent() ?
+                        ErrorInfoReply.success(droneEntity.getDeviceSn()) :
+                        new ErrorInfoReply(droneEntity.getDeviceSn(),
+                                CommonErrorEnum.DEVICE_BINDING_FAILED.getErrorCode())
+            );
+        });
+        Optional<DeviceEntity> dockOpt = this.saveDevice(dockEntityOpt.get());
+
+        bindResult.add(dockOpt.isPresent() ?
+                ErrorInfoReply.success(dock.getSn()) :
+                    new ErrorInfoReply(dock.getSn(),
+                            CommonErrorEnum.DEVICE_BINDING_FAILED.getErrorCode()));
+
+        String topic = headers.get(MqttHeaders.RECEIVED_TOPIC) + _REPLY_SUF;
+        messageSender.publish(topic,
+                CommonTopicResponse.builder()
+                        .tid(receiver.getTid())
+                        .bid(receiver.getBid())
+                        .method(RequestsMethodEnum.AIRPORT_ORGANIZATION_BIND.getMethod())
+                        .timestamp(System.currentTimeMillis())
+                        .data(RequestsReply.success(Map.of(MapKeyConst.ERR_INFOS, bindResult)))
+                        .build());
+
+    }
+
+    @Override
+    public PaginationData<DeviceDTO> getBoundDevicesWithDomain(String workspaceId, Long page,
+                                                               Long pageSize, Integer domain) {
+
+        Page<DeviceEntity> pagination = mapper.selectPage(new Page<>(page, pageSize),
+                new LambdaQueryWrapper<DeviceEntity>()
+                        .eq(DeviceEntity::getDomain, domain)
+                        .eq(DeviceEntity::getWorkspaceId, workspaceId)
+                        .eq(DeviceEntity::getBoundStatus, true));
+        List<DeviceDTO> devicesList = pagination.getRecords().stream().map(this::deviceEntityConvertToDTO)
+                .peek(device -> {
+                    device.setStatus(deviceRedisService.checkDeviceOnline(device.getDeviceSn()));
+                    if (StringUtils.hasText(device.getChildDeviceSn())) {
+                        Optional<DeviceDTO> childOpt = this.getDeviceBySn(device.getChildDeviceSn());
+                        childOpt.ifPresent(child -> {
+                            child.setStatus(deviceRedisService.checkDeviceOnline(child.getDeviceSn()));
+                            child.setWorkspaceName(device.getWorkspaceName());
+                            device.setChildren(child);
+                        });
+                    }
+                })
+                .collect(Collectors.toList());
+        return new PaginationData<DeviceDTO>(devicesList, new Pagination(pagination));
+    }
+
+    @Override
+    public void unbindDevice(String deviceSn) {
+
+        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(deviceSn);
+        if (deviceOpt.isPresent()) {
+            subDeviceOffline(deviceSn);
+        } else {
+            deviceOpt = getDeviceBySn(deviceSn);
+        }
+        if (deviceOpt.isEmpty()) {
+            return;
+        }
+        DeviceDTO device = DeviceDTO.builder()
+                .deviceSn(deviceSn)
+                .workspaceId("")
+                .userId("")
+                .boundStatus(false)
+                .build();
+        this.updateDevice(device);
+    }
+
+    @Override
+    public Optional<DeviceDTO> getDeviceBySn(String sn) {
+        List<DeviceDTO> devicesList = this.getDevicesByParams(DeviceQueryParam.builder().deviceSn(sn).build());
+        if (devicesList.isEmpty()) {
+            return Optional.empty();
+        }
+        DeviceDTO device = devicesList.get(0);
+        device.setStatus(deviceRedisService.checkDeviceOnline(sn));
+        return Optional.of(device);
+    }
+
+    /**
+     * Update the firmware version information of the device or payload.
+     * @param receiver
+     */
+    @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) {
+            final DeviceDTO device = DeviceDTO.builder()
+                    .deviceSn(receiver.getSn())
+                    .firmwareVersion(receiver.getFirmwareVersion())
+                    .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(workspaceId, upgradeDTOS);
+        if (deviceOtaFirmwares.isEmpty()) {
+            return ResponseResult.error();
+        }
+
+        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(deviceOtaFirmwares.get(0).getSn());
+        if (deviceOpt.isEmpty()) {
+            throw new RuntimeException("设备离线");
+        }
+        DeviceDTO device = deviceOpt.get();
+        String gatewaySn = DeviceDomainEnum.DOCK.getVal() == device.getDomain() ? device.getDeviceSn() : device.getParentSn();
+
+        checkOtaConditions(gatewaySn);
+
+        String bid = UUID.randomUUID().toString();
+        ServiceReply serviceReply = messageSender.publishServicesTopic(
+                gatewaySn, FirmwareMethodEnum.OTA_CREATE.getMethod(), Map.of(MapKeyConst.DEVICES, deviceOtaFirmwares), bid);
+        if (serviceReply.getResult() != ResponseResult.CODE_SUCCESS) {
+            return ResponseResult.error(serviceReply.getResult(), "硬件错误码 " + serviceReply.getResult());
+        }
+
+        // Record the device state that needs to be updated.
+        deviceOtaFirmwares.forEach(deviceOta -> deviceRedisService.setFirmwareUpgrading(deviceOta.getSn(),
+                EventsReceiver.<EventsOutputProgressReceiver<FirmwareProgressExtReceiver>>builder()
+                        .bid(bid).sn(deviceOta.getSn()).build()));
+        return ResponseResult.success();
+    }
+
+    /**
+     * Determine whether the firmware can be upgraded.
+     * @param dockSn
+     */
+    private void checkOtaConditions(String dockSn) {
+        Optional<OsdDockReceiver> deviceOpt = deviceRedisService.getDeviceOsd(dockSn, OsdDockReceiver.class);
+        if (deviceOpt.isEmpty()) {
+            throw new RuntimeException("机场离线");
+        }
+        boolean emergencyStopState = deviceOpt.get().getEmergencyStopState();
+        if (emergencyStopState) {
+//            throw new RuntimeException("The emergency stop button of the dock is pressed and can't be upgraded.");
+            throw new RuntimeException("机场急停按钮被按下,无法升级");
+        }
+
+        DockModeCodeEnum dockMode = this.getDockMode(dockSn);
+        if (DockModeCodeEnum.IDLE != dockMode) {
+            throw new RuntimeException("当前机场状态无法升级");
+        }
+    }
+
+    @Override
+    public void devicePropertySet(String workspaceId, String dockSn, DeviceSetPropertyEnum propertyEnum, JsonNode param) {
+        Optional<DeviceDTO> dockOpt = deviceRedisService.getDeviceOnline(dockSn);
+        if (dockOpt.isEmpty()) {
+            throw new RuntimeException("机场离线");
+        }
+        String childSn = dockOpt.get().getChildDeviceSn();
+        boolean deviceOnline = deviceRedisService.checkDeviceOnline(childSn);
+        Optional<OsdSubDeviceReceiver> osdOpt = deviceRedisService.getDeviceOsd(childSn, OsdSubDeviceReceiver.class);
+        if (!deviceOnline || osdOpt.isEmpty()) {
+            throw new RuntimeException("设备离线");
+        }
+
+        // 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;
+        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(), osdOpt.get());
+            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());
+
+        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("设置失败" + value.getKey() + "; 错误码: " + setReply.getResult());
+        }
+
+    }
+
+    @Override
+    public DockModeCodeEnum getDockMode(String dockSn) {
+        return deviceRedisService.getDeviceOsd(dockSn, OsdDockReceiver.class)
+                .map(OsdDockReceiver::getModeCode).orElse(DockModeCodeEnum.DISCONNECTED);
+    }
+
+    @Override
+    public DeviceModeCodeEnum getDeviceMode(String deviceSn) {
+        return deviceRedisService.getDeviceOsd(deviceSn, OsdSubDeviceReceiver.class)
+                .map(OsdSubDeviceReceiver::getModeCode).orElse(DeviceModeCodeEnum.DISCONNECTED);
+    }
+
+    @Override
+    public Boolean checkDockDrcMode(String dockSn) {
+        return deviceRedisService.getDeviceOsd(dockSn, OsdDockReceiver.class)
+                .map(OsdDockReceiver::getDrcState)
+                .orElse(DockDrcStateEnum.DISCONNECTED) != DockDrcStateEnum.DISCONNECTED;
+    }
+
+    @Override
+    public Boolean checkAuthorityFlight(String gatewaySn) {
+        return deviceRedisService.getDeviceOnline(gatewaySn).flatMap(gateway ->
+                Optional.of((DeviceDomainEnum.DOCK.getVal() == gateway.getDomain()
+                        || DeviceDomainEnum.GATEWAY.getVal() == gateway.getDomain())
+                    && ControlSourceEnum.A.getControlSource().equals(gateway.getControlSource())))
+                .orElse(true);
+    }
+
+    /**
+     * Convert device data transfer object into database entity object.
+     * @param dto
+     * @return
+     */
+    private DeviceEntity deviceDTO2Entity(DeviceDTO dto) {
+        DeviceEntity.DeviceEntityBuilder builder = DeviceEntity.builder();
+        if (dto == null) {
+            return builder.build();
+        }
+
+        return builder.deviceSn(dto.getDeviceSn())
+                .userId(dto.getUserId())
+                .nickname(dto.getNickname())
+                .workspaceId(dto.getWorkspaceId())
+                .boundStatus(dto.getBoundStatus())
+                .loginTime(dto.getLoginTime() != null ?
+                        dto.getLoginTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : null)
+                .boundTime(dto.getBoundTime() != null ?
+                        dto.getBoundTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : null)
+                .childSn(dto.getChildDeviceSn())
+                .domain(dto.getDomain())
+                .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(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(domain, droneKey[1], droneKey[2]);
+        DeviceEntity.DeviceEntityBuilder builder = DeviceEntity.builder();
+
+        dictionaryOpt.ifPresent(entity ->
+                builder.deviceName(entity.getDeviceName())
+                        .nickname(entity.getDeviceName())
+                        .deviceDesc(entity.getDeviceDesc()));
+
+        Optional<WorkspaceDTO> workspace = workspaceService.getWorkspaceNameByBindCode(receiver.getDeviceBindingCode());
+
+        DeviceEntity entity = builder
+                .workspaceId(workspace.isPresent() ? workspace.get().getWorkspaceId() : receiver.getOrganizationId())
+                .domain(droneKey[0])
+                .deviceType(droneKey[1])
+                .subType(droneKey[2])
+                .deviceSn(receiver.getSn())
+                .boundStatus(true)
+                .loginTime(System.currentTimeMillis())
+                .boundTime(System.currentTimeMillis())
+                .urlSelect(IconUrlEnum.SELECT_EQUIPMENT.getUrl())
+                .urlNormal(IconUrlEnum.NORMAL_EQUIPMENT.getUrl())
+                .build();
+        if (StringUtils.hasText(receiver.getDeviceCallsign())) {
+            entity.setNickname(receiver.getDeviceCallsign());
+        }
+        return Optional.of(entity);
+    }
+
+    /**
+     * Convert device data transfer object into device binding status data object.
+     * @param device
+     * @return
+     */
+    private BindStatusReceiver dto2BindStatus(DeviceDTO device) {
+        if (device == null) {
+            return null;
+        }
+        return BindStatusReceiver.builder()
+                .sn(device.getDeviceSn())
+                .deviceCallsign(device.getNickname())
+                .isDeviceBindOrganization(device.getBoundStatus())
+                .organizationId(device.getWorkspaceId())
+                .organizationName(device.getWorkspaceName())
+                .build();
+    }
+
+    private Optional<DeviceEntity> onlineSaveDevice(DeviceEntity device, String childSn, String parentSn) {
+
+        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());
+            device.setBoundStatus(false);
+        } else {
+            DeviceDTO oldDevice = deviceOpt.get();
+            device.setNickname(oldDevice.getNickname());
+            device.setBoundStatus(oldDevice.getBoundStatus());
+        }
+
+        device.setChildSn(childSn);
+        device.setLoginTime(System.currentTimeMillis());
+
+        Optional<DeviceEntity> saveDeviceOpt = this.saveDevice(device);
+        if (saveDeviceOpt.isEmpty()) {
+            return saveDeviceOpt;
+        }
+
+        DeviceDTO redisDevice = this.deviceEntityConvertToDTO(saveDeviceOpt.get());
+        redisDevice.setParentSn(parentSn);
+
+        deviceRedisService.setDeviceOnline(redisDevice);
+
+        return saveDeviceOpt;
+    }
+
+    /**
+     * Handles messages in the state topic about basic drone data.
+     *
+     * Note: Only the data of the drone payload is handled here. You can handle other data from the drone
+     * according to your business needs.
+     * @param deviceBasic   basic drone data
+     */
+    @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_BASIC, outputChannel = ChannelName.INBOUND_STATE_PAYLOAD)
+    public List<DevicePayloadReceiver> stateBasic(DeviceBasicReceiver deviceBasic) {
+        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(deviceBasic.getDeviceSn());
+        if (deviceOpt.isEmpty()) {
+            return deviceBasic.getPayloads();
+        }
+        Optional<DeviceDTO> dockOpt = deviceRedisService.getDeviceOnline(deviceOpt.get().getParentSn());
+        if (dockOpt.isEmpty()) {
+            return deviceBasic.getPayloads();
+        }
+        DeviceDTO dock = dockOpt.get();
+        if (!deviceBasic.getControlSource().equals(dock.getControlSource())) {
+            dock.setControlSource(deviceBasic.getControlSource());
+            deviceRedisService.setDeviceOnline(dock);
+
+            sendMessageService.sendBatch(dock.getWorkspaceId(), UserTypeEnum.WEB.getVal(),
+                    BizCodeEnum.CONTROL_SOURCE_CHANGE.getCode(),
+                    DeviceAuthorityDTO.builder()
+                            .controlSource(dock.getControlSource())
+                            .sn(dock.getDeviceSn())
+                            .type(DroneAuthorityEnum.FLIGHT)
+                            .build());
+
+        }
+        return deviceBasic.getPayloads();
+    }
+}

--
Gitblit v1.9.3