sean.zhou
2023-02-24 a7aaeabc7873a0eafb4a7ecad7f65b018b7a9bc9
src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java
@@ -20,21 +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.DeviceFirmwareStatusEnum;
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;
@@ -87,9 +86,6 @@
    private ObjectMapper objectMapper;
    @Autowired
    private RedisOpsUtils redisOps;
    @Autowired
    private IWebSocketManageService webSocketManageService;
    @Autowired
@@ -99,38 +95,35 @@
    @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 onlineSaveDevice(gatewayDevice, null).isPresent();
        }
        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;
        }
@@ -139,21 +132,24 @@
    @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());
        RedisOpsUtils.del(RedisConst.HMS_PREFIX + device.getDeviceSn());
        log.debug("{} offline.", deviceSn);
        return true;
    }
@@ -163,12 +159,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 gatewayTime = redisOps.getExpire(RedisConst.DEVICE_ONLINE_PREFIX + deviceGateway.getSn());
        long now = System.currentTimeMillis();
        long time = RedisOpsUtils.getExpire(key);
        long gatewayTime = RedisOpsUtils.getExpire(RedisConst.DEVICE_ONLINE_PREFIX + deviceGateway.getSn());
        if (time > 0 && gatewayTime > 0) {
            redisOps.expireKey(key, RedisConst.DEVICE_ALIVE_SECOND);
            RedisOpsUtils.expireKey(key, RedisConst.DEVICE_ALIVE_SECOND);
            DeviceDTO device = DeviceDTO.builder().loginTime(LocalDateTime.now()).deviceSn(deviceSn).build();
            DeviceDTO gateway = DeviceDTO.builder()
                    .loginTime(LocalDateTime.now())
@@ -176,7 +171,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());
@@ -189,69 +184,44 @@
                DeviceQueryParam.builder()
                        .childSn(deviceSn)
                        .build());
        gatewaysList.stream().filter(
                gateway -> !gateway.getDeviceSn().equals(deviceGateway.getSn()))
        gatewaysList.stream()
                .filter(gateway -> !gateway.getDeviceSn().equals(deviceGateway.getSn()))
                .findAny()
                .ifPresent(gateway -> {
                    gateway.setChildDeviceSn("");
                    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);
        Optional<DeviceEntity> gatewayEntityOpt = onlineSaveDevice(gateway, deviceSn);
        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));
        // 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);
        Optional<DeviceEntity> subDeviceEntityOpt = onlineSaveDevice(subDevice, null);
        if (subDeviceEntityOpt.isEmpty()) {
            log.error("Failed to go online, please check the status data or code logic.");
            return false;
        }
        subDevice = subDeviceEntityOpt.get();
        gateway = gatewayEntityOpt.get();
        // 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.");
                return false;
            }
            gateway.setNickname(null);
            subDevice.setNickname(null);
        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());
        }
        Optional<DeviceEntity> gatewayOpt = this.saveDevice(gateway);
        String workspaceId = this.saveDevice(subDevice).orElse(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);
        }
        // Subscribe to topic related to drone devices.
        this.subscribeTopicOnline(deviceSn);
        this.subscribeTopicOnline(deviceGateway.getSn());
        this.subscribeTopicOnline(deviceSn);
        this.pushDeviceOnlineTopo(subDevice.getWorkspaceId(), deviceGateway.getSn(), deviceSn);
        log.debug("{} online.", subDevice.getDeviceSn());
        return true;
    }
@@ -264,20 +234,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
@@ -339,34 +303,33 @@
        List<DeviceDTO> devicesList = this.getDevicesByParams(
                DeviceQueryParam.builder()
                        .workspaceId(workspaceId)
                        .domains(List.of(DeviceDomainEnum.SUB_DEVICE.getVal()))
                        .domains(List.of(DeviceDomainEnum.GATEWAY.getVal(), DeviceDomainEnum.DOCK.getVal()))
                        .build());
        devicesList.forEach(device -> {
            this.spliceDeviceTopo(device);
            device.setWorkspaceId(workspaceId);
            device.setStatus(redisOps.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn()));
        });
        devicesList.stream()
                .filter(gateway -> DeviceDomainEnum.DOCK.getVal() == gateway.getDomain() ||
                        RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + gateway.getDeviceSn()))
                .forEach(this::spliceDeviceTopo);
        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(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + gateway.getDeviceSn()));
        // sub device
        if (!StringUtils.hasText(gateway.getChildDeviceSn())) {
            return;
        }
        DeviceDTO subDevice = getDevicesByParams(DeviceQueryParam.builder().deviceSn(gateway.getChildDeviceSn()).build()).get(0);
        subDevice.setStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + 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
@@ -399,7 +362,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);
@@ -411,24 +374,20 @@
        TopologyDeviceDTO.TopologyDeviceDTOBuilder builder = TopologyDeviceDTO.builder();
        if (device != null) {
            int domain = DeviceDomainEnum.getVal(device.getDomain());
            String subType = String.valueOf(device.getSubType());
            String type = String.valueOf(device.getType());
            builder.sn(device.getDeviceSn())
                    .deviceCallsign(device.getNickname())
                    .deviceModel(DeviceModelDTO.builder()
                            .domain(String.valueOf(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(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())
                    .domain(DeviceDomainEnum.getDesc(domain))
                    .domain(device.getDomain())
                    .build();
        }
        return builder.build();
@@ -459,16 +418,19 @@
    }
    @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) {
                Optional<DeviceDTO> deviceOpt = this.getDeviceBySn(from);
@@ -477,9 +439,10 @@
                }
                device = deviceOpt.get();
                if (!StringUtils.hasText(device.getWorkspaceId())) {
                    this.unsubscribeTopicOffline(from);
                    return;
                }
                redisOps.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + from, device,
                RedisOpsUtils.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + from, device,
                        RedisConst.DEVICE_ALIVE_SECOND);
                this.subscribeTopicOnline(from);
            }
@@ -548,6 +511,9 @@
                        .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 Optional.of(deviceEntity);
@@ -568,7 +534,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())
@@ -598,7 +566,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())
@@ -611,8 +579,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();
    }
@@ -634,7 +601,7 @@
                .workspaceId(entity.getWorkspaceId())
                .type(entity.getDeviceType())
                .subType(entity.getSubType())
                .domain(DeviceDomainEnum.getDesc(entity.getDomain()))
                .domain(entity.getDomain())
                .iconUrl(IconUrlDTO.builder()
                        .normalUrl(entity.getUrlNormal())
                        .selectUrl(entity.getUrlSelect())
@@ -656,7 +623,7 @@
            return deviceDTOBuilder.firmwareStatus(DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal()).build();
        }
        // Query whether the device is updating firmware.
        Object progress = redisOps.get(RedisConst.FIRMWARE_UPGRADING_PREFIX + entity.getDeviceSn());
        Object progress = RedisOpsUtils.get(RedisConst.FIRMWARE_UPGRADING_PREFIX + entity.getDeviceSn());
        if (Objects.nonNull(progress)) {
            return deviceDTOBuilder.firmwareStatus(DeviceFirmwareStatusEnum.UPGRADING.getVal()).firmwareProgress((int)progress).build();
        }
@@ -690,23 +657,24 @@
        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);
        redisDevice.setWorkspaceId(device.getWorkspaceId());
        redisOps.setWithExpire(key, redisDevice, RedisConst.DEVICE_ALIVE_SECOND);
        if (!RedisOpsUtils.checkExist(key)) {
            return false;
        }
        if (DeviceDomainEnum.GATEWAY.getDesc().equals(redisDevice.getDomain())) {
        DeviceDTO redisDevice = (DeviceDTO)RedisOpsUtils.get(key);
        redisDevice.setWorkspaceId(device.getWorkspaceId());
        RedisOpsUtils.setWithExpire(key, redisDevice, RedisConst.DEVICE_ALIVE_SECOND);
        if (DeviceDomainEnum.GATEWAY.getVal() == redisDevice.getDomain()) {
            this.pushDeviceOnlineTopo(webSocketManageService.getValueWithWorkspace(device.getWorkspaceId()),
                    device.getDeviceSn(), device.getDeviceSn());
        }
        if (DeviceDomainEnum.SUB_DEVICE.getDesc().equals(redisDevice.getDomain())) {
        if (DeviceDomainEnum.SUB_DEVICE.getVal() == redisDevice.getDomain()) {
            DeviceDTO subDevice = this.getDevicesByParams(DeviceQueryParam.builder()
                    .childSn(device.getChildDeviceSn())
                    .build()).get(0);
@@ -767,8 +735,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<>();
@@ -803,21 +771,21 @@
    @Override
    public PaginationData<DeviceDTO> getBoundDevicesWithDomain(String workspaceId, Long page,
                                                               Long pageSize, String domain) {
                                                               Long pageSize, Integer domain) {
        Page<DeviceEntity> pagination = mapper.selectPage(new Page<>(page, pageSize),
                new LambdaQueryWrapper<DeviceEntity>()
                        .eq(DeviceEntity::getDomain, DeviceDomainEnum.getVal(domain))
                        .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(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);
                        });
@@ -830,9 +798,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)
@@ -850,12 +818,18 @@
            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) {
            final DeviceDTO device = DeviceDTO.builder()
                    .deviceSn(receiver.getSn())
@@ -872,7 +846,7 @@
    @Override
    public ResponseResult createDeviceOtaJob(String workspaceId, List<DeviceFirmwareUpgradeDTO> upgradeDTOS) {
        List<DeviceOtaCreateParam> deviceOtaFirmwares = deviceFirmwareService.getDeviceOtaFirmware(upgradeDTOS);
        List<DeviceOtaCreateParam> deviceOtaFirmwares = deviceFirmwareService.getDeviceOtaFirmware(workspaceId, upgradeDTOS);
        if (deviceOtaFirmwares.isEmpty()) {
            return ResponseResult.error();
        }
@@ -885,29 +859,97 @@
        // The bids in the progress messages reported subsequently are the same.
        String bid = UUID.randomUUID().toString();
        Optional<ServiceReply> serviceReplyOpt = messageSender.publishWithReply(
        ServiceReply serviceReply = messageSender.publishWithReply(
                topic, CommonTopicResponse.<Map<String, List<DeviceOtaCreateParam>>>builder()
                        .tid(UUID.randomUUID().toString())
                        .bid(bid)
                        .timestamp(System.currentTimeMillis())
                        .method(ServicesMethodEnum.OTA_CREATE.getMethod())
                        .method(FirmwareMethodEnum.OTA_CREATE.getMethod())
                        .data(Map.of(MapKeyConst.DEVICES, deviceOtaFirmwares))
                        .build());
        if (serviceReplyOpt.isEmpty()) {
            return ResponseResult.error("No message reply received.");
        }
        ServiceReply serviceReply = serviceReplyOpt.get();
        if (serviceReply.getResult() != ResponseResult.CODE_SUCCESS) {
            return ResponseResult.error(serviceReply.getResult(), "Firmware Error Code: " + serviceReply.getResult());
        }
        if (ServicesMethodEnum.OTA_CREATE.getProgress()) {
            // Record the device state that needs to be updated.
            deviceOtaFirmwares.forEach(deviceOta -> redisOps.setWithExpire(
                    RedisConst.FIRMWARE_UPGRADING_PREFIX + deviceOta.getSn(),
                    bid,
                    RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND));
        }
        // 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(), osd);
            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;
    }
    /**
@@ -931,7 +973,7 @@
                .boundTime(dto.getBoundTime() != null ?
                        dto.getBoundTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : null)
                .childSn(dto.getChildDeviceSn())
                .domain(StringUtils.hasText(dto.getDomain()) ? DeviceDomainEnum.getVal(dto.getDomain()) : null)
                .domain(dto.getDomain())
                .firmwareVersion(dto.getFirmwareVersion())
                .compatibleStatus(dto.getFirmwareStatus() == null ? null :
                        DeviceFirmwareStatusEnum.CONSISTENT_UPGRADE != DeviceFirmwareStatusEnum.find(dto.getFirmwareStatus()))
@@ -940,15 +982,17 @@
    /**
     * 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 ->
@@ -993,4 +1037,42 @@
                .organizationName(device.getWorkspaceName())
                .build();
    }
    private Optional<DeviceEntity> onlineSaveDevice(DeviceEntity device, String childSn) {
        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;
        }
        device.setWorkspaceId(saveDeviceOpt.get().getWorkspaceId());
        RedisOpsUtils.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn(),
                DeviceDTO.builder()
                        .deviceSn(device.getDeviceSn())
                        .workspaceId(device.getWorkspaceId())
                        .childDeviceSn(childSn)
                        .domain(device.getDomain())
                        .type(device.getDeviceType())
                        .subType(device.getSubType())
                        .build(),
                RedisConst.DEVICE_ALIVE_SECOND);
        return saveDeviceOpt;
    }
}