无人机项目后端代码
sean.zhou
2023-04-25 694b9483c7a551626244cbc222c602ea9ff74094
What's new?
1. Wayline management: added `pause wayline task` and `recover wayline task`.
2. Added command flight function.
3. Fixed some issues.
65 files modified
2 files renamed
66 files added
1 files deleted
5424 ■■■■ changed files
api/Cloud API Demo.postman_collection.json 317 ●●●●● patch | view | raw | blame | history
pom.xml 2 ●●● patch | view | raw | blame | history
sql/cloud_sample.sql 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/GlobalExceptionHandler.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/GlobalScheduleService.java 11 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java 27 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java 29 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/handler/EventsRouter.java 29 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/handler/ServicesReplyHandler.java 9 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/EventsMethodEnum.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/EventsOutputProgressReceiver.java 4 ●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java 50 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java 78 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/redis/RedisConst.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java 17 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java 27 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/controller/DockController.java 40 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/controller/DrcController.java 55 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/dto/AlarmState.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/dto/BatteryStoreMode.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/dto/DrcModeDTO.java 32 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/dto/DrcModeReasonReceiver.java 15 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/dto/DrcStatusNotifyReceiver.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/dto/FlyToProgressReceiver.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/dto/JwtAclDTO.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/dto/LinkWorkMode.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/dto/MqttBrokerDTO.java 31 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/dto/PointDTO.java 31 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/dto/RemoteDebugOpenState.java 25 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/dto/ResultNotifyDTO.java 24 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/dto/ReturnHomeState.java 27 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/dto/TakeoffProgressReceiver.java 25 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/enums/CameraModeEnum.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/enums/CameraStateEnum.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/enums/CameraTypeEnum.java 38 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/enums/DrcMethodEnum.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/enums/DrcModeReasonEnum.java 47 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/enums/DrcStatusErrorEnum.java 52 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/enums/DroneAuthorityEnum.java 25 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/enums/DroneControlMethodEnum.java 28 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/enums/FlyToStatusEnum.java 39 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/enums/GimbalResetModeEnum.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/enums/MqttAclAccessEnum.java 24 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/enums/PayloadCommandsEnum.java 52 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/enums/RemoteDebugMethodEnum.java 20 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/enums/TakeoffStatusEnum.java 47 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/param/DeviceDrcInfoParam.java 19 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/param/DrcConnectParam.java 19 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/param/DrcModeParam.java 35 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/param/DronePayloadParam.java 54 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/param/FlyToPointParam.java 37 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/param/PayloadCommandsParam.java 27 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/model/param/TakeoffToPointParam.java 49 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/service/IControlService.java 56 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/service/IDrcService.java 60 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/service/impl/CameraAimImpl.java 24 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/service/impl/CameraFocalLengthSetImpl.java 41 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/service/impl/CameraModeSwitchImpl.java 31 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/service/impl/CameraPhotoTakeImpl.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/service/impl/CameraRecordingStartImpl.java 25 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/service/impl/CameraRecordingStopImpl.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/service/impl/ControlServiceImpl.java 273 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/service/impl/DrcServiceImpl.java 249 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/service/impl/GimbalResetImpl.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/service/impl/PayloadCommandsHandler.java 82 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/service/impl/RemoteDebugHandler.java 23 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java 35 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/DeviceAuthorityDTO.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java 4 ●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/ControlSourceEnum.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/DeviceModeCodeEnum.java 64 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/DeviceSetPropertyEnum.java 5 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/DockDrcStateEnum.java 36 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/DockModeCodeEnum.java 42 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/DroneRcLostActionEnum.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/HmsEnum.java 14 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/WaylineRcLostActionEnum.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/BasicDeviceProperty.java 5 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/DeviceHmsReceiver.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/FirmwareProgressExtReceiver.java 14 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/LiveviewWorldRegionReceiver.java 20 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/OsdCameraReceiver.java 34 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/OsdDockReceiver.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/OutOfControlActionReceiver.java 29 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/RthAltitudeReceiver.java 32 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IDeviceFirmwareService.java 9 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IDeviceLogsService.java 3 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java 15 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IDeviceRedisService.java 95 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IDeviceService.java 57 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IWorkspaceService.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DeviceFirmwareServiceImpl.java 103 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DeviceHmsServiceImpl.java 41 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DeviceLogsServiceImpl.java 110 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java 108 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DeviceRedisServiceImpl.java 80 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java 321 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DockOSDServiceImpl.java 17 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java 22 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java 45 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/service/IMediaService.java 15 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java 110 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/controller/WaylineJobController.java 9 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/dto/ConditionalWaylineJobKey.java 39 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/dto/KmzFileProperties.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/dto/WaylineJobDTO.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskConditionDTO.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskCreateDTO.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskExecutableConditionDTO.java 23 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskReadyConditionDTO.java 24 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/enums/WaylineErrorCodeEnum.java 83 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/enums/WaylineMethodEnum.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskStatusEnum.java 19 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskTypeEnum.java 20 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/enums/WaylineTemplateTypeEnum.java 34 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/param/CreateJobParam.java 20 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/param/UpdateJobParam.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/service/IFlightTaskService.java 9 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/service/IWaylineJobService.java 24 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/service/IWaylineRedisService.java 100 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/service/impl/FlightTaskServiceImpl.java 184 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java 9 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/service/impl/WaylineJobServiceImpl.java 330 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/service/impl/WaylineRedisServiceImpl.java 112 ●●●●● patch | view | raw | blame | history
src/main/resources/application.yml 7 ●●●● patch | view | raw | blame | history
api/Cloud API Demo.postman_collection.json
@@ -887,7 +887,7 @@
                "apikey": [
                    {
                        "key": "value",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NzAzMTU2MDEsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NzA0MDIwMDEsImlhdCI6MTY3MDMxNTYwMSwidXNlcm5hbWUiOiJhZG1pblBDIn0.yh8SkHZVsoIXo_vtlTGNB-ZX92XayalGe_q7mNRVcdI",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2ODIyMzI5MDYsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE3Njg2MzI5MDYsImlhdCI6MTY4MjIzMjkwNiwidXNlcm5hbWUiOiJhZG1pblBDIn0.ilO-3PcvWAX9r8z3AR4VAw3kVhavYjiTx_187ACBc1M",
                        "type": "string"
                    },
                    {
@@ -1537,7 +1537,7 @@
                "apikey": [
                    {
                        "key": "value",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2Njg2NTk4MjYsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2Njg3NDYyMjYsImlhdCI6MTY2ODY1OTgyNiwidXNlcm5hbWUiOiJhZG1pblBDIn0.ykCpfJcReeb3etUzmNMQk1n0vaoDT6dl47J_aHRoTbU",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2ODAyNjAxNTYsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE3NjY2NjAxNTYsImlhdCI6MTY4MDI2MDE1NiwidXNlcm5hbWUiOiJhZG1pblBDIn0._QhvfhBxxfQN7xpFqZma1rCYbBtouo2pErtm8737L_8",
                        "type": "string"
                    },
                    {
@@ -1578,7 +1578,7 @@
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"name\": \"\",\r\n    \"file_id\": \"\",\r\n    \"dock_sn\": \"\",\r\n    \"wayline_type\": 0,\r\n    \"task_type\": 0,\r\n    \"execute_time\": 123456789123,\r\n    \"rth_altitude\": 20,\r\n    \"out_of_control_action\": 1\r\n}",
                            "raw": "{\r\n    \"name\": \"\",\r\n    \"file_id\": \"\",\r\n    \"dock_sn\": \"\",\r\n    \"wayline_type\": 0,\r\n    \"task_type\": 0,\r\n    \"task_days\": [1676029468],\r\n    \"task_periods\": [[1676029468]],\r\n    \"rth_altitude\": 20,\r\n    \"out_of_control_action\": 1\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
@@ -1679,6 +1679,64 @@
                        }
                    },
                    "response": []
                },
                {
                    "name": "Pause Job",
                    "request": {
                        "method": "PUT",
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"status\": 0\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        },
                        "url": {
                            "raw": "{{base_url}}{{wayline_version}}/workspaces/{{workspace_id}}/jobs/{{job_id}}",
                            "host": [
                                "{{base_url}}{{wayline_version}}"
                            ],
                            "path": [
                                "workspaces",
                                "{{workspace_id}}",
                                "jobs",
                                "{{job_id}}"
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Resume Job",
                    "request": {
                        "method": "PUT",
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"status\": 1\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        },
                        "url": {
                            "raw": "{{base_url}}{{wayline_version}}/workspaces/{{workspace_id}}/jobs/{{job_id}}",
                            "host": [
                                "{{base_url}}{{wayline_version}}"
                            ],
                            "path": [
                                "workspaces",
                                "{{workspace_id}}",
                                "jobs",
                                "{{job_id}}"
                            ]
                        }
                    },
                    "response": []
                }
            ],
            "auth": {
@@ -1686,7 +1744,7 @@
                "apikey": [
                    {
                        "key": "value",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2Njg2NTk4MjYsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2Njg3NDYyMjYsImlhdCI6MTY2ODY1OTgyNiwidXNlcm5hbWUiOiJhZG1pblBDIn0.ykCpfJcReeb3etUzmNMQk1n0vaoDT6dl47J_aHRoTbU",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2Nzg4NjM0NzMsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE3NjUyNjM0NzMsImlhdCI6MTY3ODg2MzQ3MywidXNlcm5hbWUiOiJhZG1pblBDIn0.r3ODgJtAHxrBCzDnCwTDCdUq8hLyfIUiDYzasYAIUII",
                        "type": "string"
                    },
                    {
@@ -1727,7 +1785,7 @@
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"action\": 0\r\n}",
                            "raw": "",
                            "options": {
                                "raw": {
                                    "language": "json"
@@ -1735,7 +1793,7 @@
                            }
                        },
                        "url": {
                            "raw": "{{base_url}}{{control_version}}/devices/{{device_sn}}/jobs/alarm_state_switch",
                            "raw": "{{base_url}}{{control_version}}/devices/{{device_sn}}/jobs/return_home",
                            "host": [
                                "{{base_url}}{{control_version}}"
                            ],
@@ -1743,7 +1801,250 @@
                                "devices",
                                "{{device_sn}}",
                                "jobs",
                                "alarm_state_switch"
                                "return_home"
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Web Drc Connect",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"client_id\": \"xxx\",\r\n    \"expire_sec\": 1800\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        },
                        "url": {
                            "raw": "{{base_url}}{{control_version}}/workspaces/{{workspace_id}}/drc/connect",
                            "host": [
                                "{{base_url}}{{control_version}}"
                            ],
                            "path": [
                                "workspaces",
                                "{{workspace_id}}",
                                "drc",
                                "connect"
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Enter Drc Mode",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"client_id\": \"\",\r\n    \"dock_sn\": \"\"\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        },
                        "url": {
                            "raw": "{{base_url}}{{control_version}}/workspaces/{{workspace_id}}/drc/enter",
                            "host": [
                                "{{base_url}}{{control_version}}"
                            ],
                            "path": [
                                "workspaces",
                                "{{workspace_id}}",
                                "drc",
                                "enter"
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Drc Mode Exit",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"client_id\": \"\",\r\n    \"dock_sn\": \"\"\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        },
                        "url": {
                            "raw": "{{base_url}}{{control_version}}/workspaces/{{workspace_id}}/drc/exit",
                            "host": [
                                "{{base_url}}{{control_version}}"
                            ],
                            "path": [
                                "workspaces",
                                "{{workspace_id}}",
                                "drc",
                                "exit"
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Fly to Point",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"max_speed\": 15,\r\n    \"points\":[\r\n        {\r\n            \"latitude\": 22.5818,\r\n            \"longitude\": 113.9394,\r\n            \"height\": 20\r\n        }\r\n    ]\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        },
                        "url": {
                            "raw": "{{base_url}}{{control_version}}/devices/{{device_sn}}/jobs/fly-to-point",
                            "host": [
                                "{{base_url}}{{control_version}}"
                            ],
                            "path": [
                                "devices",
                                "{{device_sn}}",
                                "jobs",
                                "fly-to-point"
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Stop Flying to Point",
                    "request": {
                        "method": "DELETE",
                        "header": [],
                        "url": {
                            "raw": "{{base_url}}{{control_version}}/devices/{{device_sn}}/jobs/fly-to-point",
                            "host": [
                                "{{base_url}}{{control_version}}"
                            ],
                            "path": [
                                "devices",
                                "{{device_sn}}",
                                "jobs",
                                "fly-to-point"
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Take off to Point",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"target_latitude\": 22.579,\r\n    \"target_longitude\": 113.9392,\r\n    \"target_height\": 20,\r\n    \"security_takeoff_height\": 20,\r\n    \"rth_altitude\": 20,\r\n    \"rc_lost_action\": 0,\r\n    \"exit_wayline_when_rc_lost\": 0,\r\n    \"max_speed\": 12\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        },
                        "url": {
                            "raw": "{{base_url}}{{control_version}}/devices/{{device_sn}}/jobs/takeoff-to-point",
                            "host": [
                                "{{base_url}}{{control_version}}"
                            ],
                            "path": [
                                "devices",
                                "{{device_sn}}",
                                "jobs",
                                "takeoff-to-point"
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Payload Commands",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"cmd\":\"camera_mode_switch\",\r\n    \"data\":{\r\n        \"payload_index\":\"53-0-0\",\r\n        \"camera_mode\": 1\r\n    }\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        },
                        "url": {
                            "raw": "{{base_url}}{{control_version}}/devices/{{device_sn}}/payload/commands",
                            "host": [
                                "{{base_url}}{{control_version}}"
                            ],
                            "path": [
                                "devices",
                                "{{device_sn}}",
                                "payload",
                                "commands"
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Flight Authority Grab",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "url": {
                            "raw": "{{base_url}}{{control_version}}/devices/{{device_sn}}/authority/flight",
                            "host": [
                                "{{base_url}}{{control_version}}"
                            ],
                            "path": [
                                "devices",
                                "{{device_sn}}",
                                "authority",
                                "flight"
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Payload Authority Grab",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"payload_index\":\"53-0-0\"\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        },
                        "url": {
                            "raw": "{{base_url}}{{control_version}}/devices/{{device_sn}}/authority/payload",
                            "host": [
                                "{{base_url}}{{control_version}}"
                            ],
                            "path": [
                                "devices",
                                "{{device_sn}}",
                                "authority",
                                "payload"
                            ]
                        }
                    },
@@ -1755,7 +2056,7 @@
                "apikey": [
                    {
                        "key": "value",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2Njk2MzMzMzQsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2Njk3MTk3MzQsImlhdCI6MTY2OTYzMzMzNCwidXNlcm5hbWUiOiJhZG1pblBDIn0.OoIfdpyI5eL6bFm8akq8_stzClQU41YpIJkx6_kxVHU",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2ODIyMzI5MDYsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE3Njg2MzI5MDYsImlhdCI6MTY4MjIzMjkwNiwidXNlcm5hbWUiOiJhZG1pblBDIn0.ilO-3PcvWAX9r8z3AR4VAw3kVhavYjiTx_187ACBc1M",
                        "type": "string"
                    },
                    {
pom.xml
@@ -11,7 +11,7 @@
    <groupId>com.dji</groupId>
    <artifactId>cloud-api-sample</artifactId>
    <version>1.3.1</version>
    <version>1.4.0</version>
    <name>cloud-api-sample</name>
    <properties>
sql/cloud_sample.sql
@@ -66,7 +66,7 @@
  `device_sn` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'dock, drone, remote control',
  `device_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'undefined' COMMENT 'model of the device. This parameter corresponds to the device name in the device dictionary table.',
  `user_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT 'The account used when the device was bound.',
  `nickname` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT 'custom name of the device',
  `nickname` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'custom name of the device',
  `workspace_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT 'The workspace to which the current device belongs.',
  `device_type` int NOT NULL DEFAULT '-1' COMMENT 'This parameter corresponds to the device type in the device dictionary table.',
  `sub_type` int NOT NULL DEFAULT '-1' COMMENT 'This parameter corresponds to the sub type in the device dictionary table.',
@@ -226,6 +226,7 @@
  `payload_index` smallint NOT NULL COMMENT 'The location of the payload on the device.',
  `device_sn` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'Which device the current payload belongs to.',
  `payload_desc` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `control_source` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `create_time` bigint NOT NULL,
  `update_time` bigint NOT NULL,
  PRIMARY KEY (`id`),
@@ -452,7 +453,7 @@
  `completed_time` bigint DEFAULT NULL COMMENT 'actual end time',
  `username` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'The name of the creator.',
  `begin_time` bigint NOT NULL COMMENT 'planned begin time',
  `end_time` bigint NOT NULL COMMENT 'planned end time',
  `end_time` bigint DEFAULT NULL COMMENT 'planned end time',
  `error_code` int DEFAULT NULL,
  `status` int NOT NULL COMMENT '1: pending; 2: in progress; 3: success; 4: cancel; 5: failed',
  `rth_altitude` int NOT NULL COMMENT 'return to home altitude. min: 20m; max: 500m',
src/main/java/com/dji/sample/component/GlobalExceptionHandler.java
@@ -36,7 +36,7 @@
    @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
    public ResponseResult methodArgumentNotValidExceptionHandler(BindException e) {
        e.printStackTrace();
        return ResponseResult.error(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
        return ResponseResult.error(e.getFieldError().getField() + e.getFieldError().getDefaultMessage());
    }
}
src/main/java/com/dji/sample/component/GlobalScheduleService.java
@@ -7,6 +7,7 @@
import com.dji.sample.manage.model.enums.DeviceDomainEnum;
import com.dji.sample.manage.service.IDeviceService;
import com.dji.sample.wayline.service.IWaylineJobService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
@@ -32,11 +33,12 @@
    @Autowired
    private IWaylineJobService waylineJobService;
    @Autowired
    private ObjectMapper mapper;
    /**
     * Check the status of the devices every 30 seconds. It is recommended to use cache.
     */
    @Scheduled(initialDelay = 30, fixedRate = 30, timeUnit = TimeUnit.SECONDS)
    @Scheduled(initialDelay = 10, fixedRate = 30, timeUnit = TimeUnit.SECONDS)
    private void deviceStatusListen() {
        int start = RedisConst.DEVICE_ONLINE_PREFIX.length();
@@ -49,8 +51,9 @@
                } else {
                    deviceService.unsubscribeTopicOffline(key.substring(start));
                    deviceService.pushDeviceOfflineTopo(device.getWorkspaceId(), device.getDeviceSn());
                    RedisOpsUtils.hashDel(RedisConst.LIVE_CAPACITY, new String[]{key});
                    RedisOpsUtils.del(RedisConst.HMS_PREFIX + key);
                    RedisOpsUtils.hashDel(RedisConst.LIVE_CAPACITY, new String[]{device.getDeviceSn()});
                    RedisOpsUtils.del(RedisConst.HMS_PREFIX + device.getDeviceSn());
                    RedisOpsUtils.del(RedisConst.OSD_PREFIX + device.getDeviceSn());
                }
                RedisOpsUtils.del(key);
            }
src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java
@@ -5,6 +5,7 @@
import com.dji.sample.component.mqtt.model.MqttClientOptions;
import com.dji.sample.component.mqtt.model.MqttProtocolEnum;
import com.dji.sample.component.mqtt.model.MqttUseEnum;
import com.dji.sample.control.model.dto.MqttBrokerDTO;
import lombok.Data;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -70,6 +71,32 @@
        return addr.toString();
    }
    /**
     * Get the connection parameters of the mqtt client of the drc link.
     * @param clientId
     * @param username
     * @param age   The validity period of the token. unit: s
     * @param map   Custom data added in token.
     * @return
     */
    public static MqttBrokerDTO getMqttBrokerWithDrc(String clientId, String username, Long age, Map<String, ?> map) {
        if (!mqtt.containsKey(MqttUseEnum.DRC)) {
            throw new RuntimeException("Please configure the drc link parameters of mqtt in the backend configuration file first.");
        }
        Algorithm algorithm = JwtUtil.algorithm;
        String token = JwtUtil.createToken(map, age, algorithm, null, null);
        return MqttBrokerDTO.builder()
                .address(getMqttAddress(mqtt.get(MqttUseEnum.DRC)))
                .username(username)
                .clientId(clientId)
                .expireTime(System.currentTimeMillis() / 1000 + age)
                .password(token)
                .build();
    }
    @Bean
    public MqttConnectOptions mqttConnectOptions() {
        MqttClientOptions customizeOptions = getBasicClientOptions();
src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java
@@ -107,6 +107,11 @@
        return new DirectChannel();
    }
    @Bean(name = ChannelName.OUTBOUND_EVENTS)
    public MessageChannel eventsOutboundChannel() {
        return new DirectChannel();
    }
    @Bean(name = ChannelName.INBOUND_EVENTS_FLIGHT_TASK_PROGRESS)
    public MessageChannel eventsFlightTaskProgressChannel() {
        return new DirectChannel();
@@ -172,4 +177,28 @@
        return new DirectChannel();
    }
    @Bean(name = ChannelName.INBOUND_EVENTS_FLIGHT_TASK_READY)
    public MessageChannel eventsEventsFlightTaskReady() {
        return new DirectChannel();
    }
    @Bean(name = ChannelName.INBOUND_EVENTS_FLY_TO_POINT_PROGRESS)
    public MessageChannel eventsFlyToPointProgress() {
        return new DirectChannel();
    }
    @Bean(name = ChannelName.INBOUND_EVENTS_TAKE_OFF_TO_POINT_PROGRESS)
    public MessageChannel eventsTakeoffToPointProgress() {
        return new DirectChannel();
    }
    @Bean(name = ChannelName.INBOUND_EVENTS_DRC_STATUS_NOTIFY)
    public MessageChannel eventsDrcStatusNotify() {
        return new DirectChannel();
    }
    @Bean(name = ChannelName.INBOUND_EVENTS_DRC_MODE_EXIT_NOTIFY)
    public MessageChannel eventsDrcModeExitNotify() {
        return new DirectChannel();
    }
}
src/main/java/com/dji/sample/component/mqtt/handler/EventsRouter.java
@@ -1,17 +1,20 @@
package com.dji.sample.component.mqtt.handler;
import com.dji.sample.component.mqtt.model.ChannelName;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.component.mqtt.model.EventsMethodEnum;
import com.dji.sample.component.mqtt.model.*;
import com.dji.sample.component.mqtt.service.IMessageSenderService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.MessageHeaders;
import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;
/**
 * @author sean
@@ -23,6 +26,9 @@
    @Autowired
    private ObjectMapper mapper;
    @Autowired
    private IMessageSenderService messageSenderService;
    @Bean
    public IntegrationFlow eventsMethodRouterFlow() {
@@ -42,4 +48,21 @@
                                methodEnum -> mapping.channelMapping(methodEnum, methodEnum.getChannelName())))
                .get();
    }
    @ServiceActivator(inputChannel = ChannelName.OUTBOUND_EVENTS, outputChannel = ChannelName.OUTBOUND)
    public void replyEventsOutbound(CommonTopicReceiver receiver, MessageHeaders headers) {
        if (Optional.ofNullable(receiver).map(CommonTopicReceiver::getNeedReply).flatMap(val -> Optional.of(1 != val)).orElse(true)) {
            return;
        }
        messageSenderService.publish(headers.get(MqttHeaders.RECEIVED_TOPIC) + TopicConst._REPLY_SUF,
                CommonTopicResponse.builder()
                        .tid(receiver.getTid())
                        .bid(receiver.getBid())
                        .method(receiver.getMethod())
                        .timestamp(System.currentTimeMillis())
                        .data(RequestsReply.success())
                        .build());
    }
}
src/main/java/com/dji/sample/component/mqtt/handler/ServicesReplyHandler.java
@@ -36,13 +36,16 @@
        byte[] payload = (byte[])message.getPayload();
        CommonTopicReceiver receiver = mapper.readValue(payload, new TypeReference<CommonTopicReceiver>() {});
        ServiceReply reply;
        if (LogsFileMethodEnum.FILE_UPLOAD_LIST.getMethod().equals(receiver.getMethod())) {
            LogsFileUploadList list = mapper.convertValue(receiver.getData(), new TypeReference<LogsFileUploadList>() {});
            receiver.setData(list);
            reply = new ServiceReply();
            reply.setResult(list.getResult());
            reply.setOutput(list.getFiles());
        } else {
            ServiceReply reply = mapper.convertValue(receiver.getData(), new TypeReference<ServiceReply>() {});
            receiver.setData(reply);
            reply = mapper.convertValue(receiver.getData(), new TypeReference<ServiceReply>() {});
        }
        receiver.setData(reply);
        Chan<CommonTopicReceiver<?>> chan = Chan.getInstance();
        // Put the message to the chan object.
        chan.put(receiver);
src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java
@@ -51,6 +51,8 @@
    public static final String INBOUND_EVENTS = "inboundEvents";
    public static final String OUTBOUND_EVENTS = "outboundEvents";
    public static final String INBOUND_EVENTS_FLIGHT_TASK_PROGRESS = "inboundEventsFlightTaskProgress";
    public static final String INBOUND_EVENTS_FILE_UPLOAD_CALLBACK = "inboundEventsFileUploadCallback";
@@ -76,4 +78,14 @@
    public static final String INBOUND_REQUESTS_CONFIG = "inboundRequestsConfig";
    public static final String INBOUND_EVENTS_HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA = "inboundEventsHighestPriorityUploadFlightTaskMedia";
    public static final String INBOUND_EVENTS_FLIGHT_TASK_READY = "inboundEventsFlightTaskReady";
    public static final String INBOUND_EVENTS_FLY_TO_POINT_PROGRESS = "inboundFlyToPointProgress";
    public static final String INBOUND_EVENTS_TAKE_OFF_TO_POINT_PROGRESS = "inboundTakeoffToPointProgress";
    public static final String INBOUND_EVENTS_DRC_STATUS_NOTIFY = "inboundDrcStatusNotify";
    public static final String INBOUND_EVENTS_DRC_MODE_EXIT_NOTIFY = "inboundDrcModeExitNotify";
}
src/main/java/com/dji/sample/component/mqtt/model/EventsMethodEnum.java
@@ -45,6 +45,16 @@
    HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA("highest_priority_upload_flighttask_media", ChannelName.INBOUND_EVENTS_HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA),
    FLIGHT_TASK_READY("flighttask_ready", ChannelName.INBOUND_EVENTS_FLIGHT_TASK_READY),
    FLY_TO_POINT_PROGRESS("fly_to_point_progress", ChannelName.INBOUND_EVENTS_FLY_TO_POINT_PROGRESS),
    TAKE_OFF_TO_POINT_PROGRESS("takeoff_to_point_progress", ChannelName.INBOUND_EVENTS_TAKE_OFF_TO_POINT_PROGRESS),
    DRC_STATUS_NOTIFY("drc_status_notify", ChannelName.INBOUND_EVENTS_DRC_STATUS_NOTIFY),
    JOYSTICK_INVALID_NOTIFY("joystick_invalid_notify", ChannelName.INBOUND_EVENTS_DRC_MODE_EXIT_NOTIFY),
    UNKNOWN("Unknown", ChannelName.DEFAULT);
    private String method;
src/main/java/com/dji/sample/component/mqtt/model/EventsOutputProgressReceiver.java
File was renamed from src/main/java/com/dji/sample/component/mqtt/model/EventsOutputReceiver.java
@@ -8,9 +8,11 @@
 * @date 2022/7/29
 */
@Data
public class EventsOutputReceiver {
public class EventsOutputProgressReceiver<T> {
    private String status;
    private OutputProgressReceiver progress;
    private T ext;
}
src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java
@@ -34,4 +34,9 @@
    public static final String REGEX_SN = "[A-Za-z0-9]+";
    public static final String DRC = "/drc";
    public static final String UP = "/up";
    public static final String DOWN = "/down";
}
src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java
@@ -2,6 +2,7 @@
import com.dji.sample.component.mqtt.model.CommonTopicResponse;
import com.dji.sample.component.mqtt.model.ServiceReply;
import com.fasterxml.jackson.core.type.TypeReference;
/**
 * @author sean.zhou
@@ -26,15 +27,16 @@
    void publish(String topic, int qos, CommonTopicResponse response);
    /**
     * Send live streaming start message and receive a response at the same time.
     * Send message and receive a response at the same time.
     * @param clazz
     * @param topic
     * @param response  notification of whether the start is successful.
     * @return
     */
    ServiceReply publishWithReply(String topic, CommonTopicResponse response);
    <T> T publishWithReply(Class<T> clazz, String topic, CommonTopicResponse response);
    /**
     * Send live streaming start message and receive a response at the same time.
     * Send message and receive a response at the same time.
     * @param clazz
     * @param topic
     * @param response
@@ -43,4 +45,46 @@
     * @return
     */
    <T> T publishWithReply(Class<T> clazz, String topic, CommonTopicResponse response, int retryTime);
    /**
     * Used exclusively for sending messages for services.
     * @param clazz The generic class for ServiceReply.
     * @param sn
     * @param method
     * @param data
     * @param bid
     * @param <T>
     * @return
     */
    <T> ServiceReply<T> publishServicesTopic(TypeReference<T> clazz, String sn, String method, Object data, String bid);
    /**
     * Used exclusively for sending messages for services, and does not set the received subtype.
     * @param sn
     * @param method
     * @param data
     * @param bid
     * @return
     */
    ServiceReply publishServicesTopic(String sn, String method, Object data, String bid);
    /**
     * Used exclusively for sending messages for services.
     * @param clazz The generic class for ServiceReply.
     * @param sn
     * @param method
     * @param data
     * @param <T>
     * @return
     */
    <T> ServiceReply<T> publishServicesTopic(TypeReference<T> clazz, String sn, String method, Object data);
    /**
     * Used exclusively for sending messages for services, and does not set the received subtype.
     * @param sn
     * @param method
     * @param data
     * @return
     */
    ServiceReply publishServicesTopic(String sn, String method, Object data);
}
src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java
@@ -1,17 +1,19 @@
package com.dji.sample.component.mqtt.service.impl;
import com.dji.sample.component.mqtt.model.Chan;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.component.mqtt.model.CommonTopicResponse;
import com.dji.sample.component.mqtt.model.ServiceReply;
import com.dji.sample.component.mqtt.model.*;
import com.dji.sample.component.mqtt.service.IMessageSenderService;
import com.dji.sample.component.mqtt.service.IMqttMessageGateway;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -30,17 +32,12 @@
    private ObjectMapper mapper;
    public void publish(String topic, CommonTopicResponse response) {
        try {
            log.info("send topic: {}, payload: {}", topic, response.toString());
            messageGateway.publish(topic, mapper.writeValueAsBytes(response));
        } catch (JsonProcessingException e) {
            log.info("Failed to publish the message. {}", response.toString());
            e.printStackTrace();
        }
        this.publish(topic, 1, response);
    }
    public void publish(String topic, int qos, CommonTopicResponse response) {
        try {
            log.info("send topic: {}, payload: {}", topic, response.toString());
            messageGateway.publish(topic, mapper.writeValueAsBytes(response), qos);
        } catch (JsonProcessingException e) {
            log.info("Failed to publish the message. {}", response.toString());
@@ -48,12 +45,11 @@
        }
    }
    public ServiceReply publishWithReply(String topic, CommonTopicResponse response) {
        return this.publishWithReply(ServiceReply.class, topic, response, 2);
    public <T> T publishWithReply(Class<T> clazz, String topic, CommonTopicResponse response) {
        return this.publishWithReply(clazz, topic, response, 2);
    }
    public <T> T publishWithReply(Class<T> clazz, String topic, CommonTopicResponse response, int retryTime) {
        log.info("send topic: {}, payload: {}", topic, response.toString());
        AtomicInteger time = new AtomicInteger(0);
        // Retry three times
        while (time.getAndIncrement() <= retryTime) {
@@ -62,15 +58,59 @@
            Chan<CommonTopicReceiver<T>> chan = Chan.getInstance();
            // If the message is not received in 0.5 seconds then resend it again.
            CommonTopicReceiver<T> receiver = chan.get(response.getTid());
            if (receiver == null) {
                continue;
            }
            // Need to match tid and bid.
            if (receiver.getTid().equals(response.getTid()) &&
            if (Objects.nonNull(receiver) && receiver.getTid().equals(response.getTid()) &&
                    receiver.getBid().equals(response.getBid())) {
                return receiver.getData();
                if (clazz.isAssignableFrom(receiver.getData().getClass())) {
                    return receiver.getData();
                }
                throw new TypeMismatchException(receiver.getData(), clazz);
            }
            // It must be guaranteed that the tid and bid of each message are different.
            response.setBid(UUID.randomUUID().toString());
            response.setTid(UUID.randomUUID().toString());
        }
        throw new RuntimeException("No message reply received.");
    }
    @Override
    public <T> ServiceReply<T> publishServicesTopic(TypeReference<T> clazz, String sn, String method, Object data, String bid) {
        String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + sn + TopicConst.SERVICES_SUF;
        ServiceReply reply = this.publishWithReply(ServiceReply.class, topic,
                CommonTopicResponse.builder()
                        .tid(UUID.randomUUID().toString())
                        .bid(StringUtils.hasText(bid) ? bid : UUID.randomUUID().toString())
                        .timestamp(System.currentTimeMillis())
                        .method(method)
                        .data(Objects.requireNonNullElse(data, ""))
                        .build());
        if (Objects.isNull(clazz)) {
            return reply;
        }
        // put together in "output"
        if (Objects.nonNull(reply.getInfo())) {
            reply.setOutput(mapper.convertValue(reply.getInfo(), clazz));
        }
        if (Objects.nonNull(reply.getOutput())) {
            reply.setOutput(mapper.convertValue(reply.getOutput(), clazz));
        }
        return reply;
    }
    @Override
    public ServiceReply publishServicesTopic(String sn, String method, Object data, String bid) {
        return this.publishServicesTopic(null, sn, method, data, bid);
    }
    @Override
    public <T> ServiceReply<T> publishServicesTopic(TypeReference<T> clazz, String sn, String method, Object data) {
        return this.publishServicesTopic(clazz, sn, method, data, null);
    }
    @Override
    public ServiceReply publishServicesTopic(String sn, String method, Object data) {
        return this.publishServicesTopic(null, sn, method, data, null);
    }
}
src/main/java/com/dji/sample/component/redis/RedisConst.java
@@ -39,6 +39,10 @@
    public static final String WAYLINE_JOB_TIMED_EXECUTE = "wayline_job_timed_execute";
    public static final String WAYLINE_JOB_CONDITION_PREPARE = "wayline_job_condition_prepare";
    public static final String WAYLINE_JOB_CONDITION_PREFIX = WAYLINE_JOB_CONDITION_PREPARE + DELIMITER;
    public static final String WAYLINE_JOB_BLOCK_PREFIX = "wayline_job_block" + DELIMITER;
    public static final String WAYLINE_JOB_RUNNING_PREFIX = "wayline_job_running" + DELIMITER;
@@ -53,7 +57,13 @@
    public static final String LIVE_CAPACITY = "live_capacity";
    public static final String DRC_PREFIX = "drc" + DELIMITER;
    public static final Integer DRC_MODE_ALIVE_SECOND = 3600;
    public static final String MQTT_ACL_PREFIX = "mqtt_acl" + DELIMITER;
    public static final String FILE_UPLOADING_PREFIX = "file_uploading" + DELIMITER;
}
    public static final String DRONE_CONTROL_PREFiX = "control_source" + DELIMITER;
}
src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java
@@ -57,7 +57,22 @@
    FILE_UPLOAD_CALLBACK("file_upload_callback"),
    HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA("HIGHEST_PRIORITY_UPLOAD_FLIGHTTASK_MEDIA");
    FILE_UPLOAD_PROGRESS("fileupload_progress"),
    OTA_PROGRESS("ota_progress"),
    HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA("highest_priority_upload_flighttask_media"),
    CONTROL_SOURCE_CHANGE("control_source_change"),
    FLY_TO_POINT_PROGRESS("fly_to_point_progress"),
    TAKE_OFF_TO_POINT_PROGRESS("takeoff_to_point_progress"),
    DRC_STATUS_NOTIFY("drc_status_notify"),
    JOYSTICK_INVALID_NOTIFY("joystick_invalid_notify")
    ;
    private String code;
src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java
@@ -25,4 +25,8 @@
     * @param message   message
     */
    void sendBatch(Collection<ConcurrentWebSocketSession> sessions, CustomWebSocketMessage message);
    void sendBatch(String workspaceId, Integer userType, String bizCode, Object data);
    void sendBatch(String workspaceId, String bizCode, Object data);
}
src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java
@@ -3,14 +3,17 @@
import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
import com.dji.sample.component.websocket.service.ISendMessageService;
import com.dji.sample.component.websocket.service.IWebSocketManageService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.TextMessage;
import java.io.IOException;
import java.util.Collection;
import java.util.Objects;
/**
 * @author sean.zhou
@@ -23,6 +26,9 @@
    @Autowired
    private ObjectMapper mapper;
    @Autowired
    private IWebSocketManageService webSocketManageService;
    @Override
    public void sendMessage(ConcurrentWebSocketSession session, CustomWebSocketMessage message) {
@@ -70,4 +76,25 @@
            e.printStackTrace();
        }
    }
    @Override
    public void sendBatch(String workspaceId, Integer userType, String bizCode, Object data) {
        if (!StringUtils.hasText(workspaceId)) {
            throw new RuntimeException("Workspace ID does not exist.");
        }
        Collection<ConcurrentWebSocketSession> sessions = Objects.isNull(userType) ?
                webSocketManageService.getValueWithWorkspace(workspaceId) :
                webSocketManageService.getValueWithWorkspaceAndUserType(workspaceId, userType);
        this.sendBatch(sessions, CustomWebSocketMessage.builder()
                        .data(data)
                        .timestamp(System.currentTimeMillis())
                        .bizCode(bizCode)
                        .build());
    }
    @Override
    public void sendBatch(String workspaceId, String bizCode, Object data) {
        this.sendBatch(workspaceId, null, bizCode, data);
    }
}
src/main/java/com/dji/sample/control/controller/DockController.java
@@ -1,11 +1,14 @@
package com.dji.sample.control.controller;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.control.model.param.RemoteDebugParam;
import com.dji.sample.control.model.enums.DroneAuthorityEnum;
import com.dji.sample.control.model.param.*;
import com.dji.sample.control.service.IControlService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
 * @author sean
@@ -24,6 +27,39 @@
    public ResponseResult createControlJob(@PathVariable String sn,
                                           @PathVariable("service_identifier") String serviceIdentifier,
                                           @RequestBody(required = false) RemoteDebugParam param) {
        return controlService.controlDock(sn, serviceIdentifier, param);
        return controlService.controlDockDebug(sn, serviceIdentifier, param);
    }
    @PostMapping("/{sn}/jobs/fly-to-point")
    public ResponseResult flyToPoint(@PathVariable String sn, @Valid @RequestBody FlyToPointParam param) {
        return controlService.flyToPoint(sn, param);
    }
    @DeleteMapping("/{sn}/jobs/fly-to-point")
    public ResponseResult flyToPointStop(@PathVariable String sn) {
        return controlService.flyToPointStop(sn);
    }
    @PostMapping("/{sn}/jobs/takeoff-to-point")
    public ResponseResult takeoffToPoint(@PathVariable String sn, @Valid @RequestBody TakeoffToPointParam param) {
        return controlService.takeoffToPoint(sn, param);
    }
    @PostMapping("/{sn}/authority/flight")
    public ResponseResult seizeFlightAuthority(@PathVariable String sn) {
        return controlService.seizeAuthority(sn, DroneAuthorityEnum.FLIGHT, null);
    }
    @PostMapping("/{sn}/authority/payload")
    public ResponseResult seizePayloadAuthority(@PathVariable String sn, @Valid @RequestBody DronePayloadParam param) {
        return controlService.seizeAuthority(sn, DroneAuthorityEnum.PAYLOAD, param);
    }
    @PostMapping("/{sn}/payload/commands")
    public ResponseResult payloadCommands(@PathVariable String sn, @Valid @RequestBody PayloadCommandsParam param) throws Exception {
        param.setSn(sn);
        return controlService.payloadCommands(param);
    }
}
src/main/java/com/dji/sample/control/controller/DrcController.java
New file
@@ -0,0 +1,55 @@
package com.dji.sample.control.controller;
import com.dji.sample.common.model.CustomClaim;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.control.model.dto.JwtAclDTO;
import com.dji.sample.control.model.dto.MqttBrokerDTO;
import com.dji.sample.control.model.param.DrcConnectParam;
import com.dji.sample.control.model.param.DrcModeParam;
import com.dji.sample.control.service.IDrcService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM;
/**
 * @author sean
 * @version 1.3
 * @date 2023/1/11
 */
@RestController
@Slf4j
@RequestMapping("${url.control.prefix}${url.control.version}")
public class DrcController {
    @Autowired
    private IDrcService drcService;
    @PostMapping("/workspaces/{workspace_id}/drc/connect")
    public ResponseResult drcConnect(@PathVariable("workspace_id") String workspaceId, HttpServletRequest request, @Valid @RequestBody DrcConnectParam param) {
        CustomClaim claims = (CustomClaim) request.getAttribute(TOKEN_CLAIM);
        MqttBrokerDTO brokerDTO = drcService.userDrcAuth(workspaceId, claims.getId(), claims.getUsername(), param);
        return ResponseResult.success(brokerDTO);
    }
    @PostMapping("/workspaces/{workspace_id}/drc/enter")
    public ResponseResult drcEnter(@PathVariable("workspace_id") String workspaceId, @Valid @RequestBody DrcModeParam param) {
        JwtAclDTO acl = drcService.deviceDrcEnter(workspaceId, param);
        return ResponseResult.success(acl);
    }
    @PostMapping("/workspaces/{workspace_id}/drc/exit")
    public ResponseResult drcExit(@PathVariable("workspace_id") String workspaceId, @Valid @RequestBody DrcModeParam param) {
        drcService.deviceDrcExit(workspaceId, param);
        return ResponseResult.success();
    }
}
src/main/java/com/dji/sample/control/model/dto/AlarmState.java
@@ -1,7 +1,7 @@
package com.dji.sample.control.model.dto;
import com.dji.sample.control.service.impl.RemoteDebugHandler;
import com.dji.sample.manage.model.enums.StateSwitchEnum;
import com.dji.sample.manage.model.receiver.BasicDeviceProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -18,7 +18,7 @@
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AlarmState extends BasicDeviceProperty {
public class AlarmState extends RemoteDebugHandler {
    private Integer action;
src/main/java/com/dji/sample/control/model/dto/BatteryStoreMode.java
@@ -1,7 +1,7 @@
package com.dji.sample.control.model.dto;
import com.dji.sample.control.model.enums.BatteryStoreModeEnum;
import com.dji.sample.manage.model.receiver.BasicDeviceProperty;
import com.dji.sample.control.service.impl.RemoteDebugHandler;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -18,7 +18,7 @@
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BatteryStoreMode extends BasicDeviceProperty {
public class BatteryStoreMode extends RemoteDebugHandler {
    private Integer action;
src/main/java/com/dji/sample/control/model/dto/DrcModeDTO.java
New file
@@ -0,0 +1,32 @@
package com.dji.sample.control.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author sean
 * @version 1.3
 * @date 2023/1/12
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DrcModeDTO {
    private MqttBrokerDTO mqttBroker;
    /**
     * range: 1 - 30
     */
    @Builder.Default
    private Integer osdFrequency = 10;
    /**
     * range: 1 - 30
     */
    @Builder.Default
    private Integer hsiFrequency = 1;
}
src/main/java/com/dji/sample/control/model/dto/DrcModeReasonReceiver.java
New file
@@ -0,0 +1,15 @@
package com.dji.sample.control.model.dto;
import com.dji.sample.control.model.enums.DrcModeReasonEnum;
import lombok.Data;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/14
 */
@Data
public class DrcModeReasonReceiver {
    private DrcModeReasonEnum reason;
}
src/main/java/com/dji/sample/control/model/dto/DrcStatusNotifyReceiver.java
New file
@@ -0,0 +1,18 @@
package com.dji.sample.control.model.dto;
import com.dji.sample.control.model.enums.DrcStatusErrorEnum;
import com.dji.sample.manage.model.enums.DockDrcStateEnum;
import lombok.Data;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/17
 */
@Data
public class DrcStatusNotifyReceiver {
    private DrcStatusErrorEnum result;
    private DockDrcStateEnum drcState;
}
src/main/java/com/dji/sample/control/model/dto/FlyToProgressReceiver.java
New file
@@ -0,0 +1,22 @@
package com.dji.sample.control.model.dto;
import com.dji.sample.control.model.enums.FlyToStatusEnum;
import com.dji.sample.wayline.model.enums.WaylineErrorCodeEnum;
import lombok.Data;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/14
 */
@Data
public class FlyToProgressReceiver {
    private WaylineErrorCodeEnum result;
    private FlyToStatusEnum status;
    private String flyToId;
    private Integer wayPointIndex;
}
src/main/java/com/dji/sample/control/model/dto/JwtAclDTO.java
New file
@@ -0,0 +1,26 @@
package com.dji.sample.control.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
 * @author sean
 * @version 1.3
 * @date 2023/1/12
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class JwtAclDTO {
    private List<String> sub;
    private List<String> pub;
    private List<String> all;
}
src/main/java/com/dji/sample/control/model/dto/LinkWorkMode.java
@@ -1,7 +1,7 @@
package com.dji.sample.control.model.dto;
import com.dji.sample.control.model.enums.LinkWorkModeEnum;
import com.dji.sample.manage.model.receiver.BasicDeviceProperty;
import com.dji.sample.control.service.impl.RemoteDebugHandler;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -19,7 +19,7 @@
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LinkWorkMode extends BasicDeviceProperty {
public class LinkWorkMode extends RemoteDebugHandler {
    @JsonProperty("link_workmode")
    private Integer linkWorkMode;
src/main/java/com/dji/sample/control/model/dto/MqttBrokerDTO.java
New file
@@ -0,0 +1,31 @@
package com.dji.sample.control.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author sean
 * @version 1.3
 * @date 2023/1/11
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MqttBrokerDTO {
    private String address;
    private String username;
    private String password;
    private String clientId;
    private Long expireTime;
    @Builder.Default
    private Boolean enableTls = false;
}
src/main/java/com/dji/sample/control/model/dto/PointDTO.java
New file
@@ -0,0 +1,31 @@
package com.dji.sample.control.model.dto;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotNull;
/**
 * @author sean
 * @version 1.3
 * @date 2023/2/14
 */
@Data
public class PointDTO {
    @Range(min = -90, max = 90)
    @NotNull
    private Double latitude;
    @NotNull
    @Range(min = -180, max = 180)
    private Double longitude;
    /**
     * WGS84
     * The M30 series are ellipsoidal heights.
     */
    @NotNull
    @Range(min = 2, max = 1500)
    private Double height;
}
src/main/java/com/dji/sample/control/model/dto/RemoteDebugOpenState.java
New file
@@ -0,0 +1,25 @@
package com.dji.sample.control.model.dto;
import com.dji.sample.common.util.SpringBeanUtils;
import com.dji.sample.control.service.impl.RemoteDebugHandler;
import com.dji.sample.manage.model.enums.DockModeCodeEnum;
import com.dji.sample.manage.service.IDeviceService;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * @author sean
 * @version 1.4
 * @date 2023/4/14
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class RemoteDebugOpenState extends RemoteDebugHandler {
    @Override
    public boolean canPublish(String sn) {
        IDeviceService deviceService = SpringBeanUtils.getBean(IDeviceService.class);
        DockModeCodeEnum dockMode = deviceService.getDockMode(sn);
        return DockModeCodeEnum.IDLE == dockMode;
    }
}
src/main/java/com/dji/sample/control/model/dto/ResultNotifyDTO.java
New file
@@ -0,0 +1,24 @@
package com.dji.sample.control.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/1
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ResultNotifyDTO {
    private Integer result;
    private String message;
    private String sn;
}
src/main/java/com/dji/sample/control/model/dto/ReturnHomeState.java
New file
@@ -0,0 +1,27 @@
package com.dji.sample.control.model.dto;
import com.dji.sample.common.util.SpringBeanUtils;
import com.dji.sample.control.service.impl.RemoteDebugHandler;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.receiver.OsdSubDeviceReceiver;
import com.dji.sample.manage.service.IDeviceRedisService;
/**
 * @author sean
 * @version 1.4
 * @date 2023/4/19
 */
public class ReturnHomeState extends RemoteDebugHandler {
    @Override
    public boolean canPublish(String sn) {
        IDeviceRedisService deviceRedisService = SpringBeanUtils.getBean(IDeviceRedisService.class);
        return deviceRedisService.getDeviceOnline(sn)
                .map(DeviceDTO::getChildDeviceSn)
                .flatMap(deviceSn -> deviceRedisService.getDeviceOsd(deviceSn, OsdSubDeviceReceiver.class))
                .map(OsdSubDeviceReceiver::getElevation)
                .map(elevation -> elevation > 0)
                .orElse(false);
    }
}
src/main/java/com/dji/sample/control/model/dto/TakeoffProgressReceiver.java
New file
@@ -0,0 +1,25 @@
package com.dji.sample.control.model.dto;
import com.dji.sample.control.model.enums.TakeoffStatusEnum;
import com.dji.sample.wayline.model.enums.WaylineErrorCodeEnum;
import lombok.Data;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/14
 */
@Data
public class TakeoffProgressReceiver {
    private WaylineErrorCodeEnum result;
    private TakeoffStatusEnum status;
    private String flightId;
    private String trackId;
    private Integer wayPointIndex;
}
src/main/java/com/dji/sample/control/model/enums/CameraModeEnum.java
New file
@@ -0,0 +1,26 @@
package com.dji.sample.control.model.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/3
 */
public enum CameraModeEnum {
    PHOTO, VIDEO;
    @JsonValue
    public int getVal() {
        return ordinal();
    }
    @JsonCreator
    public static CameraModeEnum find(int val) {
        return Arrays.stream(values()).filter(modeEnum -> modeEnum.ordinal() == val).findAny().get();
    }
}
src/main/java/com/dji/sample/control/model/enums/CameraStateEnum.java
New file
@@ -0,0 +1,26 @@
package com.dji.sample.control.model.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.4
 * @date 2023/4/23
 */
public enum CameraStateEnum {
    IDLE, WORKING;
    @JsonValue
    public int getVal() {
        return ordinal();
    }
    @JsonCreator
    public static CameraStateEnum find(int val) {
        return Arrays.stream(values()).filter(stateEnum -> stateEnum.ordinal() == val).findAny().get();
    }
}
src/main/java/com/dji/sample/control/model/enums/CameraTypeEnum.java
New file
@@ -0,0 +1,38 @@
package com.dji.sample.control.model.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/3
 */
@Getter
public enum CameraTypeEnum {
    ZOOM("zoom"),
    WIDE("wide"),
    IR("ir");
    String type;
    CameraTypeEnum(String type) {
        this.type = type;
    }
    @JsonValue
    public String getType() {
        return type;
    }
    @JsonCreator
    public static CameraTypeEnum find(String cameraType) {
        return Arrays.stream(CameraTypeEnum.values()).filter(typeEnum -> typeEnum.type.equals(cameraType)).findAny().get();
    }
}
src/main/java/com/dji/sample/control/model/enums/DrcMethodEnum.java
New file
@@ -0,0 +1,22 @@
package com.dji.sample.control.model.enums;
import lombok.Getter;
/**
 * @author sean
 * @version 1.3
 * @date 2023/1/11
 */
@Getter
public enum DrcMethodEnum {
    DRC_MODE_ENTER("drc_mode_enter"),
    DRC_MODE_EXIT("drc_mode_exit");
    String method;
    DrcMethodEnum(String method) {
        this.method = method;
    }
}
src/main/java/com/dji/sample/control/model/enums/DrcModeReasonEnum.java
New file
@@ -0,0 +1,47 @@
package com.dji.sample.control.model.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/14
 */
public enum DrcModeReasonEnum {
    UNKNOWN(-1, "unknown"),
    RC_LOST(0, "The remote controller is lost."),
    BATTERY_LOW_GO_HOME(1, "Due to low battery, the drone automatically returned home."),
    BATTERY_SUPER_LOW_LANDING(2, "Due to the serious low battery, the drone landed automatically."),
    NEAR_BOUNDARY(3, "The drone is near a not-fly zone."),
    RC_AUTHORITY(4, "The remote controller grabs control authority.");
    int val;
    String message;
    DrcModeReasonEnum(int val, String message) {
        this.val = val;
        this.message = message;
    }
    public int getVal() {
        return val;
    }
    public String getMessage() {
        return message;
    }
    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
    public static DrcModeReasonEnum find(int val) {
        return Arrays.stream(values()).filter(reasonEnum -> reasonEnum.val == val).findAny().orElse(UNKNOWN);
    }
}
src/main/java/com/dji/sample/control/model/enums/DrcStatusErrorEnum.java
New file
@@ -0,0 +1,52 @@
package com.dji.sample.control.model.enums;
import com.dji.sample.common.error.IErrorInfo;
import com.fasterxml.jackson.annotation.JsonCreator;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/17
 */
public enum DrcStatusErrorEnum implements IErrorInfo {
    SUCCESS(0, "success"),
    MQTT_ERR(514300, "The mqtt connection error."),
    HEARTBEAT_TIMEOUT(514301, "The heartbeat times out and the dock disconnects."),
    MQTT_CERTIFICATE_ERR(514302, "The mqtt certificate is abnormal and the connection fails."),
    MQTT_LOST(514303, "The dock network is abnormal and the mqtt connection is lost."),
    MQTT_REFUSE(514304, "The dock connection to mqtt server was refused."),
    UNKNOWN(-1, "Unknown");
    String msg;
    int code;
    DrcStatusErrorEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    @Override
    public String getErrorMsg() {
        return msg;
    }
    @Override
    public Integer getErrorCode() {
        return code;
    }
    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
    public static DrcStatusErrorEnum find(int code) {
        return Arrays.stream(values()).filter(error -> error.code == code).findAny().orElse(UNKNOWN);
    }
}
src/main/java/com/dji/sample/control/model/enums/DroneAuthorityEnum.java
New file
@@ -0,0 +1,25 @@
package com.dji.sample.control.model.enums;
import com.fasterxml.jackson.annotation.JsonValue;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/1
 */
public enum DroneAuthorityEnum {
    FLIGHT(1), PAYLOAD(2);
    int val;
    DroneAuthorityEnum(int val) {
        this.val = val;
    }
    @JsonValue
    public int getVal() {
        return val;
    }
}
src/main/java/com/dji/sample/control/model/enums/DroneControlMethodEnum.java
New file
@@ -0,0 +1,28 @@
package com.dji.sample.control.model.enums;
import lombok.Getter;
/**
 * @author sean
 * @version 1.3
 * @date 2023/2/21
 */
@Getter
public enum DroneControlMethodEnum {
    FLIGHT_AUTHORITY_GRAB("flight_authority_grab"),
    PAYLOAD_AUTHORITY_GRAB("payload_authority_grab"),
    FLY_TO_POINT("fly_to_point"),
    FLY_TO_POINT_STOP("fly_to_point_stop"),
    TAKE_OFF_TO_POINT("takeoff_to_point");
    String method;
    DroneControlMethodEnum(String method) {
        this.method = method;
    }
}
src/main/java/com/dji/sample/control/model/enums/FlyToStatusEnum.java
New file
@@ -0,0 +1,39 @@
package com.dji.sample.control.model.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/14
 */
public enum FlyToStatusEnum {
    WAYLINE_PROGRESS("wayline_progress", "The FlyTo job is in progress."),
    WAYLINE_FAILED("wayline_failed", "The Fly To task execution failed."),
    WAYLINE_OK("wayline_ok", "The FlyTo job executed successfully."),
    WAYLINE_CANCEL("wayline_cancel", "The FlyTo job is closed.");
    String status;
    String message;
    FlyToStatusEnum(String status, String message) {
        this.status = status;
        this.message = message;
    }
    public String getMessage() {
        return message;
    }
    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
    public static FlyToStatusEnum find(String status) {
        return Arrays.stream(values()).filter(statusEnum -> statusEnum.status.equals(status)).findAny().get();
    }
}
src/main/java/com/dji/sample/control/model/enums/GimbalResetModeEnum.java
New file
@@ -0,0 +1,26 @@
package com.dji.sample.control.model.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/13
 */
public enum GimbalResetModeEnum {
    RECENTER, DOWN, RECENTER_PAN, PITCH_DOWN;
    @JsonValue
    public int getVal() {
        return ordinal();
    }
    @JsonCreator
    public static GimbalResetModeEnum find(int value) {
        return Arrays.stream(values()).filter(resetModeEnum -> resetModeEnum.ordinal() == value).findAny().get();
    }
}
src/main/java/com/dji/sample/control/model/enums/MqttAclAccessEnum.java
New file
@@ -0,0 +1,24 @@
package com.dji.sample.control.model.enums;
import lombok.Getter;
/**
 * @author sean
 * @version 1.3
 * @date 2023/1/13
 */
@Getter
public enum MqttAclAccessEnum {
    SUB(1),
    PUB(2),
    ALL(3);
    int value;
    MqttAclAccessEnum(int value) {
        this.value = value;
    }
}
src/main/java/com/dji/sample/control/model/enums/PayloadCommandsEnum.java
New file
@@ -0,0 +1,52 @@
package com.dji.sample.control.model.enums;
import com.dji.sample.control.service.impl.*;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/2
 */
public enum PayloadCommandsEnum {
    CAMERA_MODE_SWitCH("camera_mode_switch", CameraModeSwitchImpl.class),
    CAMERA_PHOTO_TAKE("camera_photo_take", CameraPhotoTakeImpl.class),
    CAMERA_RECORDING_START("camera_recording_start", CameraRecordingStartImpl.class),
    CAMERA_RECORDING_STOP("camera_recording_stop", CameraRecordingStopImpl.class),
    CAMERA_AIM("camera_aim", CameraAimImpl.class),
    CAMERA_FOCAL_LENGTH_SET("camera_focal_length_set", CameraFocalLengthSetImpl.class),
    GIMBAL_RESET("gimbal_reset", GimbalResetImpl.class);
    String cmd;
    Class<? extends PayloadCommandsHandler> clazz;
    PayloadCommandsEnum(String cmd, Class<? extends PayloadCommandsHandler> clazz) {
        this.cmd = cmd;
        this.clazz = clazz;
    }
    @JsonValue
    public String getCmd() {
        return cmd;
    }
    public Class<? extends PayloadCommandsHandler> getClazz() {
        return clazz;
    }
    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
    public static PayloadCommandsEnum find(String cmd) {
        return Arrays.stream(values()).filter(cmdEnum -> cmdEnum.cmd.equals(cmd)).findAny().get();
    }
}
src/main/java/com/dji/sample/control/model/enums/RemoteDebugMethodEnum.java
File was renamed from src/main/java/com/dji/sample/control/model/enums/RemoteControlMethodEnum.java
@@ -1,9 +1,7 @@
package com.dji.sample.control.model.enums;
import com.dji.sample.control.model.dto.AlarmState;
import com.dji.sample.control.model.dto.BatteryStoreMode;
import com.dji.sample.control.model.dto.LinkWorkMode;
import com.dji.sample.manage.model.receiver.BasicDeviceProperty;
import com.dji.sample.control.model.dto.*;
import com.dji.sample.control.service.impl.RemoteDebugHandler;
import lombok.Getter;
import java.util.Arrays;
@@ -14,9 +12,9 @@
 * @date 2022/11/14
 */
@Getter
public enum RemoteControlMethodEnum {
public enum RemoteDebugMethodEnum {
    DEBUG_MODE_OPEN("debug_mode_open", false, null),
    DEBUG_MODE_OPEN("debug_mode_open", false, RemoteDebugOpenState.class),
    DEBUG_MODE_CLOSE("debug_mode_close", false, null),
@@ -24,7 +22,7 @@
    SUPPLEMENT_LIGHT_CLOSE("supplement_light_close", false, null),
    RETURN_HOME("return_home", false, null),
    RETURN_HOME("return_home", false, ReturnHomeState.class),
    DEVICE_REBOOT("device_reboot", true, null),
@@ -64,16 +62,16 @@
    private Boolean progress;
    
    private Class<? extends BasicDeviceProperty> clazz;
    private Class<? extends RemoteDebugHandler> clazz;
    RemoteControlMethodEnum(String method, Boolean progress, Class<? extends BasicDeviceProperty> clazz) {
    RemoteDebugMethodEnum(String method, Boolean progress, Class<? extends RemoteDebugHandler> clazz) {
        this.method = method;
        this.progress = progress;
        this.clazz = clazz;
    }
    public static RemoteControlMethodEnum find(String method) {
        return Arrays.stream(RemoteControlMethodEnum.values())
    public static RemoteDebugMethodEnum find(String method) {
        return Arrays.stream(RemoteDebugMethodEnum.values())
                .filter(methodEnum -> methodEnum.method.equals(method))
                .findAny()
                .orElse(UNKNOWN);
src/main/java/com/dji/sample/control/model/enums/TakeoffStatusEnum.java
New file
@@ -0,0 +1,47 @@
package com.dji.sample.control.model.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/17
 */
public enum TakeoffStatusEnum {
    TASK_READY("task_ready", "The drone is preparing to take off."),
    WAYLINE_PROGRESS("wayline_progress", "The drone is taking off."),
    WAYLINE_FAILED("wayline_failed", "The drone failed to take off."),
    WAYLINE_OK("wayline_ok", "The drone took off successfully."),
    WAYLINE_CANCEL("wayline_cancel", "The drone takeoff job has been cancelled."),
    TASK_FINISH("task_finish", "The drone takeoff job is completed.");
    String status;
    String message;
    TakeoffStatusEnum(String status, String message) {
        this.status = status;
        this.message = message;
    }
    public String getStatus() {
        return status;
    }
    public String getMessage() {
        return message;
    }
    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
    public static TakeoffStatusEnum find(String status) {
        return Arrays.stream(values()).filter(statusEnum -> statusEnum.status.equals(status)).findAny().get();
    }
}
src/main/java/com/dji/sample/control/model/param/DeviceDrcInfoParam.java
New file
@@ -0,0 +1,19 @@
package com.dji.sample.control.model.param;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
/**
 * @author sean
 * @version 1.3
 * @date 2023/2/2
 */
@Data
public class DeviceDrcInfoParam {
    @Range(min = 1, max = 30)
    private Integer osdFrequency = 10;
    @Range(min = 1, max = 30)
    private Integer hsiFrequency = 1;
}
src/main/java/com/dji/sample/control/model/param/DrcConnectParam.java
New file
@@ -0,0 +1,19 @@
package com.dji.sample.control.model.param;
import com.dji.sample.component.redis.RedisConst;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
/**
 * @author sean
 * @version 1.3
 * @date 2023/1/11
 */
@Data
public class DrcConnectParam {
    private String clientId;
    @Range(min = 1800, max = 86400)
    private long expireSec = RedisConst.DRC_MODE_ALIVE_SECOND;
}
src/main/java/com/dji/sample/control/model/param/DrcModeParam.java
New file
@@ -0,0 +1,35 @@
package com.dji.sample.control.model.param;
import com.dji.sample.component.redis.RedisConst;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Range;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
/**
 * @author sean
 * @version 1.3
 * @date 2023/1/11
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DrcModeParam {
    @NotBlank
    private String clientId;
    @NotBlank
    private String dockSn;
    @Range(min = 1800, max = 86400)
    private long expireSec = RedisConst.DRC_MODE_ALIVE_SECOND;
    @Valid
    private DeviceDrcInfoParam deviceInfo = new DeviceDrcInfoParam();
}
src/main/java/com/dji/sample/control/model/param/DronePayloadParam.java
New file
@@ -0,0 +1,54 @@
package com.dji.sample.control.model.param;
import com.dji.sample.control.model.enums.CameraModeEnum;
import com.dji.sample.control.model.enums.CameraTypeEnum;
import com.dji.sample.control.model.enums.GimbalResetModeEnum;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/1
 */
@Data
public class DronePayloadParam {
    @Pattern(regexp = "\\d+-\\d+-\\d+")
    @NotNull
    private String payloadIndex;
    private CameraTypeEnum cameraType;
    @Range(min = 2, max = 200)
    private Float zoomFactor;
    private CameraModeEnum cameraMode;
    /**
     * true: Lock the gimbal, the gimbal and the drone rotate together.
     * false: Only the gimbal rotates, but the drone does not.
     */
    private Boolean locked;
    private Double pitchSpeed;
    /**
     * Only valid when locked is false.
     */
    private Double yawSpeed;
    /**
     * upper left corner as center point
     */
    @Range(min = 0, max = 1)
    private Double x;
    @Range(min = 0, max = 1)
    private Double y;
    private GimbalResetModeEnum resetMode;
}
src/main/java/com/dji/sample/control/model/param/FlyToPointParam.java
New file
@@ -0,0 +1,37 @@
package com.dji.sample.control.model.param;
import com.dji.sample.control.model.dto.PointDTO;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Range;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;
/**
 * @author sean
 * @version 1.3
 * @date 2023/2/14
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class FlyToPointParam {
    private String flyToId;
    @Range(min = 1, max = 15)
    @NotNull
    private Integer maxSpeed;
    /**
     * The M30 series only support one point.
     */
    @Size(min = 1)
    @Valid
    @NotNull
    private List<PointDTO> points;
}
src/main/java/com/dji/sample/control/model/param/PayloadCommandsParam.java
New file
@@ -0,0 +1,27 @@
package com.dji.sample.control.model.param;
import com.dji.sample.control.model.enums.PayloadCommandsEnum;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/2
 */
@Data
public class PayloadCommandsParam {
    private String sn;
    @NotNull
    @Valid
    private PayloadCommandsEnum cmd;
    @Valid
    @NotNull
    private DronePayloadParam data;
}
src/main/java/com/dji/sample/control/model/param/TakeoffToPointParam.java
New file
@@ -0,0 +1,49 @@
package com.dji.sample.control.model.param;
import com.dji.sample.manage.model.enums.DroneRcLostActionEnum;
import com.dji.sample.manage.model.enums.WaylineRcLostActionEnum;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotNull;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/1
 */
@Data
public class TakeoffToPointParam {
    private String flightId;
    @Range(min = -180, max = 180)
    @NotNull
    private Double targetLongitude;
    @Range(min = -90, max = 90)
    @NotNull
    private Double targetLatitude;
    @Range(min = 2, max = 1500)
    @NotNull
    private Double targetHeight;
    @Range(min = 2, max = 1500)
    @NotNull
    private Double securityTakeoffHeight;
    @Range(min = 2, max = 1500)
    @NotNull
    private Double rthAltitude;
    @NotNull
    private DroneRcLostActionEnum rcLostAction;
    @NotNull
    private WaylineRcLostActionEnum exitWaylineWhenRcLost;
    @Range(min = 1, max = 15)
    @NotNull
    private Double maxSpeed;
}
src/main/java/com/dji/sample/control/service/IControlService.java
@@ -2,7 +2,8 @@
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.control.model.param.RemoteDebugParam;
import com.dji.sample.control.model.enums.DroneAuthorityEnum;
import com.dji.sample.control.model.param.*;
import org.springframework.messaging.MessageHeaders;
/**
@@ -19,13 +20,60 @@
     * @param param
     * @return
     */
    ResponseResult controlDock(String sn, String serviceIdentifier, RemoteDebugParam param);
    ResponseResult controlDockDebug(String sn, String serviceIdentifier, RemoteDebugParam param);
    /**
     * Handles multi-state command progress information.
     * Make the drone fly to the target point.
     * @param sn
     * @param param
     * @return
     */
    ResponseResult flyToPoint(String sn, FlyToPointParam param);
    /**
     * End the mission of flying the drone to the target point.
     * @param sn
     * @return
     */
    ResponseResult flyToPointStop(String sn);
    /**
     * Handle progress result notifications for fly to target point.
     * @param receiver
     * @param headers
     * @return
     */
    void handleControlProgress(CommonTopicReceiver receiver, MessageHeaders headers);
    CommonTopicReceiver handleFlyToPointProgress(CommonTopicReceiver receiver, MessageHeaders headers);
    /**
     * Control the drone to take off.
     * @param sn
     * @param param
     * @return
     */
    ResponseResult takeoffToPoint(String sn, TakeoffToPointParam param);
    /**
     * Handle progress result notifications for takeoff to target point.
     * @param receiver
     * @param headers
     * @return
     */
    CommonTopicReceiver handleTakeoffToPointProgress(CommonTopicReceiver receiver, MessageHeaders headers);
    /**
     * Seize the control authority of the drone or the payload control authority.
     * @param sn
     * @param authority
     * @param param
     * @return
     */
    ResponseResult seizeAuthority(String sn, DroneAuthorityEnum authority, DronePayloadParam param);
    /**
     * Control the payload of the drone.
     * @param param
     * @return
     */
    ResponseResult payloadCommands(PayloadCommandsParam param) throws Exception;
}
src/main/java/com/dji/sample/control/service/IDrcService.java
New file
@@ -0,0 +1,60 @@
package com.dji.sample.control.service;
import com.dji.sample.control.model.dto.JwtAclDTO;
import com.dji.sample.control.model.dto.MqttBrokerDTO;
import com.dji.sample.control.model.param.DrcConnectParam;
import com.dji.sample.control.model.param.DrcModeParam;
/**
 * @author sean
 * @version 1.3
 * @date 2023/1/11
 */
public interface IDrcService {
    /**
     * Save the drc mode of dock in redis.
     * @param dockSn
     * @param clientId
     */
    void setDrcModeInRedis(String dockSn, String clientId);
    /**
     * Query the client that is controlling the dock.
     * @param dockSn
     * @return clientId
     */
    String getDrcModeInRedis(String dockSn);
    /**
     * Delete the drc mode of dock in redis.
     * @param dockSn
     * @return
     */
    Boolean delDrcModeInRedis(String dockSn);
    /**
     * Provide mqtt options for the control terminal.
     * @param workspaceId
     * @param userId
     * @param username
     * @param param
     * @return
     */
    MqttBrokerDTO userDrcAuth(String workspaceId, String userId, String username, DrcConnectParam param);
    /**
     * Make the dock enter drc mode. And grant relevant permissions.
     * @param workspaceId
     * @param param
     * @return
     */
    JwtAclDTO deviceDrcEnter(String workspaceId, DrcModeParam param);
    /**
     * Make the dock exit drc mode.
     * @param workspaceId
     * @param param
     */
    void deviceDrcExit(String workspaceId, DrcModeParam param);
}
src/main/java/com/dji/sample/control/service/impl/CameraAimImpl.java
New file
@@ -0,0 +1,24 @@
package com.dji.sample.control.service.impl;
import com.dji.sample.control.model.param.DronePayloadParam;
import java.util.Objects;
/**
 * @author sean
 * @version 1.4
 * @date 2023/4/23
 */
public class CameraAimImpl extends PayloadCommandsHandler {
    public CameraAimImpl(DronePayloadParam param) {
        super(param);
    }
    @Override
    public boolean valid() {
        return Objects.nonNull(param.getX()) && Objects.nonNull(param.getY())
                && Objects.nonNull(param.getLocked()) && Objects.nonNull(param.getCameraType());
    }
}
src/main/java/com/dji/sample/control/service/impl/CameraFocalLengthSetImpl.java
New file
@@ -0,0 +1,41 @@
package com.dji.sample.control.service.impl;
import com.dji.sample.control.model.enums.CameraStateEnum;
import com.dji.sample.control.model.enums.CameraTypeEnum;
import com.dji.sample.control.model.param.DronePayloadParam;
import java.util.Objects;
/**
 * @author sean
 * @version 1.4
 * @date 2023/4/23
 */
public class CameraFocalLengthSetImpl extends PayloadCommandsHandler {
    public CameraFocalLengthSetImpl(DronePayloadParam param) {
        super(param);
    }
    @Override
    public boolean valid() {
        return Objects.nonNull(param.getCameraType()) && Objects.nonNull(param.getZoomFactor())
                && (CameraTypeEnum.ZOOM == param.getCameraType()
                || CameraTypeEnum.IR == param.getCameraType());
    }
    @Override
    public boolean canPublish(String deviceSn) {
        super.canPublish(deviceSn);
        if (CameraStateEnum.WORKING == osdCamera.getPhotoState()) {
            return false;
        }
        switch (param.getCameraType()) {
            case IR:
                return param.getZoomFactor().intValue() != osdCamera.getIrZoomFactor();
            case ZOOM:
                return param.getZoomFactor().intValue() != osdCamera.getZoomFactor();
        }
        return false;
    }
}
src/main/java/com/dji/sample/control/service/impl/CameraModeSwitchImpl.java
New file
@@ -0,0 +1,31 @@
package com.dji.sample.control.service.impl;
import com.dji.sample.control.model.enums.CameraStateEnum;
import com.dji.sample.control.model.param.DronePayloadParam;
import java.util.Objects;
/**
 * @author sean
 * @version 1.4
 * @date 2023/4/23
 */
public class CameraModeSwitchImpl extends PayloadCommandsHandler {
    public CameraModeSwitchImpl(DronePayloadParam param) {
        super(param);
    }
    @Override
    public boolean valid() {
        return Objects.nonNull(param.getCameraMode());
    }
    @Override
    public boolean canPublish(String deviceSn) {
        super.canPublish(deviceSn);
        return param.getCameraMode() != osdCamera.getCameraMode()
                && CameraStateEnum.IDLE == osdCamera.getPhotoState()
                && CameraStateEnum.IDLE == osdCamera.getRecordingState();
    }
}
src/main/java/com/dji/sample/control/service/impl/CameraPhotoTakeImpl.java
New file
@@ -0,0 +1,22 @@
package com.dji.sample.control.service.impl;
import com.dji.sample.control.model.enums.CameraStateEnum;
import com.dji.sample.control.model.param.DronePayloadParam;
/**
 * @author sean
 * @version 1.4
 * @date 2023/4/23
 */
public class CameraPhotoTakeImpl extends PayloadCommandsHandler {
    public CameraPhotoTakeImpl(DronePayloadParam param) {
        super(param);
    }
    @Override
    public boolean canPublish(String deviceSn) {
        super.canPublish(deviceSn);
        return CameraStateEnum.WORKING != osdCamera.getPhotoState() && osdCamera.getRemainPhotoNum() > 0;
    }
}
src/main/java/com/dji/sample/control/service/impl/CameraRecordingStartImpl.java
New file
@@ -0,0 +1,25 @@
package com.dji.sample.control.service.impl;
import com.dji.sample.control.model.enums.CameraModeEnum;
import com.dji.sample.control.model.enums.CameraStateEnum;
import com.dji.sample.control.model.param.DronePayloadParam;
/**
 * @author sean
 * @version 1.4
 * @date 2023/4/23
 */
public class CameraRecordingStartImpl extends PayloadCommandsHandler {
    public CameraRecordingStartImpl(DronePayloadParam param) {
        super(param);
    }
    @Override
    public boolean canPublish(String deviceSn) {
        super.canPublish(deviceSn);
        return CameraModeEnum.VIDEO == osdCamera.getCameraMode()
                && CameraStateEnum.IDLE == osdCamera.getRecordingState()
                && osdCamera.getRemainRecordDuration() > 0;
    }
}
src/main/java/com/dji/sample/control/service/impl/CameraRecordingStopImpl.java
New file
@@ -0,0 +1,22 @@
package com.dji.sample.control.service.impl;
import com.dji.sample.control.model.enums.CameraStateEnum;
import com.dji.sample.control.model.param.DronePayloadParam;
/**
 * @author sean
 * @version 1.4
 * @date 2023/4/23
 */
public class CameraRecordingStopImpl extends PayloadCommandsHandler {
    public CameraRecordingStopImpl(DronePayloadParam param) {
        super(param);
    }
    @Override
    public boolean canPublish(String deviceSn) {
        super.canPublish(deviceSn);
        return CameraStateEnum.WORKING == osdCamera.getRecordingState();
    }
}
src/main/java/com/dji/sample/control/service/impl/ControlServiceImpl.java
@@ -6,26 +6,34 @@
import com.dji.sample.component.mqtt.service.IMessageSenderService;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
import com.dji.sample.component.websocket.model.BizCodeEnum;
import com.dji.sample.component.websocket.service.ISendMessageService;
import com.dji.sample.component.websocket.service.IWebSocketManageService;
import com.dji.sample.control.model.enums.RemoteControlMethodEnum;
import com.dji.sample.control.model.param.RemoteDebugParam;
import com.dji.sample.control.model.dto.FlyToProgressReceiver;
import com.dji.sample.control.model.dto.ResultNotifyDTO;
import com.dji.sample.control.model.dto.TakeoffProgressReceiver;
import com.dji.sample.control.model.enums.DroneAuthorityEnum;
import com.dji.sample.control.model.enums.DroneControlMethodEnum;
import com.dji.sample.control.model.enums.RemoteDebugMethodEnum;
import com.dji.sample.control.model.param.*;
import com.dji.sample.control.service.IControlService;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.enums.DeviceModeCodeEnum;
import com.dji.sample.manage.model.enums.DockModeCodeEnum;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import com.dji.sample.manage.model.receiver.BasicDeviceProperty;
import com.dji.sample.manage.service.IDevicePayloadService;
import com.dji.sample.manage.service.IDeviceRedisService;
import com.dji.sample.manage.service.IDeviceService;
import com.dji.sample.wayline.model.enums.WaylineErrorCodeEnum;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Service;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
/**
@@ -44,55 +52,49 @@
    private ISendMessageService webSocketMessageService;
    @Autowired
    private IWebSocketManageService webSocketManageService;
    private IDeviceService deviceService;
    @Autowired
    private IDeviceService deviceService;
    private IDeviceRedisService deviceRedisService;
    @Autowired
    private ObjectMapper mapper;
    @Autowired
    private IDevicePayloadService devicePayloadService;
    private RemoteDebugHandler checkDebugCondition(String sn, RemoteDebugParam param, RemoteDebugMethodEnum controlMethodEnum) {
        RemoteDebugHandler handler = Objects.nonNull(controlMethodEnum.getClazz()) ?
                mapper.convertValue(Objects.nonNull(param) ? param : new Object(), controlMethodEnum.getClazz())
                : new RemoteDebugHandler();
        if (!handler.canPublish(sn)) {
            throw new RuntimeException("The current state of the dock does not support this function.");
        }
        if (Objects.nonNull(param) && !handler.valid()) {
            throw new RuntimeException(CommonErrorEnum.ILLEGAL_ARGUMENT.getErrorMsg());
        }
        return handler;
    }
    @Override
    public ResponseResult controlDock(String sn, String serviceIdentifier, RemoteDebugParam param) {
        RemoteControlMethodEnum controlMethodEnum = RemoteControlMethodEnum.find(serviceIdentifier);
        if (RemoteControlMethodEnum.UNKNOWN == controlMethodEnum) {
    public ResponseResult controlDockDebug(String sn, String serviceIdentifier, RemoteDebugParam param) {
        RemoteDebugMethodEnum controlMethodEnum = RemoteDebugMethodEnum.find(serviceIdentifier);
        if (RemoteDebugMethodEnum.UNKNOWN == controlMethodEnum) {
            return ResponseResult.error("The " + serviceIdentifier + " method does not exist.");
        }
        Object data = "";
        // Add parameter validation.
        if (Objects.nonNull(controlMethodEnum.getClazz())) {
            if (Objects.isNull(param)) {
                return ResponseResult.error(CommonErrorEnum.ILLEGAL_ARGUMENT);
            }
            BasicDeviceProperty basicDeviceProperty = mapper.convertValue(param.getAction(), controlMethodEnum.getClazz());
            if (!basicDeviceProperty.valid()) {
                return ResponseResult.error(CommonErrorEnum.ILLEGAL_ARGUMENT);
            }
            data = basicDeviceProperty;
        }
        RemoteDebugHandler data = checkDebugCondition(sn, param, controlMethodEnum);
        boolean isExist = deviceService.checkDeviceOnline(sn);
        boolean isExist = deviceRedisService.checkDeviceOnline(sn);
        if (!isExist) {
            return ResponseResult.error("The dock is offline.");
        }
        String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + sn + TopicConst.SERVICES_SUF;
        String bid = UUID.randomUUID().toString();
        ServiceReply serviceReplyOpt = messageSenderService.publishWithReply(
                topic, CommonTopicResponse.builder()
                        .tid(UUID.randomUUID().toString())
                        .bid(bid)
                        .method(serviceIdentifier)
                        .timestamp(System.currentTimeMillis())
                        .data(data)
                        .build());
        ServiceReply serviceReply = messageSenderService.publishServicesTopic(sn, serviceIdentifier, data, bid);
        ServiceReply<EventsOutputReceiver> serviceReply = mapper.convertValue(
                serviceReplyOpt, new TypeReference<ServiceReply<EventsOutputReceiver>>() {});
        if (ResponseResult.CODE_SUCCESS != serviceReply.getResult()) {
            return ResponseResult.error(serviceReply.getResult(),
                    Objects.nonNull(serviceReply.getOutput()) ? serviceReply.getOutput().getStatus()
                            : "error: " + serviceIdentifier + serviceReply.getResult());
                    "error: " + serviceIdentifier + serviceReply.getResult());
        }
        if (controlMethodEnum.getProgress()) {
            RedisOpsUtils.setWithExpire(serviceIdentifier + RedisConst.DELIMITER +  bid, sn,
@@ -101,17 +103,22 @@
        return ResponseResult.success();
    }
    @Override
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_CONTROL_PROGRESS, outputChannel = ChannelName.OUTBOUND)
    public void handleControlProgress(CommonTopicReceiver receiver, MessageHeaders headers) {
    /**
     * Handles multi-state command progress information.
     * @param receiver
     * @param headers
     * @return
     */
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_CONTROL_PROGRESS, outputChannel = ChannelName.OUTBOUND_EVENTS)
    public CommonTopicReceiver handleControlProgress(CommonTopicReceiver receiver, MessageHeaders headers) {
        String key = receiver.getMethod() + RedisConst.DELIMITER + receiver.getBid();
        if (RedisOpsUtils.getExpire(key) <= 0) {
            return;
            return receiver;
        }
        String sn = RedisOpsUtils.get(key).toString();
        EventsReceiver<EventsOutputReceiver> eventsReceiver = mapper.convertValue(receiver.getData(),
                new TypeReference<EventsReceiver<EventsOutputReceiver>>(){});
        EventsReceiver<EventsOutputProgressReceiver> eventsReceiver = mapper.convertValue(receiver.getData(),
                new TypeReference<EventsReceiver<EventsOutputProgressReceiver>>(){});
        eventsReceiver.setBid(receiver.getBid());
        eventsReceiver.setSn(sn);
@@ -127,26 +134,168 @@
            RedisOpsUtils.del(key);
        }
        DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + sn);
        webSocketMessageService.sendBatch(
                webSocketManageService.getValueWithWorkspaceAndUserType(
                        device.getWorkspaceId(), UserTypeEnum.WEB.getVal()),
                CustomWebSocketMessage.builder()
                        .data(eventsReceiver)
                        .timestamp(System.currentTimeMillis())
                        .bizCode(receiver.getMethod())
                        .build());
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(sn);
        if (receiver.getNeedReply() != null && receiver.getNeedReply() == 1) {
            String topic = headers.get(MqttHeaders.RECEIVED_TOPIC) + TopicConst._REPLY_SUF;
            messageSenderService.publish(topic,
                    CommonTopicResponse.builder()
                            .tid(receiver.getTid())
                            .bid(receiver.getBid())
                            .method(receiver.getMethod())
                            .timestamp(System.currentTimeMillis())
                            .data(RequestsReply.success())
                            .build());
        if (deviceOpt.isEmpty()) {
            throw new RuntimeException("The device is offline.");
        }
        DeviceDTO device = deviceOpt.get();
        webSocketMessageService.sendBatch(device.getWorkspaceId(), UserTypeEnum.WEB.getVal(),
                receiver.getMethod(), eventsReceiver);
        return receiver;
    }
    private void checkFlyToCondition(String dockSn) {
        // TODO 设备固件版本不兼容情况
        Optional<DeviceDTO> dockOpt = deviceRedisService.getDeviceOnline(dockSn);
        if (dockOpt.isEmpty()) {
            throw new RuntimeException("The dock is offline, please restart the dock.");
        }
        DeviceModeCodeEnum deviceMode = deviceService.getDeviceMode(dockOpt.get().getChildDeviceSn());
        if (DeviceModeCodeEnum.MANUAL != deviceMode) {
            throw new RuntimeException("The current state of the drone does not support this function, please try again later.");
        }
        ResponseResult result = seizeAuthority(dockSn, DroneAuthorityEnum.FLIGHT, null);
        if (ResponseResult.CODE_SUCCESS != result.getCode()) {
            throw new IllegalArgumentException(result.getMessage());
        }
    }
    @Override
    public ResponseResult flyToPoint(String sn, FlyToPointParam param) {
        checkFlyToCondition(sn);
        param.setFlyToId(UUID.randomUUID().toString());
        ServiceReply reply = messageSenderService.publishServicesTopic(sn, DroneControlMethodEnum.FLY_TO_POINT.getMethod(), param, param.getFlyToId());
        return ResponseResult.CODE_SUCCESS != reply.getResult() ?
                ResponseResult.error("Flying to the target point failed." + reply.getResult())
                : ResponseResult.success();
    }
    @Override
    public ResponseResult flyToPointStop(String sn) {
        ServiceReply reply = messageSenderService.publishServicesTopic(sn, DroneControlMethodEnum.FLY_TO_POINT_STOP.getMethod(), null);
        return ResponseResult.CODE_SUCCESS != reply.getResult() ?
                ResponseResult.error("The drone flying to the target point failed to stop. " + reply.getResult())
                : ResponseResult.success();
    }
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FLY_TO_POINT_PROGRESS, outputChannel = ChannelName.OUTBOUND_EVENTS)
    public CommonTopicReceiver handleFlyToPointProgress(CommonTopicReceiver receiver, MessageHeaders headers) {
        String dockSn  = receiver.getGateway();
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(dockSn);
        if (deviceOpt.isEmpty()) {
            log.error("The dock is offline.");
            return null;
        }
        FlyToProgressReceiver eventsReceiver = mapper.convertValue(receiver.getData(), new TypeReference<FlyToProgressReceiver>(){});
        webSocketMessageService.sendBatch(deviceOpt.get().getWorkspaceId(), UserTypeEnum.WEB.getVal(),
                BizCodeEnum.FLY_TO_POINT_PROGRESS.getCode(),
                ResultNotifyDTO.builder().sn(dockSn)
                        .message(WaylineErrorCodeEnum.SUCCESS == eventsReceiver.getResult() ?
                                eventsReceiver.getStatus().getMessage() : eventsReceiver.getResult().getErrorMsg())
                        .result(eventsReceiver.getResult().getErrorCode())
                        .build());
        return receiver;
    }
    private void checkTakeoffCondition(String dockSn) {
        Optional<DeviceDTO> dockOpt = deviceRedisService.getDeviceOnline(dockSn);
        if (dockOpt.isEmpty() || DockModeCodeEnum.IDLE != deviceService.getDockMode(dockSn)) {
            throw new RuntimeException("The current state does not support takeoff.");
        }
        ResponseResult result = seizeAuthority(dockSn, DroneAuthorityEnum.FLIGHT, null);
        if (ResponseResult.CODE_SUCCESS != result.getCode()) {
            throw new IllegalArgumentException(result.getMessage());
        }
    }
    @Override
    public ResponseResult takeoffToPoint(String sn, TakeoffToPointParam param) {
        checkTakeoffCondition(sn);
        param.setFlightId(UUID.randomUUID().toString());
        ServiceReply reply = messageSenderService.publishServicesTopic(sn, DroneControlMethodEnum.TAKE_OFF_TO_POINT.getMethod(), param, param.getFlightId());
        return ResponseResult.CODE_SUCCESS != reply.getResult() ?
                ResponseResult.error("The drone failed to take off. " + reply.getResult())
                : ResponseResult.success();
    }
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_TAKE_OFF_TO_POINT_PROGRESS, outputChannel = ChannelName.OUTBOUND_EVENTS)
    public CommonTopicReceiver handleTakeoffToPointProgress(CommonTopicReceiver receiver, MessageHeaders headers) {
        String dockSn  = receiver.getGateway();
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(dockSn);
        if (deviceOpt.isEmpty()) {
            log.error("The dock is offline.");
            return null;
        }
        TakeoffProgressReceiver eventsReceiver = mapper.convertValue(receiver.getData(), new TypeReference<TakeoffProgressReceiver>(){});
        webSocketMessageService.sendBatch(deviceOpt.get().getWorkspaceId(), UserTypeEnum.WEB.getVal(),
                BizCodeEnum.TAKE_OFF_TO_POINT_PROGRESS.getCode(),
                ResultNotifyDTO.builder().sn(dockSn)
                        .message(WaylineErrorCodeEnum.SUCCESS == eventsReceiver.getResult() ?
                                eventsReceiver.getStatus().getMessage() : eventsReceiver.getResult().getErrorMsg())
                        .result(eventsReceiver.getResult().getErrorCode())
                        .build());
        return receiver;
    }
    @Override
    public ResponseResult seizeAuthority(String sn, DroneAuthorityEnum authority, DronePayloadParam param) {
        String method;
        switch (authority) {
            case FLIGHT:
                if (deviceService.checkAuthorityFlight(sn)) {
                    return ResponseResult.success();
                }
                method = DroneControlMethodEnum.FLIGHT_AUTHORITY_GRAB.getMethod();
                break;
            case PAYLOAD:
                if (checkPayloadAuthority(sn, param.getPayloadIndex())) {
                    return ResponseResult.success();
                }
                method = DroneControlMethodEnum.PAYLOAD_AUTHORITY_GRAB.getMethod();
                break;
            default:
                return ResponseResult.error(CommonErrorEnum.ILLEGAL_ARGUMENT);
        }
        ServiceReply serviceReply = messageSenderService.publishServicesTopic(sn, method, param);
        return ResponseResult.CODE_SUCCESS != serviceReply.getResult() ?
                ResponseResult.error(serviceReply.getResult(), "Method: " + method + " Error Code:" + serviceReply.getResult())
                : ResponseResult.success();
    }
    private Boolean checkPayloadAuthority(String sn, String payloadIndex) {
        Optional<DeviceDTO> dockOpt = deviceRedisService.getDeviceOnline(sn);
        if (dockOpt.isEmpty()) {
            throw new RuntimeException("The dock is offline, please restart the dock.");
        }
        return devicePayloadService.checkAuthorityPayload(dockOpt.get().getChildDeviceSn(), payloadIndex);
    }
    @Override
    public ResponseResult payloadCommands(PayloadCommandsParam param) throws Exception {
        param.getCmd().getClazz()
                .getDeclaredConstructor(DronePayloadParam.class)
                .newInstance(param.getData())
                .checkCondition(param.getSn());
        ServiceReply serviceReply = messageSenderService.publishServicesTopic(param.getSn(), param.getCmd().getCmd(), param.getData());
        return ResponseResult.CODE_SUCCESS != serviceReply.getResult() ?
                ResponseResult.error(serviceReply.getResult(), " Error Code:" + serviceReply.getResult())
                : ResponseResult.success();
    }
}
src/main/java/com/dji/sample/control/service/impl/DrcServiceImpl.java
New file
@@ -0,0 +1,249 @@
package com.dji.sample.control.service.impl;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.mqtt.config.MqttConfiguration;
import com.dji.sample.component.mqtt.model.*;
import com.dji.sample.component.mqtt.service.IMessageSenderService;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.component.websocket.model.BizCodeEnum;
import com.dji.sample.component.websocket.service.ISendMessageService;
import com.dji.sample.control.model.dto.*;
import com.dji.sample.control.model.enums.DrcMethodEnum;
import com.dji.sample.control.model.enums.DrcStatusErrorEnum;
import com.dji.sample.control.model.enums.DroneAuthorityEnum;
import com.dji.sample.control.model.enums.MqttAclAccessEnum;
import com.dji.sample.control.model.param.DrcConnectParam;
import com.dji.sample.control.model.param.DrcModeParam;
import com.dji.sample.control.service.IControlService;
import com.dji.sample.control.service.IDrcService;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.enums.DockModeCodeEnum;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import com.dji.sample.manage.model.receiver.OsdSubDeviceReceiver;
import com.dji.sample.manage.service.IDeviceRedisService;
import com.dji.sample.manage.service.IDeviceService;
import com.dji.sample.wayline.model.dto.WaylineTaskProgressReceiver;
import com.dji.sample.wayline.model.enums.WaylineJobStatusEnum;
import com.dji.sample.wayline.model.enums.WaylineTaskStatusEnum;
import com.dji.sample.wayline.model.param.UpdateJobParam;
import com.dji.sample.wayline.service.IWaylineJobService;
import com.dji.sample.wayline.service.IWaylineRedisService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
 * @author sean
 * @version 1.3
 * @date 2023/1/11
 */
@Service
@Slf4j
public class DrcServiceImpl implements IDrcService {
    @Autowired
    private IMessageSenderService messageSenderService;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private IWaylineJobService waylineJobService;
    @Autowired
    private IDeviceService deviceService;
    @Autowired
    private ObjectMapper mapper;
    @Autowired
    private ISendMessageService webSocketMessageService;
    @Autowired
    private IControlService controlService;
    @Autowired
    private IDeviceRedisService deviceRedisService;
    @Autowired
    private IWaylineRedisService waylineRedisService;
    @Override
    public void setDrcModeInRedis(String dockSn, String clientId) {
        RedisOpsUtils.setWithExpire(RedisConst.DRC_PREFIX + dockSn, clientId, RedisConst.DRC_MODE_ALIVE_SECOND);
    }
    @Override
    public String getDrcModeInRedis(String dockSn) {
        return (String) RedisOpsUtils.get(RedisConst.DRC_PREFIX + dockSn);
    }
    @Override
    public Boolean delDrcModeInRedis(String dockSn) {
        return RedisOpsUtils.del(RedisConst.DRC_PREFIX + dockSn);
    }
    @Override
    public MqttBrokerDTO userDrcAuth(String workspaceId, String userId, String username, DrcConnectParam param) {
        // refresh token
        String clientId = param.getClientId();
        // first time
        if (!StringUtils.hasText(clientId) || !RedisOpsUtils.checkExist(RedisConst.MQTT_ACL_PREFIX + clientId)) {
            clientId = userId + "-" + System.currentTimeMillis();
            RedisOpsUtils.hashSet(RedisConst.MQTT_ACL_PREFIX + clientId, "", MqttAclAccessEnum.ALL.getValue());
        }
        String key = RedisConst.MQTT_ACL_PREFIX + clientId;
        try {
            RedisOpsUtils.expireKey(key, RedisConst.DRC_MODE_ALIVE_SECOND);
            return MqttConfiguration.getMqttBrokerWithDrc(
                    clientId, username, param.getExpireSec(), Collections.emptyMap());
        } catch (RuntimeException e) {
            RedisOpsUtils.del(key);
            throw e;
        }
    }
    private void checkDrcModeCondition(String workspaceId, String dockSn) {
        Optional<EventsReceiver<WaylineTaskProgressReceiver>> runningOpt = waylineRedisService.getRunningWaylineJob(dockSn);
        if (runningOpt.isPresent() && WaylineJobStatusEnum.IN_PROGRESS == waylineJobService.getWaylineState(dockSn)) {
            waylineJobService.updateJobStatus(workspaceId, runningOpt.get().getBid(),
                    UpdateJobParam.builder().status(WaylineTaskStatusEnum.PAUSE).build());
        }
        DockModeCodeEnum dockMode = deviceService.getDockMode(dockSn);
        Optional<DeviceDTO> dockOpt = deviceRedisService.getDeviceOnline(dockSn);
        if (dockOpt.isPresent() && (DockModeCodeEnum.IDLE == dockMode || DockModeCodeEnum.WORKING == dockMode)) {
            Optional<OsdSubDeviceReceiver> deviceOsd = deviceRedisService.getDeviceOsd(dockOpt.get().getChildDeviceSn(), OsdSubDeviceReceiver.class);
            if (deviceOsd.isEmpty() || deviceOsd.get().getElevation() <= 0) {
                throw new RuntimeException("The drone is not in the sky and cannot enter command flight mode.");
            }
        } else {
            throw new RuntimeException("The current state of the dock does not support entering command flight mode.");
        }
        ResponseResult result = controlService.seizeAuthority(dockSn, DroneAuthorityEnum.FLIGHT, null);
        if (ResponseResult.CODE_SUCCESS != result.getCode()) {
            throw new IllegalArgumentException(result.getMessage());
        }
    }
    @Override
    public JwtAclDTO deviceDrcEnter(String workspaceId, DrcModeParam param) {
        String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + param.getDockSn() + TopicConst.DRC;
        String pubTopic = topic + TopicConst.DOWN;
        String subTopic = topic + TopicConst.UP;
        // If the dock is in drc mode, refresh the permissions directly.
        if (deviceService.checkDockDrcMode(param.getDockSn())
                && param.getClientId().equals(this.getDrcModeInRedis(param.getDockSn()))) {
            refreshAcl(param.getDockSn(), param.getClientId(), topic, subTopic);
            return JwtAclDTO.builder().sub(List.of(subTopic)).pub(List.of(pubTopic)).build();
        }
        checkDrcModeCondition(workspaceId, param.getDockSn());
        ServiceReply reply = messageSenderService.publishServicesTopic(
                param.getDockSn(), DrcMethodEnum.DRC_MODE_ENTER.getMethod(),
                DrcModeDTO.builder()
                    .mqttBroker(MqttConfiguration.getMqttBrokerWithDrc(param.getDockSn() + "-" + System.currentTimeMillis(), param.getDockSn(),
                            RedisConst.DRC_MODE_ALIVE_SECOND.longValue(),
                            Map.of(MapKeyConst.ACL, objectMapper.convertValue(JwtAclDTO.builder()
                                    .pub(List.of(subTopic))
                                    .sub(List.of(pubTopic))
                                    .build(), new TypeReference<Map<String, ?>>() {}))))
                    .build());
        if (ResponseResult.CODE_SUCCESS != reply.getResult()) {
            throw new RuntimeException("SN: " + param.getDockSn() + "; Error Code:" + reply.getResult() + "; Failed to enter command flight control mode, please try again later!");
        }
        refreshAcl(param.getDockSn(), param.getClientId(), pubTopic, subTopic);
        return JwtAclDTO.builder().sub(List.of(subTopic)).pub(List.of(pubTopic)).build();
    }
    private void refreshAcl(String dockSn, String clientId, String pubTopic, String subTopic) {
        this.setDrcModeInRedis(dockSn, clientId);
        // assign acl,Match by clientId. https://www.emqx.io/docs/zh/v4.4/advanced/acl-redis.html
        // scheme: HSET mqtt_acl:[clientid] [topic] [access]
        String key = RedisConst.MQTT_ACL_PREFIX + clientId;
        RedisOpsUtils.hashSet(key, pubTopic, MqttAclAccessEnum.PUB.getValue());
        RedisOpsUtils.hashSet(key, subTopic, MqttAclAccessEnum.SUB.getValue());
    }
    @Override
    public void deviceDrcExit(String workspaceId, DrcModeParam param) {
        if (!deviceService.checkDockDrcMode(param.getDockSn())) {
            throw new RuntimeException("The dock is not in flight control mode.");
        }
        ServiceReply reply = messageSenderService.publishServicesTopic(
                param.getDockSn(), DrcMethodEnum.DRC_MODE_EXIT.getMethod(), "");
        if (ResponseResult.CODE_SUCCESS != reply.getResult()) {
            throw new RuntimeException("SN: " + param.getDockSn() + "; Error Code:" +
                    reply.getResult() + "; Failed to exit command flight control mode, please try again later!");
        }
        String jobId = waylineRedisService.getPausedWaylineJobId(param.getDockSn());
        if (StringUtils.hasText(jobId)) {
            waylineJobService.updateJobStatus(workspaceId, jobId, UpdateJobParam.builder().status(WaylineTaskStatusEnum.RESUME).build());
        }
        this.delDrcModeInRedis(param.getDockSn());
        RedisOpsUtils.del(RedisConst.MQTT_ACL_PREFIX + param.getClientId());
    }
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_DRC_STATUS_NOTIFY, outputChannel = ChannelName.OUTBOUND_EVENTS)
    public CommonTopicReceiver handleDrcStatusNotify(CommonTopicReceiver receiver, MessageHeaders headers) {
        String dockSn  = receiver.getGateway();
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(dockSn);
        if (deviceOpt.isEmpty()) {
            return null;
        }
        DrcStatusNotifyReceiver eventsReceiver = mapper.convertValue(receiver.getData(), new TypeReference<DrcStatusNotifyReceiver>(){});
        if (DrcStatusErrorEnum.SUCCESS != eventsReceiver.getResult()) {
            webSocketMessageService.sendBatch(
                    deviceOpt.get().getWorkspaceId(), UserTypeEnum.WEB.getVal(), BizCodeEnum.DRC_STATUS_NOTIFY.getCode(),
                    ResultNotifyDTO.builder().sn(dockSn)
                            .message(eventsReceiver.getResult().getErrorMsg())
                            .result(eventsReceiver.getResult().getErrorCode()).build());
        }
        return receiver;
    }
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_DRC_MODE_EXIT_NOTIFY, outputChannel = ChannelName.OUTBOUND_EVENTS)
    public CommonTopicReceiver handleDrcModeExitNotify(CommonTopicReceiver receiver, MessageHeaders headers) {
        String dockSn  = receiver.getGateway();
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(dockSn);
        if (deviceOpt.isEmpty()) {
            return null;
        }
        DrcModeReasonReceiver eventsReceiver = mapper.convertValue(receiver.getData(), new TypeReference<DrcModeReasonReceiver>(){});
        webSocketMessageService.sendBatch(
                deviceOpt.get().getWorkspaceId(), UserTypeEnum.WEB.getVal(), BizCodeEnum.JOYSTICK_INVALID_NOTIFY.getCode(),
                        ResultNotifyDTO.builder().sn(dockSn)
                                .message(eventsReceiver.getReason().getMessage())
                                .result(eventsReceiver.getReason().getVal()).build());
        return receiver;
    }
}
src/main/java/com/dji/sample/control/service/impl/GimbalResetImpl.java
New file
@@ -0,0 +1,22 @@
package com.dji.sample.control.service.impl;
import com.dji.sample.control.model.param.DronePayloadParam;
import java.util.Objects;
/**
 * @author sean
 * @version 1.4
 * @date 2023/4/23
 */
public class GimbalResetImpl extends PayloadCommandsHandler {
    public GimbalResetImpl(DronePayloadParam param) {
        super(param);
    }
    @Override
    public boolean valid() {
        return Objects.nonNull(param.getResetMode());
    }
}
src/main/java/com/dji/sample/control/service/impl/PayloadCommandsHandler.java
New file
@@ -0,0 +1,82 @@
package com.dji.sample.control.service.impl;
import com.dji.sample.common.util.SpringBeanUtils;
import com.dji.sample.control.model.param.DronePayloadParam;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.receiver.OsdCameraReceiver;
import com.dji.sample.manage.model.receiver.OsdSubDeviceReceiver;
import com.dji.sample.manage.service.IDevicePayloadService;
import com.dji.sample.manage.service.IDeviceRedisService;
import java.util.Optional;
/**
 * @author sean
 * @version 1.4
 * @date 2023/4/23
 */
public abstract class PayloadCommandsHandler {
    DronePayloadParam param;
    OsdCameraReceiver osdCamera;
    PayloadCommandsHandler(DronePayloadParam param) {
        this.param = param;
    }
    public boolean valid() {
        return true;
    }
    public boolean canPublish(String deviceSn) {
        Optional<OsdSubDeviceReceiver> deviceOpt = SpringBeanUtils.getBean(IDeviceRedisService.class)
                .getDeviceOsd(deviceSn, OsdSubDeviceReceiver.class);
        if (deviceOpt.isEmpty()) {
            throw new RuntimeException("The device is offline.");
        }
        osdCamera = deviceOpt.get().getCameras().stream()
                .filter(osdCamera -> param.getPayloadIndex().equals(osdCamera.getPayloadIndex()))
                .findAny()
                .orElseThrow(() -> new RuntimeException("Did not receive osd information about the camera, please check the cache data."));
        return true;
    }
    private String checkDockOnline(String dockSn) {
        Optional<DeviceDTO> deviceOpt = SpringBeanUtils.getBean(IDeviceRedisService.class).getDeviceOnline(dockSn);
        if (deviceOpt.isEmpty()) {
            throw new RuntimeException("The dock is offline.");
        }
        return deviceOpt.get().getChildDeviceSn();
    }
    private void checkDeviceOnline(String deviceSn) {
        boolean isOnline = SpringBeanUtils.getBean(IDeviceRedisService.class).checkDeviceOnline(deviceSn);
        if (!isOnline) {
            throw new RuntimeException("The device is offline.");
        }
    }
    private void checkAuthority(String deviceSn) {
        boolean hasAuthority = SpringBeanUtils.getBean(IDevicePayloadService.class)
                .checkAuthorityPayload(deviceSn, param.getPayloadIndex());
        if (!hasAuthority) {
            throw new RuntimeException("The device does not have payload control authority.");
        }
    }
    public final void checkCondition(String dockSn) {
        if (!valid()) {
            throw new RuntimeException("illegal argument");
        }
        String deviceSn = checkDockOnline(dockSn);
        checkDeviceOnline(deviceSn);
        checkAuthority(deviceSn);
        if (!canPublish(deviceSn)) {
            throw new RuntimeException("The current state of the drone does not support this function, please try again later.");
        }
    }
}
src/main/java/com/dji/sample/control/service/impl/RemoteDebugHandler.java
New file
@@ -0,0 +1,23 @@
package com.dji.sample.control.service.impl;
import com.dji.sample.common.util.SpringBeanUtils;
import com.dji.sample.manage.model.enums.DockModeCodeEnum;
import com.dji.sample.manage.service.IDeviceService;
/**
 * @author sean
 * @version 1.3
 * @date 2022/10/27
 */
public class RemoteDebugHandler {
    public boolean valid() {
        return false;
    }
    public boolean canPublish(String sn) {
        IDeviceService deviceService = SpringBeanUtils.getBean(IDeviceService.class);
        DockModeCodeEnum dockMode = deviceService.getDockMode(sn);
        return DockModeCodeEnum.REMOTE_DEBUGGING == dockMode;
    }
}
src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java
File was deleted
src/main/java/com/dji/sample/manage/model/dto/DeviceAuthorityDTO.java
New file
@@ -0,0 +1,26 @@
package com.dji.sample.manage.model.dto;
import com.dji.sample.control.model.enums.DroneAuthorityEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/2
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DeviceAuthorityDTO {
    private String sn;
    private DroneAuthorityEnum type;
    private String controlSource;
}
src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java
@@ -25,7 +25,7 @@
    private String workspaceId;
    private String deviceIndex;
    private String controlSource;
    private String deviceDesc;
@@ -62,4 +62,6 @@
    private Integer firmwareStatus;
    private Integer firmwareProgress;
    private String parentSn;
}
src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java
@@ -23,7 +23,11 @@
    private String payloadName;
    private Integer payloadIndex;
    private Integer index;
    private String payloadDesc;
    private String controlSource;
    private String payloadIndex;
}
src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java
@@ -53,4 +53,7 @@
    @TableField(value = "payload_desc")
    private String payloadDesc;
    @TableField(value = "control_source")
    private String controlSource;
}
src/main/java/com/dji/sample/manage/model/enums/ControlSourceEnum.java
New file
@@ -0,0 +1,26 @@
package com.dji.sample.manage.model.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/16
 */
public enum ControlSourceEnum {
    A, B;
    @JsonValue
    public String getControlSource() {
        return name();
    }
    @JsonCreator
    public static ControlSourceEnum find(String controlSource) {
        return Arrays.stream(values()).filter(controlSourceEnum -> controlSourceEnum.name().equals(controlSource)).findAny().get();
    }
}
src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java
@@ -2,6 +2,8 @@
import lombok.Getter;
import java.util.Arrays;
/**
 *
 * @author sean.zhou
@@ -17,11 +19,17 @@
    PAYLOAD(1),
    DOCK (3);
    DOCK (3),
    UNKNOWN(-1);
    int val;
    DeviceDomainEnum(int val) {
        this.val = val;
    }
    public static DeviceDomainEnum find(int val) {
        return Arrays.stream(values()).filter(domainEnum -> domainEnum.val == val).findAny().orElse(UNKNOWN);
    }
}
src/main/java/com/dji/sample/manage/model/enums/DeviceModeCodeEnum.java
New file
@@ -0,0 +1,64 @@
package com.dji.sample.manage.model.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/9
 */
public enum DeviceModeCodeEnum {
    IDLE(0),
    TAKEOFF_PREPARE(1),
    TAKEOFF_FINISHED(2),
    MANUAL(3),
    TAKEOFF_AUTO(4),
    WAYLINE(5),
    PANORAMIC_SHOT(6),
    ACTIVE_TRACK(7),
    ADS_B_AVOIDANCE(8),
    RETURN_AUTO(9),
    LANDING_AUTO(10),
    LANDING_FORCED(11),
    LANDING_THREE_PROPELLER(12),
    UPGRADING(13),
    DISCONNECTED(14),
    APAS(15),
    VIRTUAL_JOYSTICK(16);
    int val;
    DeviceModeCodeEnum(int val) {
        this.val = val;
    }
    @JsonValue
    public int getVal() {
        return val;
    }
    @JsonCreator
    public static DeviceModeCodeEnum find(int value) {
        return Arrays.stream(values()).filter(modeCodeEnum -> modeCodeEnum.ordinal() == value).findAny().orElse(DISCONNECTED);
    }
}
src/main/java/com/dji/sample/manage/model/enums/DeviceSetPropertyEnum.java
@@ -20,8 +20,11 @@
    DISTANCE_LIMIT_STATUS("distance_limit_status", DistanceLimitStatusReceiver.class),
    OBSTACLE_AVOIDANCE("obstacle_avoidance", ObstacleAvoidanceReceiver.class);
    OBSTACLE_AVOIDANCE("obstacle_avoidance", ObstacleAvoidanceReceiver.class),
    RTH_ALTITUDE("rth_altitude", RthAltitudeReceiver.class),
    OUT_OF_CONTROL_ACTION("out_of_control_action", OutOfControlActionReceiver.class);
    String property;
src/main/java/com/dji/sample/manage/model/enums/DockDrcStateEnum.java
New file
@@ -0,0 +1,36 @@
package com.dji.sample.manage.model.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.4
 * @date 2023/2/28
 */
public enum DockDrcStateEnum {
    DISCONNECTED(0),
    CONNECTING(1),
    CONNECTED(2);
    int val;
    DockDrcStateEnum(int val) {
        this.val = val;
    }
    @JsonValue
    public int getVal() {
        return val;
    }
    @JsonCreator
    public static DockDrcStateEnum find(int val) {
        return Arrays.stream(values()).filter(drcState -> drcState.getVal() == val).findAny().orElse(DISCONNECTED);
    }
}
src/main/java/com/dji/sample/manage/model/enums/DockModeCodeEnum.java
New file
@@ -0,0 +1,42 @@
package com.dji.sample.manage.model.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.4
 * @date 2023/2/28
 */
public enum DockModeCodeEnum {
    IDLE(0),
    DEBUGGING(1),
    REMOTE_DEBUGGING(2),
    UPGRADING(3),
    WORKING(4),
    DISCONNECTED(-1);
    int val;
    DockModeCodeEnum(int val) {
        this.val = val;
    }
    @JsonValue
    public int getVal() {
        return val;
    }
    @JsonCreator
    public static DockModeCodeEnum find(int val) {
        return Arrays.stream(values()).filter(modeCode -> modeCode.getVal() == val).findAny().orElse(DISCONNECTED);
    }
}
src/main/java/com/dji/sample/manage/model/enums/DroneRcLostActionEnum.java
New file
@@ -0,0 +1,26 @@
package com.dji.sample.manage.model.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/1
 */
public enum DroneRcLostActionEnum {
    HOVER, LAND, RETURN_HOME;
    @JsonValue
    public int getVal() {
        return ordinal();
    }
    @JsonCreator
    public static DroneRcLostActionEnum find(int val) {
        return Arrays.stream(values()).filter(controlActionEnum -> controlActionEnum.ordinal() == val).findAny().get();
    }
}
src/main/java/com/dji/sample/manage/model/enums/HmsEnum.java
@@ -38,20 +38,6 @@
    }
    @Getter
    public enum DomainType {
        DRONE_NEST("drone_nest"),
        DRONE("drone");
        private String domain;
        DomainType(String domain) {
            this.domain = domain;
        }
    }
    @Getter
    public enum HmsFaqIdEnum {
        DOCK_TIP("dock_tip_"),
src/main/java/com/dji/sample/manage/model/enums/WaylineRcLostActionEnum.java
New file
@@ -0,0 +1,26 @@
package com.dji.sample.manage.model.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/15
 */
public enum WaylineRcLostActionEnum {
    CONTINUE_WAYLINE, EXECUTE_RC_LOST_ACTION;
    @JsonValue
    public int getVal() {
        return ordinal();
    }
    @JsonCreator
    public static WaylineRcLostActionEnum find(int val) {
        return Arrays.stream(values()).filter(lostActionEnum -> lostActionEnum.ordinal() == val).findAny().get();
    }
}
src/main/java/com/dji/sample/manage/model/receiver/BasicDeviceProperty.java
@@ -1,14 +1,11 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
/**
 * @author sean
 * @version 1.3
 * @date 2022/10/27
 */
@Data
public class BasicDeviceProperty {
public abstract class BasicDeviceProperty {
    public boolean valid() {
        return false;
src/main/java/com/dji/sample/manage/model/receiver/DeviceHmsReceiver.java
@@ -14,8 +14,6 @@
    private String deviceType;
    private String domainType;
    private Integer imminent;
    private Integer inTheSky;
src/main/java/com/dji/sample/manage/model/receiver/FirmwareProgressExtReceiver.java
New file
@@ -0,0 +1,14 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/30
 */
@Data
public class FirmwareProgressExtReceiver {
    private Long rate;
}
src/main/java/com/dji/sample/manage/model/receiver/LiveviewWorldRegionReceiver.java
New file
@@ -0,0 +1,20 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/8
 */
@Data
public class LiveviewWorldRegionReceiver {
    private Double bottom;
    private Double left;
    private Double right;
    private Double top;
}
src/main/java/com/dji/sample/manage/model/receiver/OsdCameraReceiver.java
New file
@@ -0,0 +1,34 @@
package com.dji.sample.manage.model.receiver;
import com.dji.sample.control.model.enums.CameraModeEnum;
import com.dji.sample.control.model.enums.CameraStateEnum;
import lombok.Data;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/8
 */
@Data
public class OsdCameraReceiver {
    private CameraModeEnum cameraMode;
    private LiveviewWorldRegionReceiver liveviewWorldRegion;
    private String payloadIndex;
    private CameraStateEnum photoState;
    private Integer recordTime;
    private CameraStateEnum recordingState;
    private Long remainPhotoNum;
    private Long remainRecordDuration;
    private Float zoomFactor;
    private Float irZoomFactor;
}
src/main/java/com/dji/sample/manage/model/receiver/OsdDockReceiver.java
@@ -1,5 +1,7 @@
package com.dji.sample.manage.model.receiver;
import com.dji.sample.manage.model.enums.DockDrcStateEnum;
import com.dji.sample.manage.model.enums.DockModeCodeEnum;
import lombok.Data;
/**
@@ -40,13 +42,13 @@
    private StorageReceiver storage;
    private Integer modeCode;
    private DockModeCodeEnum modeCode;
    private Integer coverState;
    private Integer supplementLightState;
    private Integer emergencyStopState;
    private Boolean emergencyStopState;
    private Integer airConditionerMode;
@@ -85,4 +87,6 @@
    private DockSdrReceiver sdr;
    private DockWirelessLinkReceiver wirelessLink;
    private DockDrcStateEnum drcState;
}
src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java
@@ -1,5 +1,8 @@
package com.dji.sample.manage.model.receiver;
import com.dji.sample.manage.model.enums.DeviceModeCodeEnum;
import com.dji.sample.manage.model.enums.DroneRcLostActionEnum;
import com.dji.sample.manage.model.enums.WaylineRcLostActionEnum;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
@@ -41,7 +44,7 @@
    private Double longitude;
    private Integer modeCode;
    private DeviceModeCodeEnum modeCode;
    private Double totalFlightDistance;
@@ -67,4 +70,15 @@
    private ObstacleAvoidanceReceiver obstacleAvoidance;
    private Long activationTime;
    private List<OsdCameraReceiver> cameras;
    private DroneRcLostActionEnum rcLostAction;
    private Integer rthAltitude;
    private Integer totalFlightSorties;
    private WaylineRcLostActionEnum exitWaylineWhenRcLost;
}
src/main/java/com/dji/sample/manage/model/receiver/OutOfControlActionReceiver.java
New file
@@ -0,0 +1,29 @@
package com.dji.sample.manage.model.receiver;
import com.dji.sample.manage.model.enums.DroneRcLostActionEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.Objects;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/3
 */
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OutOfControlActionReceiver extends BasicDeviceProperty {
    private Integer value;
    @Override
    public boolean valid() {
        return Objects.nonNull(value) && value >= 0 && value < DroneRcLostActionEnum.values().length;
    }
}
src/main/java/com/dji/sample/manage/model/receiver/RthAltitudeReceiver.java
New file
@@ -0,0 +1,32 @@
package com.dji.sample.manage.model.receiver;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.Objects;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/3
 */
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RthAltitudeReceiver extends BasicDeviceProperty {
    private Integer value;
    private static final int RTH_ALTITUDE_MAX = 500;
    private static final int RTH_ALTITUDE_MIN = 20;
    @Override
    public boolean valid() {
        return  Objects.nonNull(this.value) && this.value >= RTH_ALTITUDE_MIN && this.value <= RTH_ALTITUDE_MAX;
    }
}
src/main/java/com/dji/sample/manage/service/IDeviceFirmwareService.java
@@ -1,14 +1,12 @@
package com.dji.sample.manage.service;
import com.dji.sample.common.model.PaginationData;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.manage.model.dto.DeviceFirmwareDTO;
import com.dji.sample.manage.model.dto.DeviceFirmwareNoteDTO;
import com.dji.sample.manage.model.dto.DeviceFirmwareUpgradeDTO;
import com.dji.sample.manage.model.param.DeviceFirmwareQueryParam;
import com.dji.sample.manage.model.param.DeviceFirmwareUploadParam;
import com.dji.sample.manage.model.param.DeviceOtaCreateParam;
import org.springframework.messaging.MessageHeaders;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@@ -46,13 +44,6 @@
     * @return
     */
    List<DeviceOtaCreateParam> getDeviceOtaFirmware(String workspaceId, List<DeviceFirmwareUpgradeDTO> upgradeDTOS);
    /**
     * Interface to handle device firmware update progress.
     * @param receiver
     * @param headers
     */
    void handleOtaProgress(CommonTopicReceiver receiver, MessageHeaders headers);
    /**
     * Query firmware version information by page.
src/main/java/com/dji/sample/manage/service/IDeviceLogsService.java
@@ -74,8 +74,9 @@
     * Handle logs file upload progress.
     * @param receiver
     * @param headers
     * @return
     */
    void handleFileUploadProgress(CommonTopicReceiver receiver, MessageHeaders headers);
    CommonTopicReceiver handleFileUploadProgress(CommonTopicReceiver receiver, MessageHeaders headers);
    /**
     * Update status, which is updated when the logs upload succeeds or fails.
src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java
@@ -55,15 +55,16 @@
    void updateFirmwareVersion(FirmwareVersionReceiver receiver);
    /**
     * Handle the topic that contains the payloads field in the state, and save the payloads data.
     * @param payloadReceiverList
     * @param timestamp
     */
    void saveDeviceBasicPayload(List<DevicePayloadReceiver> payloadReceiverList, Long timestamp);
    /**
     * Delete payload data based on payload sn.
     * @param payloadSns
     */
    void deletePayloadsByPayloadsSn(Collection<String> payloadSns);
    /**
     * Check if the device has payload control.
     * @param deviceSn
     * @param payloadIndex
     * @return
     */
    Boolean checkAuthorityPayload(String deviceSn, String payloadIndex);
}
src/main/java/com/dji/sample/manage/service/IDeviceRedisService.java
New file
@@ -0,0 +1,95 @@
package com.dji.sample.manage.service;
import com.dji.sample.component.mqtt.model.EventsOutputProgressReceiver;
import com.dji.sample.component.mqtt.model.EventsReceiver;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.receiver.FirmwareProgressExtReceiver;
import java.util.Optional;
import java.util.Set;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/21
 */
public interface IDeviceRedisService {
    /**
     * Determine if the device is online.
     * @param sn
     * @return
     */
    Boolean checkDeviceOnline(String sn);
    /**
     * Query the basic information of the device in redis.
     * @param sn
     * @return
     */
    Optional<DeviceDTO> getDeviceOnline(String sn);
    /**
     * Save the basic information of the device in redis.
     * @param device
     */
    void setDeviceOnline(DeviceDTO device);
    /**
     * Delete the basic device information saved in redis.
     * @param sn
     * @return
     */
    Boolean delDeviceOnline(String sn);
    /**
     * Get the device's osd real-time data.
     * @param sn
     * @param clazz
     * @param <T>
     * @return
     */
    <T> Optional<T> getDeviceOsd(String sn, Class<T> clazz);
    /**
     * Save the firmware update progress of the device in redis.
     * @param sn
     * @param events
     */
    void setFirmwareUpgrading(String sn, EventsReceiver<EventsOutputProgressReceiver<FirmwareProgressExtReceiver>> events);
    /**
     * Query the firmware update progress of the device in redis.
     * @param sn
     * @return
     */
    Optional<EventsReceiver<EventsOutputProgressReceiver<FirmwareProgressExtReceiver>>> getFirmwareUpgradingProgress(String sn);
    /**
     * Delete the firmware update progress of the device in redis.
     * @param sn
     * @return
     */
    Boolean delFirmwareUpgrading(String sn);
    /**
     * Save the hms key of the device in redis.
     * @param sn
     * @param keys
     */
    void addEndHmsKeys(String sn, String... keys);
    /**
     * Query all hms keys of the device in redis.
     * @param sn
     * @return
     */
    Set<String> getAllHmsKeys(String sn);
    /**
     * Delete all hms keys of the device in redis.
     * @param sn
     * @return
     */
    Boolean delHmsKeysBySn(String sn);
}
src/main/java/com/dji/sample/manage/service/IDeviceService.java
@@ -2,19 +2,18 @@
import com.dji.sample.common.model.PaginationData;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.component.mqtt.model.CommonTopicResponse;
import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.dto.DeviceFirmwareUpgradeDTO;
import com.dji.sample.manage.model.dto.TopologyDeviceDTO;
import com.dji.sample.manage.model.enums.DeviceModeCodeEnum;
import com.dji.sample.manage.model.enums.DeviceSetPropertyEnum;
import com.dji.sample.manage.model.enums.DockModeCodeEnum;
import com.dji.sample.manage.model.param.DeviceQueryParam;
import com.dji.sample.manage.model.receiver.FirmwareVersionReceiver;
import com.dji.sample.manage.model.receiver.StatusGatewayReceiver;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import java.util.Collection;
import java.util.List;
@@ -155,24 +154,6 @@
    Boolean bindDevice(DeviceDTO device);
    /**
     * Handle dock binding status requests.
     * Note: If your business does not need to bind the dock to the organization,
     *       you can directly reply to the successful message without implementing business logic.
     * @param receiver
     * @param headers
     */
    void bindStatus(CommonTopicReceiver receiver, MessageHeaders headers);
    /**
     * Handle dock binding requests.
     * Note: If your business does not need to bind the dock to the organization,
     *       you can directly reply to the successful message without implementing business logic.
     * @param receiver
     * @param headers
     */
    void bindDevice(CommonTopicReceiver receiver, MessageHeaders headers);
    /**
     * Get the binding devices list in one workspace.
     * @param workspaceId
     * @param page
@@ -194,12 +175,6 @@
     * @return device
     */
    Optional<DeviceDTO> getDeviceBySn(String sn);
    /**
     * Update the firmware version information of the device or payload.
     * @param receiver
     */
    void updateFirmwareVersion(FirmwareVersionReceiver receiver);
    /**
     * Create job for device firmware updates.
@@ -227,9 +202,31 @@
    void deviceOnePropertySet(String topic, DeviceSetPropertyEnum propertyEnum, Map.Entry<String, Object> value);
    /**
     * Determine if the device is online.
     * @param sn
     * Check the working status of the dock.
     * @param dockSn
     * @return
     */
    Boolean checkDeviceOnline(String sn);
    DockModeCodeEnum getDockMode(String dockSn);
    /**
     * Query the working status of the aircraft.
     * @param deviceSn
     * @return
     */
    DeviceModeCodeEnum getDeviceMode(String deviceSn);
    /**
     * Check if the dock is in drc mode.
     * @param dockSn
     * @return
     */
    Boolean checkDockDrcMode(String dockSn);
    /**
     * Check if the device has flight control.
     * @param gatewaySn
     * @return
     */
    Boolean checkAuthorityFlight(String gatewaySn);
}
src/main/java/com/dji/sample/manage/service/IWorkspaceService.java
@@ -25,8 +25,6 @@
    /**
     * Handle the request for obtaining the organization information corresponding to the device binding.
     * Note: If your business does not need to bind the dock to the organization,
     *       you can directly reply to the successful message without implementing business logic.
     * @param receiver
     */
    void replyOrganizationGet(CommonTopicReceiver receiver, MessageHeaders headers);
src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java
@@ -1,6 +1,5 @@
package com.dji.sample.manage.service.impl;
import com.dji.sample.component.mqtt.model.StateDataEnum;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.manage.model.dto.CapacityCameraDTO;
@@ -36,20 +35,19 @@
    @Override
    public List<CapacityCameraDTO> getCapacityCameraByDeviceSn(String deviceSn) {
        return (List<CapacityCameraDTO>) RedisOpsUtils.hashGet(StateDataEnum.LIVE_CAPACITY.getDesc(), deviceSn);
        return (List<CapacityCameraDTO>) RedisOpsUtils.hashGet(RedisConst.LIVE_CAPACITY, deviceSn);
    }
    @Override
    public Boolean deleteCapacityCameraByDeviceSn(String deviceSn) {
        return RedisOpsUtils.hashDel(StateDataEnum.LIVE_CAPACITY.getDesc(), new String[]{deviceSn});
        return RedisOpsUtils.hashDel(RedisConst.LIVE_CAPACITY, new String[]{deviceSn});
    }
    @Override
    public void saveCapacityCameraReceiverList(List<CapacityCameraReceiver> capacityCameraReceivers, String deviceSn, Long timestamp) {
        List<CapacityCameraDTO> capacity = capacityCameraReceivers.stream()
                .map(this::receiver2Dto).collect(Collectors.toList());
        RedisOpsUtils.hashSet(StateDataEnum.LIVE_CAPACITY.getDesc(), deviceSn, capacity);
        RedisOpsUtils.setWithExpire(StateDataEnum.LIVE_CAPACITY + RedisConst.DELIMITER + deviceSn, timestamp, RedisConst.DEVICE_ALIVE_SECOND);
        RedisOpsUtils.hashSet(RedisConst.LIVE_CAPACITY, deviceSn, capacity);
    }
    @Override
src/main/java/com/dji/sample/manage/service/impl/DeviceFirmwareServiceImpl.java
@@ -13,8 +13,7 @@
import com.dji.sample.component.oss.service.impl.OssServiceContext;
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.CustomWebSocketMessage;
import com.dji.sample.component.websocket.model.BizCodeEnum;
import com.dji.sample.component.websocket.service.IWebSocketManageService;
import com.dji.sample.component.websocket.service.impl.SendMessageServiceImpl;
import com.dji.sample.manage.dao.IDeviceFirmwareMapper;
@@ -24,15 +23,15 @@
import com.dji.sample.manage.model.param.DeviceFirmwareQueryParam;
import com.dji.sample.manage.model.param.DeviceFirmwareUploadParam;
import com.dji.sample.manage.model.param.DeviceOtaCreateParam;
import com.dji.sample.manage.model.receiver.FirmwareProgressExtReceiver;
import com.dji.sample.manage.service.IDeviceFirmwareService;
import com.dji.sample.manage.service.IDeviceService;
import com.dji.sample.manage.service.IDeviceRedisService;
import com.dji.sample.manage.service.IFirmwareModelService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
@@ -77,13 +76,13 @@
    private IWebSocketManageService webSocketManageService;
    @Autowired
    private IDeviceService deviceService;
    @Autowired
    private OssServiceContext ossServiceContext;
    @Autowired
    private IFirmwareModelService firmwareModelService;
    @Autowired
    private IDeviceRedisService deviceRedisService;
    @Override
    public Optional<DeviceFirmwareDTO> getFirmware(String workspaceId, String deviceName, String version) {
@@ -108,7 +107,7 @@
    public List<DeviceOtaCreateParam> getDeviceOtaFirmware(String workspaceId, List<DeviceFirmwareUpgradeDTO> upgradeDTOS) {
        List<DeviceOtaCreateParam> deviceOtaList = new ArrayList<>();
        upgradeDTOS.forEach(upgradeDevice -> {
            boolean exist = deviceService.checkDeviceOnline(upgradeDevice.getSn());
            boolean exist = deviceRedisService.checkDeviceOnline(upgradeDevice.getSn());
            if (!exist) {
                throw new IllegalArgumentException("Device is offline.");
            }
@@ -125,18 +124,15 @@
        return deviceOtaList;
    }
    @Override
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_OTA_PROGRESS, outputChannel = ChannelName.OUTBOUND)
    public void handleOtaProgress(CommonTopicReceiver receiver, MessageHeaders headers) {
        String topic = headers.get(MqttHeaders.RECEIVED_TOPIC).toString();
        String sn  = topic.substring((TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT).length(),
                topic.indexOf(TopicConst.EVENTS_SUF));
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_OTA_PROGRESS, outputChannel = ChannelName.OUTBOUND_EVENTS)
    public CommonTopicReceiver handleOtaProgress(CommonTopicReceiver receiver, MessageHeaders headers) {
        String sn  = receiver.getGateway();
        EventsReceiver<EventsOutputReceiver> eventsReceiver = objectMapper.convertValue(receiver.getData(),
                new TypeReference<EventsReceiver<EventsOutputReceiver>>(){});
        EventsReceiver<EventsOutputProgressReceiver<FirmwareProgressExtReceiver>> eventsReceiver = objectMapper.convertValue(receiver.getData(),
                new TypeReference<EventsReceiver<EventsOutputProgressReceiver<FirmwareProgressExtReceiver>>>(){});
        eventsReceiver.setBid(receiver.getBid());
        EventsOutputReceiver output = eventsReceiver.getOutput();
        EventsOutputProgressReceiver<FirmwareProgressExtReceiver> output = eventsReceiver.getOutput();
        log.info("SN: {}, {} ===> Upgrading progress: {}",
                sn, receiver.getMethod(), output.getProgress().toString());
@@ -144,59 +140,34 @@
            log.error("SN: {}, {} ===> Error code: {}", sn, receiver.getMethod(), eventsReceiver.getResult());
        }
        DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + sn);
        String childDeviceSn = device.getChildDeviceSn();
        boolean upgrade = RedisOpsUtils.getExpire(RedisConst.FIRMWARE_UPGRADING_PREFIX + sn) > 0;
        boolean childUpgrade = RedisOpsUtils.getExpire(RedisConst.FIRMWARE_UPGRADING_PREFIX + childDeviceSn) > 0;
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(sn);
        if (deviceOpt.isEmpty()) {
            return null;
        }
        // Determine whether it is the ending state, delete the update state key in redis after the job ends.
        EventsResultStatusEnum statusEnum = EventsResultStatusEnum.find(output.getStatus());
        Collection<ConcurrentWebSocketSession> sessions = webSocketManageService.getValueWithWorkspaceAndUserType(
                device.getWorkspaceId(), UserTypeEnum.WEB.getVal());
        CustomWebSocketMessage<Object> build = CustomWebSocketMessage.builder()
                .data(eventsReceiver)
                .timestamp(System.currentTimeMillis())
                .bizCode(receiver.getMethod())
                .build();
        if (upgrade) {
            if (statusEnum.getEnd()) {
                // Delete the cache after the update is complete.
                RedisOpsUtils.del(RedisConst.FIRMWARE_UPGRADING_PREFIX + sn);
            } else {
                // Update the update progress of the dock in redis.
                RedisOpsUtils.setWithExpire(
                        RedisConst.FIRMWARE_UPGRADING_PREFIX + sn, output.getProgress().getPercent(),
                        RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND);
            }
            eventsReceiver.setSn(sn);
            webSocketMessageService.sendBatch(sessions, build);
        }
        if (childUpgrade) {
            if (!StringUtils.hasText(eventsReceiver.getSn())) {
                eventsReceiver.setSn(childDeviceSn);
                webSocketMessageService.sendBatch(sessions, build);
            }
            if (statusEnum.getEnd()) {
                RedisOpsUtils.del(RedisConst.FIRMWARE_UPGRADING_PREFIX + childDeviceSn);
            } else {
                // Update the update progress of the drone in redis.
                RedisOpsUtils.setWithExpire(
                        RedisConst.FIRMWARE_UPGRADING_PREFIX + childDeviceSn, output.getProgress().getPercent(),
                        RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND);
            }
        }
        DeviceDTO device = deviceOpt.get();
        handleProgress(device.getWorkspaceId(), sn, eventsReceiver, statusEnum.getEnd());
        handleProgress(device.getWorkspaceId(), device.getChildDeviceSn(), eventsReceiver, statusEnum.getEnd());
        if (receiver.getNeedReply() != null && receiver.getNeedReply() == 1) {
            String replyTopic = headers.get(MqttHeaders.RECEIVED_TOPIC) + TopicConst._REPLY_SUF;
            messageSenderService.publish(replyTopic,
                    CommonTopicResponse.builder()
                            .tid(receiver.getTid())
                            .bid(receiver.getBid())
                            .method(receiver.getMethod())
                            .timestamp(System.currentTimeMillis())
                            .data(RequestsReply.success())
                            .build());
        return receiver;
    }
    private void handleProgress(String workspaceId, String sn,
                    EventsReceiver<EventsOutputProgressReceiver<FirmwareProgressExtReceiver>> events, boolean isEnd) {
        boolean upgrade = deviceRedisService.getFirmwareUpgradingProgress(sn).isPresent();
        if (!upgrade) {
            return;
        }
        if (isEnd) {
            // Delete the cache after the update is complete.
            deviceRedisService.delFirmwareUpgrading(sn);
        } else {
            // Update the update progress of the dock in redis.
            deviceRedisService.setFirmwareUpgrading(sn, events);
        }
        events.setSn(sn);
        webSocketMessageService.sendBatch(workspaceId, UserTypeEnum.WEB.getVal(), BizCodeEnum.OTA_PROGRESS.getCode(), events);
    }
    @Override
src/main/java/com/dji/sample/manage/service/impl/DeviceHmsServiceImpl.java
@@ -9,11 +9,7 @@
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.component.mqtt.model.MapKeyConst;
import com.dji.sample.component.mqtt.model.TopicConst;
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.service.impl.SendMessageServiceImpl;
import com.dji.sample.component.websocket.service.impl.WebSocketManageServiceImpl;
import com.dji.sample.manage.dao.IDeviceHmsMapper;
@@ -23,14 +19,17 @@
import com.dji.sample.manage.model.dto.DeviceHmsDTO;
import com.dji.sample.manage.model.dto.TelemetryDTO;
import com.dji.sample.manage.model.entity.DeviceHmsEntity;
import com.dji.sample.manage.model.enums.DeviceDomainEnum;
import com.dji.sample.manage.model.enums.HmsEnum;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import com.dji.sample.manage.model.param.DeviceHmsQueryParam;
import com.dji.sample.manage.model.receiver.DeviceHmsReceiver;
import com.dji.sample.manage.model.receiver.HmsArgsReceiver;
import com.dji.sample.manage.service.IDeviceHmsService;
import com.dji.sample.manage.service.IDeviceRedisService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.mqtt.support.MqttHeaders;
@@ -54,6 +53,7 @@
 */
@Service
@Transactional
@Slf4j
public class DeviceHmsServiceImpl implements IDeviceHmsService {
    @Autowired
@@ -67,6 +67,9 @@
    @Autowired
    private WebSocketManageServiceImpl webSocketManageService;
    @Autowired
    private IDeviceRedisService deviceRedisService;
    private static final Pattern PATTERN_KEY = Pattern.compile(
            HmsEnum.FormatKeyEnum.KEY_START +
@@ -90,11 +93,8 @@
                .updateTime(0L)
                .sn(sn)
                .build();
        String key = RedisConst.HMS_PREFIX + sn;
        // Query all unread hms messages of the device in redis.
        Set<String> hmsMap = RedisOpsUtils.listGetAll(key).stream().map(String::valueOf).collect(Collectors.toSet());
        DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + sn);
        Set<String> hmsMap = deviceRedisService.getAllHmsKeys(sn);
        List<DeviceHmsDTO> unReadList = new ArrayList<>();
        objectMapper.convertValue(((Map) (receiver.getData())).get(MapKeyConst.LIST),
@@ -114,15 +114,14 @@
        if (unReadList.isEmpty()) {
            return;
        }
        RedisOpsUtils.listRPush(key, unReadList.stream().map(DeviceHmsDTO::getKey).toArray(String[]::new));
        deviceRedisService.addEndHmsKeys(sn, unReadList.stream().map(DeviceHmsDTO::getKey).toArray(String[]::new));
        // push to the web
        Collection<ConcurrentWebSocketSession> sessions = webSocketManageService.getValueWithWorkspaceAndUserType(
                device.getWorkspaceId(), UserTypeEnum.WEB.getVal());
        sendMessageService.sendBatch(sessions, CustomWebSocketMessage.builder()
                .bizCode(BizCodeEnum.DEVICE_HMS.getCode())
                .data(TelemetryDTO.<List<DeviceHmsDTO>>builder().sn(sn).host(unReadList).build())
                .timestamp(System.currentTimeMillis())
                .build());
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(sn);
        if (deviceOpt.isEmpty()) {
            return;
        }
        sendMessageService.sendBatch(deviceOpt.get().getWorkspaceId(), UserTypeEnum.WEB.getVal(),
                BizCodeEnum.DEVICE_HMS.getCode(), TelemetryDTO.<List<DeviceHmsDTO>>builder().sn(sn).host(unReadList).build());
    }
    @Override
@@ -159,7 +158,7 @@
                        .eq(DeviceHmsEntity::getSn, deviceSn)
                        .eq(DeviceHmsEntity::getUpdateTime, 0L));
        // Delete unread messages cached in redis.
        RedisOpsUtils.del(RedisConst.HMS_PREFIX + deviceSn);
        deviceRedisService.delHmsKeysBySn(deviceSn);
    }
    private DeviceHmsDTO entity2Dto(DeviceHmsEntity entity) {
@@ -191,8 +190,12 @@
        dto.setLevel(receiver.getLevel());
        dto.setModule(receiver.getModule());
        dto.setHmsId(UUID.randomUUID().toString());
        if (HmsEnum.DomainType.DRONE_NEST.getDomain().equals(receiver.getDomainType())) {
        Optional<DeviceDomainEnum> domainEnumOpt = Optional.ofNullable(receiver.getDeviceType())
                .map(type -> type.split("-")).map(type -> type[0]).map(Integer::parseInt).map(DeviceDomainEnum::find);
        if (domainEnumOpt.isEmpty()) {
            throw new RuntimeException("The device type does not match, please check the data.");
        }
        if (DeviceDomainEnum.DOCK == domainEnumOpt.get()) {
            dto.setHmsKey(HmsEnum.HmsFaqIdEnum.DOCK_TIP.getText() + receiver.getCode());
            return;
        }
src/main/java/com/dji/sample/manage/service/impl/DeviceLogsServiceImpl.java
@@ -10,9 +10,8 @@
import com.dji.sample.component.mqtt.service.IMessageSenderService;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
import com.dji.sample.component.websocket.model.BizCodeEnum;
import com.dji.sample.component.websocket.service.ISendMessageService;
import com.dji.sample.component.websocket.service.IWebSocketManageService;
import com.dji.sample.manage.dao.IDeviceLogsMapper;
import com.dji.sample.manage.model.dto.*;
import com.dji.sample.manage.model.entity.DeviceLogsEntity;
@@ -22,6 +21,7 @@
import com.dji.sample.manage.model.param.LogsFileUpdateParam;
import com.dji.sample.manage.model.receiver.*;
import com.dji.sample.manage.service.IDeviceLogsService;
import com.dji.sample.manage.service.IDeviceRedisService;
import com.dji.sample.manage.service.ILogsFileService;
import com.dji.sample.manage.service.ITopologyService;
import com.dji.sample.media.model.StsCredentialsDTO;
@@ -70,9 +70,6 @@
    private ILogsFileService logsFileService;
    @Autowired
    private RedisOpsUtils redisOpsUtils;
    @Autowired
    private IStorageService storageService;
    @Autowired
@@ -82,7 +79,7 @@
    private ISendMessageService webSocketMessageService;
    @Autowired
    private IWebSocketManageService webSocketManageService;
    private IDeviceRedisService deviceRedisService;
    @Override
    public PaginationData<DeviceLogsDTO> getUploadedLogs(String deviceSn, DeviceLogsQueryParam param) {
@@ -104,29 +101,21 @@
    @Override
    public ResponseResult getRealTimeLogs(String deviceSn, List<String> domainList) {
        boolean exist = redisOpsUtils.getExpire(RedisConst.DEVICE_ONLINE_PREFIX + deviceSn) > 0;
        boolean exist = deviceRedisService.checkDeviceOnline(deviceSn);
        if (!exist) {
            return ResponseResult.error("Device is offline.");
        }
        String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + deviceSn + TopicConst.SERVICES_SUF;
        LogsFileUploadList data = messageSenderService.publishWithReply(
                LogsFileUploadList.class,
                topic,
                CommonTopicResponse.builder()
                        .tid(UUID.randomUUID().toString())
                        .bid(UUID.randomUUID().toString())
                        .method(LogsFileMethodEnum.FILE_UPLOAD_LIST.getMethod())
                        .timestamp(System.currentTimeMillis())
                        .data(Map.of(MapKeyConst.MODULE_LIST, domainList))
                        .build(), 1);
        ServiceReply<List<LogsFileUpload>> data = messageSenderService.publishServicesTopic(
                new TypeReference<List<LogsFileUpload>>() {}, deviceSn, LogsFileMethodEnum.FILE_UPLOAD_LIST.getMethod(),
                Map.of(MapKeyConst.MODULE_LIST, domainList));
        for (LogsFileUpload file : data.getFiles()) {
        for (LogsFileUpload file : data.getOutput()) {
            if (file.getDeviceSn().isBlank()) {
                file.setDeviceSn(deviceSn);
            }
        }
        return ResponseResult.success(data);
        return ResponseResult.success(new LogsFileUploadList(data.getOutput(), data.getResult()));
    }
    @Override
@@ -164,15 +153,8 @@
        credentialsDTO.setParams(LogsFileUploadList.builder().files(files).build());
        String bid = UUID.randomUUID().toString();
        ServiceReply reply = messageSenderService.publishWithReply(
                TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + deviceSn + TopicConst.SERVICES_SUF,
                CommonTopicResponse.<LogsUploadCredentialsDTO>builder()
                        .tid(UUID.randomUUID().toString())
                        .bid(bid)
                        .timestamp(System.currentTimeMillis())
                        .method(LogsFileMethodEnum.FILE_UPLOAD_START.getMethod())
                        .data(credentialsDTO)
                        .build());
        ServiceReply reply = messageSenderService.publishServicesTopic(
                deviceSn, LogsFileMethodEnum.FILE_UPLOAD_START.getMethod(), credentialsDTO, bid);
        if (ResponseResult.CODE_SUCCESS != reply.getResult()) {
            return ResponseResult.error(String.valueOf(reply.getResult()));
@@ -184,7 +166,7 @@
        }
        // Save the status of the log upload.
        redisOpsUtils.hashSet(RedisConst.LOGS_FILE_PREFIX + deviceSn, bid, LogsOutputProgressDTO.builder().logsId(logsId).build());
        RedisOpsUtils.hashSet(RedisConst.LOGS_FILE_PREFIX + deviceSn, bid, LogsOutputProgressDTO.builder().logsId(logsId).build());
        return ResponseResult.success();
    }
@@ -195,16 +177,8 @@
        if (LogsFileUpdateMethodEnum.UNKNOWN == method) {
            return ResponseResult.error("Illegal param");
        }
        String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + deviceSn + TopicConst.SERVICES_SUF;
        String bid = UUID.randomUUID().toString();
        ServiceReply reply = messageSenderService.publishWithReply(topic,
                CommonTopicResponse.<LogsFileUpdateParam>builder()
                        .tid(UUID.randomUUID().toString())
                        .bid(bid)
                        .timestamp(System.currentTimeMillis())
                        .method(LogsFileMethodEnum.FILE_UPLOAD_UPDATE.getMethod())
                        .data(param)
                        .build());
        ServiceReply reply = messageSenderService.publishServicesTopic(
                deviceSn, LogsFileMethodEnum.FILE_UPLOAD_UPDATE.getMethod(), param);
        if (ResponseResult.CODE_SUCCESS != reply.getResult()) {
            return ResponseResult.error("Error Code : " + reply.getResult());
@@ -220,24 +194,12 @@
        logsFileService.deleteFileByLogsId(logsId);
    }
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FILE_UPLOAD_PROGRESS, outputChannel = ChannelName.OUTBOUND)
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FILE_UPLOAD_PROGRESS, outputChannel = ChannelName.OUTBOUND_EVENTS)
    @Override
    public void handleFileUploadProgress(CommonTopicReceiver receiver, MessageHeaders headers) {
    public CommonTopicReceiver handleFileUploadProgress(CommonTopicReceiver receiver, MessageHeaders headers) {
        String topic = headers.get(MqttHeaders.RECEIVED_TOPIC).toString();
        String sn  = topic.substring((TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT).length(),
                topic.indexOf(TopicConst.EVENTS_SUF));
        if (receiver.getNeedReply() != null && receiver.getNeedReply() == 1) {
            String replyTopic = headers.get(MqttHeaders.RECEIVED_TOPIC) + TopicConst._REPLY_SUF;
            messageSenderService.publish(replyTopic,
                    CommonTopicResponse.builder()
                            .tid(receiver.getTid())
                            .bid(receiver.getBid())
                            .method(receiver.getMethod())
                            .timestamp(System.currentTimeMillis())
                            .data(RequestsReply.success())
                            .build());
        }
        EventsReceiver<OutputLogsProgressReceiver> eventsReceiver = objectMapper.convertValue(receiver.getData(),
                new TypeReference<EventsReceiver<OutputLogsProgressReceiver>>(){});
@@ -246,7 +208,12 @@
        webSocketData.setBid(receiver.getBid());
        webSocketData.setSn(sn);
        DeviceDTO device = (DeviceDTO) redisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + sn);
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(sn);
        if (deviceOpt.isEmpty()) {
            return null;
        }
        DeviceDTO device = deviceOpt.get();
        try {
            OutputLogsProgressReceiver output = eventsReceiver.getOutput();
@@ -255,12 +222,12 @@
            String key = RedisConst.LOGS_FILE_PREFIX + sn;
            LogsOutputProgressDTO progress;
            boolean exist = redisOpsUtils.checkExist(key);
            boolean exist = RedisOpsUtils.checkExist(key);
            if (!exist && !statusEnum.getEnd()) {
                progress = LogsOutputProgressDTO.builder().logsId(receiver.getBid()).build();
                redisOpsUtils.hashSet(key, receiver.getBid(), progress);
                RedisOpsUtils.hashSet(key, receiver.getBid(), progress);
            } else if (exist) {
                progress = (LogsOutputProgressDTO) redisOpsUtils.hashGet(key, receiver.getBid());
                progress = (LogsOutputProgressDTO) RedisOpsUtils.hashGet(key, receiver.getBid());
            } else {
                progress = LogsOutputProgressDTO.builder().build();
            }
@@ -269,7 +236,7 @@
            // If the logs file is empty, delete the cache of this task.
            List<LogsExtFileReceiver> fileReceivers = output.getExt().getFiles();
            if (CollectionUtils.isEmpty(fileReceivers)) {
                redisOpsUtils.del(RedisConst.LOGS_FILE_PREFIX + sn);
                RedisOpsUtils.del(RedisConst.LOGS_FILE_PREFIX + sn);
            }
            // refresh cache.
@@ -295,10 +262,10 @@
            });
            progress.setFiles(fileProgressList);
            webSocketData.setOutput(progress);
            redisOpsUtils.hashSet(RedisConst.LOGS_FILE_PREFIX + sn, receiver.getBid(), progress);
            RedisOpsUtils.hashSet(RedisConst.LOGS_FILE_PREFIX + sn, receiver.getBid(), progress);
            // Delete the cache at the end of the task.
            if (statusEnum.getEnd()) {
                redisOpsUtils.del(RedisConst.LOGS_FILE_PREFIX + sn);
                RedisOpsUtils.del(RedisConst.LOGS_FILE_PREFIX + sn);
                this.updateLogsStatus(receiver.getBid(), DeviceLogsStatusEnum.find(statusEnum).getVal());
                fileReceivers.forEach(file -> logsFileService.updateFile(receiver.getBid(), file));
@@ -306,18 +273,13 @@
        } catch (NullPointerException e) {
            this.updateLogsStatus(receiver.getBid(), DeviceLogsStatusEnum.FAILED.getVal());
            redisOpsUtils.del(RedisConst.LOGS_FILE_PREFIX + sn);
            RedisOpsUtils.del(RedisConst.LOGS_FILE_PREFIX + sn);
        }
        webSocketMessageService.sendBatch(
                webSocketManageService.getValueWithWorkspaceAndUserType(
                        device.getWorkspaceId(), UserTypeEnum.WEB.getVal()),
                CustomWebSocketMessage.builder()
                        .data(webSocketData)
                        .timestamp(System.currentTimeMillis())
                        .bizCode(receiver.getMethod())
                        .build());
        webSocketMessageService.sendBatch(device.getWorkspaceId(), UserTypeEnum.WEB.getVal(),
                BizCodeEnum.FILE_UPLOAD_PROGRESS.getCode(), webSocketData);
        return receiver;
    }
    @Override
@@ -340,9 +302,9 @@
            return null;
        }
        String key = RedisConst.LOGS_FILE_PREFIX + entity.getDeviceSn();
        LogsOutputProgressDTO progress = new LogsOutputProgressDTO();
        if (redisOpsUtils.hashCheck(key, entity.getLogsId())) {
            progress = (LogsOutputProgressDTO) redisOpsUtils.hashGet(key, entity.getLogsId());
        LogsOutputProgressDTO progress = null;
        if (RedisOpsUtils.hashCheck(key, entity.getLogsId())) {
            progress = (LogsOutputProgressDTO) RedisOpsUtils.hashGet(key, entity.getLogsId());
        }
        return DeviceLogsDTO.builder()
@@ -354,7 +316,7 @@
                .logsInformation(entity.getLogsInfo())
                .userName(entity.getUsername())
                .deviceLogs(LogsFileUploadList.builder().files(logsFileService.getLogsFileByLogsId(entity.getLogsId())).build())
                .logsProgress(progress.getFiles())
                .logsProgress(Objects.requireNonNullElse(progress, new LogsOutputProgressDTO()).getFiles())
                .deviceTopo(topologyService.getDeviceTopologyByGatewaySn(entity.getDeviceSn()).orElse(null))
                .build();
    }
src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java
@@ -68,7 +68,7 @@
                Map<String, Object> receiverData = (Map<String, Object>) receiver.getData();
                data.setPayloads(payloadsList.stream()
                        .map(payload -> mapper.convertValue(
                                receiverData.getOrDefault(payload.getPayloadName(), Map.of()),
                                receiverData.getOrDefault(payload.getPayloadIndex(), Map.of()),
                                OsdPayloadReceiver.class))
                        .collect(Collectors.toList()));
src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java
@@ -2,21 +2,31 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.dji.sample.component.mqtt.model.ChannelName;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.component.websocket.model.BizCodeEnum;
import com.dji.sample.component.websocket.service.ISendMessageService;
import com.dji.sample.control.model.enums.DroneAuthorityEnum;
import com.dji.sample.manage.dao.IDevicePayloadMapper;
import com.dji.sample.manage.model.dto.DeviceAuthorityDTO;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.dto.DeviceDictionaryDTO;
import com.dji.sample.manage.model.dto.DevicePayloadDTO;
import com.dji.sample.manage.model.entity.DevicePayloadEntity;
import com.dji.sample.manage.model.enums.ControlSourceEnum;
import com.dji.sample.manage.model.enums.DeviceDomainEnum;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import com.dji.sample.manage.model.receiver.DevicePayloadReceiver;
import com.dji.sample.manage.model.receiver.FirmwareVersionReceiver;
import com.dji.sample.manage.service.ICapacityCameraService;
import com.dji.sample.manage.service.IDeviceDictionaryService;
import com.dji.sample.manage.service.IDevicePayloadService;
import com.dji.sample.manage.service.IDeviceRedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@@ -43,6 +53,12 @@
    @Autowired
    private ICapacityCameraService capacityCameraService;
    @Autowired
    private ISendMessageService sendMessageService;
    @Autowired
    private IDeviceRedisService deviceRedisService;
    @Override
    public Integer checkPayloadExist(String payloadSn) {
        DevicePayloadEntity devicePayload = mapper.selectOne(
@@ -56,7 +72,9 @@
        // If it already exists, update the data directly.
        if (id > 0) {
            entity.setId(id);
            return mapper.updateById(entity);
            // For the payload of the drone itself, there is no firmware version.
            entity.setFirmwareVersion(null);
            return mapper.updateById(entity) > 0 ? entity.getId() : 0;
        }
        return mapper.insert(entity) > 0 ? entity.getId() : 0;
    }
@@ -68,9 +86,17 @@
        }
        String deviceSn = payloadReceiverList.get(0).getDeviceSn();
        String key = RedisConst.DEVICE_ONLINE_PREFIX + deviceSn;
        DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(key);
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(deviceSn);
        if (deviceOpt.isEmpty()) {
            return false;
        }
        DeviceDTO device = deviceOpt.get();
        List<DevicePayloadDTO> payloads = new ArrayList<>();
        Map<String, String> controlMap = CollectionUtils.isEmpty(device.getPayloadsList()) ?
                Collections.emptyMap() :
                device.getPayloadsList().stream()
                        .collect(Collectors.toMap(DevicePayloadDTO::getPayloadIndex, DevicePayloadDTO::getControlSource));
        for (DevicePayloadReceiver payloadReceiver : payloadReceiverList) {
            int payloadId = this.saveOnePayloadDTO(payloadReceiver);
@@ -78,13 +104,23 @@
                return false;
            }
            payloads.add(this.receiver2Dto(payloadReceiver));
            if (!controlMap.getOrDefault(payloadReceiver.getPayloadIndex(), "").equals(payloadReceiver.getControlSource())) {
                sendMessageService.sendBatch(device.getWorkspaceId(), UserTypeEnum.WEB.getVal(),
                                    BizCodeEnum.CONTROL_SOURCE_CHANGE.getCode(),
                                    DeviceAuthorityDTO.builder()
                                            .controlSource(payloadReceiver.getControlSource())
                                            .sn(payloadReceiver.getSn())
                                            .type(DroneAuthorityEnum.PAYLOAD)
                                            .build());
            }
        }
        if (payloads.isEmpty()) {
            payloads = this.getDevicePayloadEntitiesByDeviceSn(deviceSn);
        }
        device.setPayloadsList(payloads);
        RedisOpsUtils.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn(), device, RedisConst.DEVICE_ALIVE_SECOND);
        deviceRedisService.setDeviceOnline(device);
        return true;
    }
@@ -122,33 +158,35 @@
                        .eq(DevicePayloadEntity::getDeviceSn, receiver.getSn()));
    }
    @Override
    public void saveDeviceBasicPayload(List<DevicePayloadReceiver> payloadReceiverList, Long timestamp) {
    /**
     * Handle payload data for devices.
     * @param payloadReceiverList
     * @param headers
     */
    @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_PAYLOAD)
    public void handleDeviceBasicPayload(List<DevicePayloadReceiver> payloadReceiverList, MessageHeaders headers) {
        if (payloadReceiverList.isEmpty()) {
            return;
        }
        String deviceSn = payloadReceiverList.stream().findAny().get().getDeviceSn();
        String deviceSn = payloadReceiverList.get(0).getDeviceSn();
        String key = RedisConst.STATE_PAYLOAD_PREFIX + deviceSn;
        // Solve timing problems
        long last = (long) Objects.requireNonNullElse(RedisOpsUtils.get(key), 0L);
        long timestamp = headers.getTimestamp();
        if (last > timestamp) {
            return;
        }
        // Filter unsaved payload information.
        Set<String> payloadSns = this.getDevicePayloadEntitiesByDeviceSn(payloadReceiverList.get(0).getDeviceSn())
                .stream().map(DevicePayloadDTO::getPayloadSn).collect(Collectors.toSet());
        Set<String> newPayloadSns = payloadReceiverList.stream().map(DevicePayloadReceiver::getSn).collect(Collectors.toSet());
        Set<String> needToDel = payloadSns.stream().filter(sn -> !newPayloadSns.contains(sn)).collect(Collectors.toSet());
        this.deletePayloadsByPayloadsSn(needToDel);
        List<DevicePayloadReceiver> needToSave = payloadReceiverList.stream()
                .filter(payload -> !payloadSns.contains(payload.getSn())).collect(Collectors.toList());
        payloadSns.removeAll(newPayloadSns);
        this.deletePayloadsByPayloadsSn(payloadSns);
        // Save the new payload information.
        boolean isSave = this.savePayloadDTOs(needToSave);
        boolean isSave = this.savePayloadDTOs(payloadReceiverList);
        if (isSave) {
            RedisOpsUtils.setWithExpire(key, timestamp, RedisConst.DEVICE_ALIVE_SECOND);
        }
@@ -164,6 +202,19 @@
                .or(wrapper -> payloadSns.forEach(sn -> wrapper.eq(DevicePayloadEntity::getPayloadSn, sn))));
    }
    @Override
    public Boolean checkAuthorityPayload(String deviceSn, String payloadIndex) {
        return deviceRedisService.getDeviceOnline(deviceSn).flatMap(device ->
                Optional.of(DeviceDomainEnum.SUB_DEVICE.getVal() == device.getDomain()
                        && !CollectionUtils.isEmpty(device.getPayloadsList())
                        && ControlSourceEnum.A.getControlSource()
                        .equals(device.getPayloadsList().stream()
                                .filter(payload -> payloadIndex.equals(payload.getPayloadIndex()))
                                .map(DevicePayloadDTO::getControlSource).findAny()
                                .orElse(ControlSourceEnum.B.getControlSource())))).orElse(true);
    }
    /**
     * Convert database entity objects into payload data transfer object.
     * @param entity
@@ -175,7 +226,9 @@
            builder.payloadSn(entity.getPayloadSn())
                    .payloadName(entity.getPayloadName())
                    .payloadDesc(entity.getPayloadDesc())
                    .payloadIndex(entity.getPayloadIndex());
                    .index(entity.getPayloadIndex())
                    .payloadIndex(entity.getPayloadType() + "-" + entity.getSubType() + "-" + entity.getPayloadIndex())
                    .controlSource(entity.getControlSource());
        }
        return builder.build();
    }
@@ -196,25 +249,23 @@
        String[] payloadIndexArr = dto.getPayloadIndex().split("-");
        try {
            int[] arr = Arrays.stream(payloadIndexArr)
                    .map(Integer::valueOf)
                    .mapToInt(Integer::intValue)
                    .mapToInt(Integer::parseInt)
                    .toArray();
            if (arr.length == 3) {
                Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService
                        .getOneDictionaryInfoByTypeSubType(DeviceDomainEnum.PAYLOAD.getVal(), arr[0], arr[1]);
                dictionaryOpt.ifPresent(dictionary ->
                        builder.payloadName(dictionary.getDeviceName())
                                .payloadDesc(dictionary.getDeviceDesc()));
            Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService
                    .getOneDictionaryInfoByTypeSubType(DeviceDomainEnum.PAYLOAD.getVal(), arr[0], arr[1]);
            dictionaryOpt.ifPresent(dictionary ->
                    builder.payloadName(dictionary.getDeviceName())
                            .payloadDesc(dictionary.getDeviceDesc()));
            }
            builder.payloadType(arr[0])
                    .subType(arr[1])
                    .payloadIndex(arr[2]);
                    .payloadIndex(arr[2])
                    .controlSource(dto.getControlSource());
        } catch (NumberFormatException e) {
            builder.payloadType(Integer.valueOf(payloadIndexArr[0]))
            builder.payloadType(-1)
                    .subType(-1)
                    .payloadIndex(Integer.valueOf(payloadIndexArr[2]));
                    .payloadIndex(-1);
        }
        return builder
@@ -229,7 +280,8 @@
            return builder.build();
        }
        return builder.payloadSn(receiver.getSn())
                .payloadName(receiver.getPayloadIndex())
                .payloadIndex(receiver.getPayloadIndex())
                .controlSource(receiver.getControlSource())
                .build();
    }
src/main/java/com/dji/sample/manage/service/impl/DeviceRedisServiceImpl.java
New file
@@ -0,0 +1,80 @@
package com.dji.sample.manage.service.impl;
import com.dji.sample.component.mqtt.model.EventsOutputProgressReceiver;
import com.dji.sample.component.mqtt.model.EventsReceiver;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.receiver.FirmwareProgressExtReceiver;
import com.dji.sample.manage.service.IDeviceRedisService;
import org.springframework.stereotype.Service;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/21
 */
@Service
public class DeviceRedisServiceImpl implements IDeviceRedisService {
    @Override
    public Boolean checkDeviceOnline(String sn) {
        String key = RedisConst.DEVICE_ONLINE_PREFIX + sn;
        return RedisOpsUtils.checkExist(key) && RedisOpsUtils.getExpire(key) > 0;
    }
    @Override
    public Optional<DeviceDTO> getDeviceOnline(String sn) {
        return Optional.ofNullable((DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + sn));
    }
    @Override
    public void setDeviceOnline(DeviceDTO device) {
        RedisOpsUtils.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn(), device, RedisConst.DEVICE_ALIVE_SECOND);
    }
    @Override
    public Boolean delDeviceOnline(String sn) {
        return RedisOpsUtils.del(RedisConst.DEVICE_ONLINE_PREFIX + sn);
    }
    @Override
    public <T> Optional<T> getDeviceOsd(String sn, Class<T> clazz) {
        return Optional.ofNullable(clazz.cast(RedisOpsUtils.get(RedisConst.OSD_PREFIX + sn)));
    }
    @Override
    public void setFirmwareUpgrading(String sn, EventsReceiver<EventsOutputProgressReceiver<FirmwareProgressExtReceiver>> events) {
        RedisOpsUtils.setWithExpire(RedisConst.FIRMWARE_UPGRADING_PREFIX + sn, events, RedisConst.DEVICE_ALIVE_SECOND * 20);
    }
    @Override
    public Optional<EventsReceiver<EventsOutputProgressReceiver<FirmwareProgressExtReceiver>>> getFirmwareUpgradingProgress(String sn) {
        return Optional.ofNullable((EventsReceiver<EventsOutputProgressReceiver<FirmwareProgressExtReceiver>>) RedisOpsUtils.get(RedisConst.FIRMWARE_UPGRADING_PREFIX + sn));
    }
    @Override
    public Boolean delFirmwareUpgrading(String sn) {
        return RedisOpsUtils.del(RedisConst.FIRMWARE_UPGRADING_PREFIX + sn);
    }
    @Override
    public void addEndHmsKeys(String sn, String... keys) {
        RedisOpsUtils.listRPush(RedisConst.HMS_PREFIX + sn, keys);
    }
    @Override
    public Set<String> getAllHmsKeys(String sn) {
        return RedisOpsUtils.listGetAll(RedisConst.HMS_PREFIX + sn).stream()
                .map(String::valueOf).collect(Collectors.toSet());
    }
    @Override
    public Boolean delHmsKeysBySn(String sn) {
        return RedisOpsUtils.del(RedisConst.HMS_PREFIX + sn);
    }
}
src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java
@@ -17,6 +17,7 @@
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
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;
@@ -92,6 +93,12 @@
    private IDeviceFirmwareService deviceFirmwareService;
    @Autowired
    private ICapacityCameraService capacityCameraService;
    @Autowired
    private IDeviceRedisService deviceRedisService;
    @Autowired
    @Qualifier("gatewayOSDServiceImpl")
    private ITSAService tsaService;
@@ -104,24 +111,23 @@
        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 = RedisOpsUtils.checkExist(key);
        if (!exist) {
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(gatewaySn);
        if (deviceOpt.isEmpty()) {
            Optional<DeviceDTO> gatewayOpt = this.getDeviceBySn(gatewaySn);
            if (gatewayOpt.isPresent()) {
                DeviceDTO value = gatewayOpt.get();
                RedisOpsUtils.setWithExpire(key, value, RedisConst.DEVICE_ALIVE_SECOND);
                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).isPresent();
            return onlineSaveDevice(gatewayDevice, null, null).isPresent();
        }
        DeviceDTO deviceDTO = (DeviceDTO) (RedisOpsUtils.get(key));
        DeviceDTO deviceDTO = deviceOpt.get();
        String deviceSn = deviceDTO.getChildDeviceSn();
        if (!StringUtils.hasText(deviceSn)) {
            return true;
@@ -134,22 +140,22 @@
    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) {
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(deviceSn);
        if (deviceOpt.isEmpty()) {
            log.debug("The drone is already offline.");
            return true;
        }
        DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(key);
        DeviceDTO device = deviceOpt.get();
        // Cancel drone-related subscriptions.
        this.unsubscribeTopicOffline(deviceSn);
        payloadService.deletePayloadsByDeviceSn(new ArrayList<>(List.of(deviceSn)));
        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);
        RedisOpsUtils.del(key);
        RedisOpsUtils.del(RedisConst.OSD_PREFIX + device.getDeviceSn());
        RedisOpsUtils.del(RedisConst.HMS_PREFIX + device.getDeviceSn());
        log.debug("{} offline.", deviceSn);
        return true;
    }
@@ -157,13 +163,11 @@
    @Override
    public Boolean deviceOnline(StatusGatewayReceiver deviceGateway) {
        String deviceSn = deviceGateway.getSubDevices().get(0).getSn();
        String key = RedisConst.DEVICE_ONLINE_PREFIX + deviceSn;
        // change log:  Use redis instead of
        long time = RedisOpsUtils.getExpire(key);
        long gatewayTime = RedisOpsUtils.getExpire(RedisConst.DEVICE_ONLINE_PREFIX + deviceGateway.getSn());
        if (time > 0 && gatewayTime > 0) {
            RedisOpsUtils.expireKey(key, RedisConst.DEVICE_ALIVE_SECOND);
        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())
@@ -171,7 +175,7 @@
                    .childDeviceSn(deviceSn).build();
            this.updateDevice(gateway);
            this.updateDevice(device);
            String workspaceId = ((DeviceDTO)(RedisOpsUtils.get(key))).getWorkspaceId();
            String workspaceId = deviceOpt.get().getWorkspaceId();
            if (StringUtils.hasText(workspaceId)) {
                this.subscribeTopicOnline(deviceSn);
                this.subscribeTopicOnline(deviceGateway.getSn());
@@ -193,14 +197,14 @@
                });
        DeviceEntity gateway = deviceGatewayConvertToDeviceEntity(deviceGateway);
        Optional<DeviceEntity> gatewayEntityOpt = onlineSaveDevice(gateway, deviceSn);
        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));
        Optional<DeviceEntity> subDeviceEntityOpt = onlineSaveDevice(subDevice, null);
        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;
@@ -308,7 +312,7 @@
        devicesList.stream()
                .filter(gateway -> DeviceDomainEnum.DOCK.getVal() == gateway.getDomain() ||
                        RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + gateway.getDeviceSn()))
                        deviceRedisService.checkDeviceOnline(gateway.getDeviceSn()))
                .forEach(this::spliceDeviceTopo);
        return devicesList;
@@ -317,7 +321,7 @@
    @Override
    public void spliceDeviceTopo(DeviceDTO gateway) {
        gateway.setStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + gateway.getDeviceSn()));
        gateway.setStatus(deviceRedisService.checkDeviceOnline(gateway.getDeviceSn()));
        // sub device
        if (!StringUtils.hasText(gateway.getChildDeviceSn())) {
@@ -325,7 +329,7 @@
        }
        DeviceDTO subDevice = getDevicesByParams(DeviceQueryParam.builder().deviceSn(gateway.getChildDeviceSn()).build()).get(0);
        subDevice.setStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + subDevice.getDeviceSn()));
        subDevice.setStatus(deviceRedisService.checkDeviceOnline(subDevice.getDeviceSn()));
        gateway.setChildren(subDevice);
        // payloads
@@ -362,8 +366,7 @@
        this.getDeviceTopoForPilot(sn)
                .ifPresent(pilotMessage::setData);
        boolean exist = RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + sn);
        pilotMessage.getData().setOnlineStatus(exist);
        pilotMessage.getData().setOnlineStatus(deviceRedisService.checkDeviceOnline(sn));
        pilotMessage.getData().setGatewaySn(gatewaySn.equals(sn) ? "" : gatewaySn);
        sendMessageService.sendBatch(sessions, pilotMessage);
@@ -383,7 +386,7 @@
                            .key(device.getDomain() + "-" + device.getType() + "-" + device.getSubType())
                            .build())
                    .iconUrls(device.getIconUrl())
                    .onlineStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn()))
                    .onlineStatus(deviceRedisService.checkDeviceOnline(device.getDeviceSn()))
                    .boundStatus(device.getBoundStatus())
                    .model(device.getDeviceName())
                    .userId(device.getUserId())
@@ -427,25 +430,24 @@
            String from = topic.substring((THING_MODEL_PRE + PRODUCT).length(),
                    topic.indexOf(OSD_SUF));
            // Real-time update of device status in memory
            RedisOpsUtils.expireKey(RedisConst.DEVICE_ONLINE_PREFIX + from, RedisConst.DEVICE_ALIVE_SECOND);
            Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(from);
            DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + from);
            if (device == null) {
                Optional<DeviceDTO> deviceOpt = this.getDeviceBySn(from);
            if (deviceOpt.isEmpty()) {
                deviceOpt = this.getDeviceBySn(from);
                if (deviceOpt.isEmpty()) {
                    log.error("Please restart the drone.");
                    return;
                }
                device = deviceOpt.get();
                if (!StringUtils.hasText(device.getWorkspaceId())) {
                if (!StringUtils.hasText(deviceOpt.get().getWorkspaceId())) {
                    this.unsubscribeTopicOffline(from);
                    return;
                }
                RedisOpsUtils.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + from, device,
                        RedisConst.DEVICE_ALIVE_SECOND);
                deviceRedisService.setDeviceOnline(deviceOpt.get());
                this.subscribeTopicOnline(from);
            }
            DeviceDTO device = deviceOpt.get();
            deviceRedisService.setDeviceOnline(device);
            receiver = objectMapper.readValue(payload, CommonTopicReceiver.class);
@@ -550,6 +552,7 @@
                .version(gateway.getVersion())
                .domain(gateway.getDomain() != null ?
                        gateway.getDomain() : DeviceDomainEnum.GATEWAY.getVal())
                .deviceIndex(gateway.getSubDevices().isEmpty() ? null : gateway.getSubDevices().get(0).getIndex())
                .build();
    }
@@ -578,7 +581,6 @@
                .deviceType(device.getType())
                .subType(device.getSubType())
                .version(device.getVersion())
                .deviceIndex(device.getIndex())
                .domain(DeviceDomainEnum.SUB_DEVICE.getVal())
                .build();
    }
@@ -592,12 +594,12 @@
        if (entity == null) {
            return null;
        }
        DeviceDTO.DeviceDTOBuilder deviceDTOBuilder = DeviceDTO.builder()
        DeviceDTO deviceDTO = DeviceDTO.builder()
                .deviceSn(entity.getDeviceSn())
                .childDeviceSn(entity.getChildSn())
                .deviceName(entity.getDeviceName())
                .deviceDesc(entity.getDeviceDesc())
                .deviceIndex(entity.getDeviceIndex())
                .controlSource(entity.getDeviceIndex())
                .workspaceId(entity.getWorkspaceId())
                .type(entity.getDeviceType())
                .subType(entity.getSubType())
@@ -617,31 +619,43 @@
                .firmwareVersion(entity.getFirmwareVersion())
                .workspaceName(entity.getWorkspaceId() != null ?
                        workspaceService.getWorkspaceByWorkspaceId(entity.getWorkspaceId())
                                .map(WorkspaceDTO::getWorkspaceName).orElse("") : "");
                                .map(WorkspaceDTO::getWorkspaceName).orElse("") : "")
                .firmwareStatus(DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal()).build();
        addFirmwareStatus(deviceDTO, entity);
        return deviceDTO;
    }
    private void addFirmwareStatus(DeviceDTO deviceDTO, DeviceEntity entity) {
        if (!StringUtils.hasText(entity.getFirmwareVersion())) {
            return deviceDTOBuilder.firmwareStatus(DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal()).build();
            return;
        }
        // Query whether the device is updating firmware.
        Object progress = RedisOpsUtils.get(RedisConst.FIRMWARE_UPGRADING_PREFIX + entity.getDeviceSn());
        if (Objects.nonNull(progress)) {
            return deviceDTOBuilder.firmwareStatus(DeviceFirmwareStatusEnum.UPGRADING.getVal()).firmwareProgress((int)progress).build();
        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.isPresent()) {
            DeviceFirmwareNoteDTO firmwareNoteDTO = firmwareReleaseNoteOpt.get();
            if (firmwareNoteDTO.getProductVersion().equals(entity.getFirmwareVersion())) {
                return deviceDTOBuilder.firmwareStatus(entity.getCompatibleStatus() ?
                        DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal() :
                        DeviceFirmwareStatusEnum.CONSISTENT_UPGRADE.getVal()).build();
            }
            return deviceDTOBuilder.firmwareStatus(DeviceFirmwareStatusEnum.NORMAL_UPGRADE.getVal()).build();
        if (firmwareReleaseNoteOpt.isEmpty()) {
            deviceDTO.setFirmwareStatus(DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal());
            return;
        }
        return deviceDTOBuilder.firmwareStatus(DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal()).build();
        if (entity.getFirmwareVersion().equals(firmwareReleaseNoteOpt.get().getProductVersion())) {
            deviceDTO.setFirmwareStatus(entity.getCompatibleStatus() ?
                    DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal() :
                    DeviceFirmwareStatusEnum.CONSISTENT_UPGRADE.getVal());
            return;
        }
        deviceDTO.setFirmwareStatus(DeviceFirmwareStatusEnum.NORMAL_UPGRADE.getVal());
    }
    @Override
@@ -661,14 +675,14 @@
            return false;
        }
        String key = RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn();
        if (!RedisOpsUtils.checkExist(key)) {
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(device.getDeviceSn());
        if (deviceOpt.isEmpty()) {
            return false;
        }
        DeviceDTO redisDevice = (DeviceDTO)RedisOpsUtils.get(key);
        DeviceDTO redisDevice = deviceOpt.get();
        redisDevice.setWorkspaceId(device.getWorkspaceId());
        RedisOpsUtils.setWithExpire(key, redisDevice, RedisConst.DEVICE_ALIVE_SECOND);
        deviceRedisService.setDeviceOnline(redisDevice);
        if (DeviceDomainEnum.GATEWAY.getVal() == redisDevice.getDomain()) {
            this.pushDeviceOnlineTopo(webSocketManageService.getValueWithWorkspace(device.getWorkspaceId()),
@@ -686,7 +700,11 @@
        return true;
    }
    @Override
    /**
     * 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);
@@ -715,7 +733,11 @@
    }
    @Override
    /**
     * 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(),
@@ -732,8 +754,6 @@
                drone = device;
            }
        }
        assert dock != null;
        Optional<DeviceEntity> dockEntityOpt = this.bindDevice2Entity(DeviceDomainEnum.DOCK.getVal(), dock);
        Optional<DeviceEntity> droneEntityOpt = this.bindDevice2Entity(DeviceDomainEnum.SUB_DEVICE.getVal(), drone);
@@ -780,12 +800,11 @@
                        .eq(DeviceEntity::getBoundStatus, true));
        List<DeviceDTO> devicesList = pagination.getRecords().stream().map(this::deviceEntityConvertToDTO)
                .peek(device -> {
                    device.setStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn()));
                    device.setStatus(deviceRedisService.checkDeviceOnline(device.getDeviceSn()));
                    if (StringUtils.hasText(device.getChildDeviceSn())) {
                        Optional<DeviceDTO> childOpt = this.getDeviceBySn(device.getChildDeviceSn());
                        childOpt.ifPresent(child -> {
                            child.setStatus(
                                    RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + child.getDeviceSn()));
                            child.setStatus(deviceRedisService.checkDeviceOnline(child.getDeviceSn()));
                            child.setWorkspaceName(device.getWorkspaceName());
                            device.setChildren(child);
                        });
@@ -797,11 +816,16 @@
    @Override
    public void unbindDevice(String deviceSn) {
        String key = RedisConst.DEVICE_ONLINE_PREFIX + deviceSn;
        DeviceDTO redisDevice = (DeviceDTO) RedisOpsUtils.get(key);
        redisDevice.setWorkspaceId("");
        RedisOpsUtils.setWithExpire(key, redisDevice, RedisConst.DEVICE_ALIVE_SECOND);
        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("")
@@ -818,11 +842,14 @@
            return Optional.empty();
        }
        DeviceDTO device = devicesList.get(0);
        device.setStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + sn));
        device.setStatus(deviceRedisService.checkDeviceOnline(sn));
        return Optional.of(device);
    }
    @Override
    /**
     * 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.
@@ -851,43 +878,59 @@
            return ResponseResult.error();
        }
        DeviceOtaCreateParam deviceOtaFirmware = deviceOtaFirmwares.get(0);
        List<DeviceDTO> devices = getDevicesByParams(DeviceQueryParam.builder().childSn(deviceOtaFirmware.getSn()).build());
        String gatewaySn = devices.isEmpty() ? deviceOtaFirmware.getSn() : devices.get(0).getDeviceSn();
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(deviceOtaFirmwares.get(0).getSn());
        if (deviceOpt.isEmpty()) {
            throw new RuntimeException("Device is offline.");
        }
        DeviceDTO device = deviceOpt.get();
        String gatewaySn = DeviceDomainEnum.DOCK.getVal() == device.getDomain() ? device.getDeviceSn() : device.getParentSn();
        String topic = THING_MODEL_PRE + PRODUCT + gatewaySn + SERVICES_SUF;
        checkOtaConditions(gatewaySn);
        // The bids in the progress messages reported subsequently are the same.
        String bid = UUID.randomUUID().toString();
        ServiceReply serviceReply = messageSender.publishWithReply(
                topic, CommonTopicResponse.<Map<String, List<DeviceOtaCreateParam>>>builder()
                        .tid(UUID.randomUUID().toString())
                        .bid(bid)
                        .timestamp(System.currentTimeMillis())
                        .method(FirmwareMethodEnum.OTA_CREATE.getMethod())
                        .data(Map.of(MapKeyConst.DEVICES, deviceOtaFirmwares))
                        .build());
        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(), "Firmware Error Code: " + serviceReply.getResult());
        }
        // Record the device state that needs to be updated.
        deviceOtaFirmwares.forEach(deviceOta -> RedisOpsUtils.setWithExpire(
                RedisConst.FIRMWARE_UPGRADING_PREFIX + deviceOta.getSn(),
                bid,
                RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND));
        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("Dock is offline.");
        }
        boolean emergencyStopState = deviceOpt.get().getEmergencyStopState();
        if (emergencyStopState) {
            throw new RuntimeException("The emergency stop button of the dock is pressed and can't be upgraded.");
        }
        DockModeCodeEnum dockMode = this.getDockMode(dockSn);
        if (DockModeCodeEnum.IDLE != dockMode) {
            throw new RuntimeException("The current status of the dock can't be upgraded.");
        }
    }
    @Override
    public void devicePropertySet(String workspaceId, String dockSn, DeviceSetPropertyEnum propertyEnum, JsonNode param) {
        boolean dockOnline = this.checkDeviceOnline(dockSn);
        if (!dockOnline) {
        Optional<DeviceDTO> dockOpt = deviceRedisService.getDeviceOnline(dockSn);
        if (dockOpt.isEmpty()) {
            throw new RuntimeException("Dock is offline.");
        }
        DeviceDTO deviceDTO = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + dockSn);
        boolean deviceOnline = this.checkDeviceOnline(deviceDTO.getChildDeviceSn());
        if (!deviceOnline) {
        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("Device is offline.");
        }
@@ -899,7 +942,6 @@
        }
        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;
@@ -907,7 +949,7 @@
        // 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);
            boolean isPublish = basicDeviceProperty.canPublish(node.getKey(), osdOpt.get());
            if (!isPublish) {
                continue;
            }
@@ -929,8 +971,7 @@
                        .tid(UUID.randomUUID().toString())
                        .timestamp(System.currentTimeMillis())
                        .data(value)
                        .build(),
                2);
                        .build());
        while (true) {
            reply = (Map<String, Object>) reply.get(value.getKey());
@@ -947,9 +988,32 @@
    }
    public Boolean checkDeviceOnline(String sn) {
        String key = RedisConst.DEVICE_ONLINE_PREFIX + sn;
        return RedisOpsUtils.checkExist(key) && RedisOpsUtils.getExpire(key) > 0;
    @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);
    }
    /**
@@ -1038,7 +1102,7 @@
                .build();
    }
    private Optional<DeviceEntity> onlineSaveDevice(DeviceEntity device, String childSn) {
    private Optional<DeviceEntity> onlineSaveDevice(DeviceEntity device, String childSn, String parentSn) {
        Optional<DeviceDTO> deviceOpt = this.getDeviceBySn(device.getDeviceSn());
        if (deviceOpt.isEmpty()) {
@@ -1060,19 +1124,46 @@
        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);
        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();
    }
}
src/main/java/com/dji/sample/manage/service/impl/DockOSDServiceImpl.java
@@ -1,6 +1,8 @@
package com.dji.sample.manage.service.impl;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
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;
@@ -11,6 +13,7 @@
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.Objects;
/**
 * @author sean
@@ -40,6 +43,20 @@
            OsdDockReceiver data = mapper.convertValue(receiver.getData(), OsdDockReceiver.class);
            wsMessage.getData().setHost(data);
            sendMessageService.sendBatch(webSessions, wsMessage);
            String key = RedisConst.OSD_PREFIX + device.getDeviceSn();
            OsdDockReceiver redisData = (OsdDockReceiver) RedisOpsUtils.get(key);
            if (Objects.nonNull(data.getModeCode())) {
                if (Objects.nonNull(redisData)) {
                    data.setDrcState(redisData.getDrcState());
                }
                RedisOpsUtils.setWithExpire(key, data, RedisConst.DEVICE_ALIVE_SECOND);
                return;
            }
            if (Objects.nonNull(data.getDrcState()) && Objects.nonNull(redisData)) {
                redisData.setDrcState(data.getDrcState());
                RedisOpsUtils.setWithExpire(key, redisData, RedisConst.DEVICE_ALIVE_SECOND);
            }
        }
    }
}
src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java
@@ -15,10 +15,7 @@
import com.dji.sample.manage.model.param.DeviceQueryParam;
import com.dji.sample.manage.model.receiver.CapacityDeviceReceiver;
import com.dji.sample.manage.model.receiver.LiveCapacityReceiver;
import com.dji.sample.manage.service.ICapacityCameraService;
import com.dji.sample.manage.service.IDeviceService;
import com.dji.sample.manage.service.ILiveStreamService;
import com.dji.sample.manage.service.IWorkspaceService;
import com.dji.sample.manage.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -52,6 +49,9 @@
    @Autowired
    private IMessageSenderService messageSender;
    @Autowired
    private IDeviceRedisService deviceRedisService;
    @Override
    public List<CapacityDeviceDTO> getLiveCapacity(String workspaceId) {
@@ -64,7 +64,7 @@
        // Query the live capability of each drone.
        return devicesList.stream()
                .filter(device -> RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn()))
                .filter(device -> deviceRedisService.checkDeviceOnline(device.getDeviceSn()))
                .map(device -> CapacityDeviceDTO.builder()
                        .name(Objects.requireNonNullElse(device.getNickname(), device.getDeviceName()))
                        .sn(device.getDeviceSn())
@@ -125,8 +125,8 @@
                        .toString());
                break;
            case RTSP:
                Object url = Objects.requireNonNullElse(receiveReply.getOutput(), receiveReply.getInfo());
                this.resolveUrlUser(String.valueOf(url), live);
                String url = receiveReply.getOutput().toString();
                this.resolveUrlUser(url, live);
                break;
            case UNKNOWN:
                return ResponseResult.error(LiveErrorEnum.URL_TYPE_NOT_SUPPORTED);
@@ -207,7 +207,7 @@
        response.setMethod(LiveStreamMethodEnum.LIVE_LENS_CHANGE.getMethod());
        response.setData(liveParam);
        return messageSender.publishWithReply(respTopic, response);
        return messageSender.publishWithReply(ServiceReply.class, respTopic, response);
    }
    /**
@@ -304,7 +304,7 @@
        response.setData(liveParam);
        response.setMethod(LiveStreamMethodEnum.LIVE_START_PUSH.getMethod());
        return messageSender.publishWithReply(topic, response);
        return messageSender.publishWithReply(ServiceReply.class, topic, response);
    }
    /**
@@ -323,7 +323,7 @@
        response.setMethod(LiveStreamMethodEnum.LIVE_SET_QUALITY.getMethod());
        response.setData(data);
        return messageSender.publishWithReply(respTopic, response);
        return messageSender.publishWithReply(ServiceReply.class, respTopic, response);
    }
    /**
@@ -340,7 +340,7 @@
        response.setData(data);
        response.setMethod(LiveStreamMethodEnum.LIVE_STOP_PUSH.getMethod());
        return messageSender.publishWithReply(topic, response);
        return messageSender.publishWithReply(ServiceReply.class, topic, response);
    }
}
src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java
@@ -3,9 +3,7 @@
import com.dji.sample.common.model.CustomClaim;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.websocket.model.BizCodeEnum;
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
import com.dji.sample.component.websocket.service.ISendMessageService;
import com.dji.sample.component.websocket.service.IWebSocketManageService;
import com.dji.sample.map.model.dto.*;
import com.dji.sample.map.service.IWorkspaceElementService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -33,9 +31,6 @@
    @Autowired
    private ISendMessageService sendMessageService;
    @Autowired
    private IWebSocketManageService webSocketManageService;
    /**
     * In the first connection, pilot will send out this http request to obtain the group element list.
@@ -79,14 +74,8 @@
        // Notify all WebSocket connections in this workspace to be updated when an element is created.
        elementService.getElementByElementId(elementCreate.getId())
                .ifPresent(groupElement ->
                        sendMessageService.sendBatch(
                                webSocketManageService.getValueWithWorkspace(workspaceId),
                                CustomWebSocketMessage.<GroupElementDTO>builder()
                                        .timestamp(System.currentTimeMillis())
                                        .bizCode(BizCodeEnum.MAP_ELEMENT_CREATE.getCode())
                                        .data(groupElement)
                                        .build()));
                .ifPresent(groupElement -> sendMessageService.sendBatch(
                        workspaceId, BizCodeEnum.MAP_ELEMENT_CREATE.getCode(), groupElement));
        return ResponseResult.success(new ConcurrentHashMap<>(Map.of("id", elementCreate.getId())));
    }
@@ -115,14 +104,8 @@
        // Notify all WebSocket connections in this workspace to update when there is an element update.
        elementService.getElementByElementId(elementId)
                .ifPresent(groupElement ->
                        sendMessageService.sendBatch(
                                webSocketManageService.getValueWithWorkspace(workspaceId),
                                CustomWebSocketMessage.<GroupElementDTO>builder()
                                        .timestamp(System.currentTimeMillis())
                                        .bizCode(BizCodeEnum.MAP_ELEMENT_UPDATE.getCode())
                                        .data(groupElement)
                                        .build()));
                .ifPresent(groupElement -> sendMessageService.sendBatch(
                        workspaceId, BizCodeEnum.MAP_ELEMENT_UPDATE.getCode(), groupElement));
        return response;
    }
@@ -144,16 +127,11 @@
        // Notify all WebSocket connections in this workspace to update when an element is deleted.
        if (ResponseResult.CODE_SUCCESS == response.getCode()) {
            elementOpt.ifPresent(element ->
                    sendMessageService.sendBatch(
                    webSocketManageService.getValueWithWorkspace(workspaceId),
                            CustomWebSocketMessage.<WebSocketElementDelDTO>builder()
                                    .timestamp(System.currentTimeMillis())
                                    .bizCode(BizCodeEnum.MAP_ELEMENT_DELETE.getCode())
                                    .data(WebSocketElementDelDTO.builder()
                    sendMessageService.sendBatch(workspaceId, BizCodeEnum.MAP_ELEMENT_DELETE.getCode(),
                                    WebSocketElementDelDTO.builder()
                                            .elementId(elementId)
                                            .groupId(element.getGroupId())
                                            .build())
                                    .build()));
                                            .build()));
        }
        return response;
    }
@@ -173,14 +151,9 @@
        // Notify all WebSocket connections in this workspace to update when elements are deleted.
        if (ResponseResult.CODE_SUCCESS == response.getCode()) {
            sendMessageService.sendBatch(
                    webSocketManageService.getValueWithWorkspace(workspaceId),
                    CustomWebSocketMessage.builder()
                            .timestamp(System.currentTimeMillis())
                            .bizCode(BizCodeEnum.MAP_GROUP_REFRESH.getCode())
            sendMessageService.sendBatch(workspaceId, BizCodeEnum.MAP_GROUP_REFRESH.getCode(),
                            // Group ids that need to re-request data
                            .data(new ConcurrentHashMap<String, String[]>(Map.of("ids", new String[]{groupId})))
                            .build());
                            new ConcurrentHashMap<String, String[]>(Map.of("ids", new String[]{groupId})));
        }
        return response;
    }
src/main/java/com/dji/sample/media/service/IMediaService.java
@@ -1,8 +1,6 @@
package com.dji.sample.media.service;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.media.model.FileUploadDTO;
import org.springframework.messaging.MessageHeaders;
import java.util.List;
@@ -44,17 +42,4 @@
     */
    List<String> getExistTinyFingerprints(String workspaceId, List<String> tinyFingerprints);
    /**
     * Handle media files messages reported by dock.
     * @param receiver
     * @return
     */
    void handleFileUploadCallBack(CommonTopicReceiver receiver);
    /**
     * Handles the highest priority message about media uploads.
     * @param receiver
     * @param headers
     */
    void handleHighestPriorityUploadFlightTaskMedia(CommonTopicReceiver receiver, MessageHeaders headers);
}
src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java
@@ -1,17 +1,17 @@
package com.dji.sample.media.service.impl;
import com.dji.sample.common.error.CommonErrorEnum;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.mqtt.model.*;
import com.dji.sample.component.mqtt.model.ChannelName;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.component.mqtt.model.MapKeyConst;
import com.dji.sample.component.mqtt.service.IMessageSenderService;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.component.websocket.model.BizCodeEnum;
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
import com.dji.sample.component.websocket.service.ISendMessageService;
import com.dji.sample.component.websocket.service.IWebSocketManageService;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import com.dji.sample.manage.service.IDeviceRedisService;
import com.dji.sample.manage.service.IDeviceService;
import com.dji.sample.media.model.FileUploadCallback;
import com.dji.sample.media.model.FileUploadDTO;
@@ -22,14 +22,15 @@
import com.dji.sample.wayline.model.dto.WaylineJobDTO;
import com.dji.sample.wayline.service.IWaylineJobService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -39,6 +40,7 @@
 * @date 2021/12/9
 */
@Service
@Slf4j
public class MediaServiceImpl implements IMediaService {
    @Autowired
@@ -60,7 +62,7 @@
    private ISendMessageService sendMessageService;
    @Autowired
    private IWebSocketManageService webSocketManageService;
    private IDeviceRedisService deviceRedisService;
    @Override
    public Boolean fastUpload(String workspaceId, String fingerprint) {
@@ -90,47 +92,44 @@
    }
    @Override
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FILE_UPLOAD_CALLBACK, outputChannel = ChannelName.OUTBOUND)
    public void handleFileUploadCallBack(CommonTopicReceiver receiver) {
    /**
     * Handle media files messages reported by dock.
     * @param receiver
     * @return
     */
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FILE_UPLOAD_CALLBACK, outputChannel = ChannelName.OUTBOUND_EVENTS)
    public CommonTopicReceiver handleFileUploadCallBack(CommonTopicReceiver receiver) {
        FileUploadCallback callback = objectMapper.convertValue(receiver.getData(), FileUploadCallback.class);
        String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + receiver.getGateway()
                + TopicConst.EVENTS_SUF + TopicConst._REPLY_SUF;
        CommonTopicResponse<RequestsReply> data = CommonTopicResponse.<RequestsReply>builder()
                .timestamp(System.currentTimeMillis())
                .method(EventsMethodEnum.FILE_UPLOAD_CALLBACK.getMethod())
                .data(RequestsReply.success())
                .tid(receiver.getTid())
                .bid(receiver.getBid())
                .build();
        if (callback.getResult() != ResponseResult.CODE_SUCCESS) {
            messageSenderService.publish(topic, data);
            return;
            log.error("Media file upload failed!");
            return null;
        }
        String jobId = callback.getFile().getExt().getFlightId();
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(receiver.getGateway());
        MediaFileCountDTO mediaFileCount = (MediaFileCountDTO) RedisOpsUtils.hashGet(RedisConst.MEDIA_FILE_PREFIX + receiver.getGateway(), jobId);
        // duplicate data
        if (receiver.getBid().equals(mediaFileCount.getBid()) && receiver.getTid().equals(mediaFileCount.getTid())) {
            messageSenderService.publish(topic, data);
            return;
        if (deviceOpt.isEmpty()
                || (Objects.nonNull(mediaFileCount) && receiver.getBid().equals(mediaFileCount.getBid())
                && receiver.getTid().equals(mediaFileCount.getTid()))) {
            return receiver;
        }
        DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + receiver.getGateway());
        DeviceDTO device = deviceOpt.get();
        Optional<WaylineJobDTO> jobOpt = waylineJobService.getJobByJobId(device.getWorkspaceId(), jobId);
        if (jobOpt.isPresent()) {
            boolean isSave = parseMediaFile(callback, jobOpt.get());
            if (!isSave) {
                data.setData(RequestsReply.error(CommonErrorEnum.ILLEGAL_ARGUMENT));
                log.error("Failed to save the file to the database, please check the data manually.");
                return null;
            }
        }
        messageSenderService.publish(topic, data);
        notifyUploadedCount(mediaFileCount, receiver, jobId);
        notifyUploadedCount(mediaFileCount, receiver, jobId, device);
        return receiver;
    }
    /**
@@ -139,7 +138,11 @@
     * @param receiver
     * @param jobId
     */
    private void notifyUploadedCount(MediaFileCountDTO mediaFileCount, CommonTopicReceiver receiver, String jobId) {
    private void notifyUploadedCount(MediaFileCountDTO mediaFileCount, CommonTopicReceiver receiver, String jobId, DeviceDTO dock) {
        // Do not notify when files that do not belong to the route are uploaded.
        if (Objects.isNull(mediaFileCount)) {
            return;
        }
        mediaFileCount.setBid(receiver.getBid());
        mediaFileCount.setTid(receiver.getTid());
        mediaFileCount.setUploadedCount(mediaFileCount.getUploadedCount() + 1);
@@ -163,13 +166,8 @@
            RedisOpsUtils.hashSet(key, jobId, mediaFileCount);
        }
        DeviceDTO dock = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + receiver.getGateway());
        sendMessageService.sendBatch(webSocketManageService.getValueWithWorkspaceAndUserType(dock.getWorkspaceId(), UserTypeEnum.WEB.getVal()),
                CustomWebSocketMessage.builder()
                        .bizCode(BizCodeEnum.FILE_UPLOAD_CALLBACK.getCode())
                        .timestamp(System.currentTimeMillis())
                        .data(mediaFileCount)
                        .build());
        sendMessageService.sendBatch(dock.getWorkspaceId(), UserTypeEnum.WEB.getVal(),
                        BizCodeEnum.FILE_UPLOAD_CALLBACK.getCode(), mediaFileCount);
    }
    private Boolean parseMediaFile(FileUploadCallback callback, WaylineJobDTO job) {
@@ -184,22 +182,18 @@
        return fileService.saveFile(job.getWorkspaceId(), callback.getFile()) > 0;
    }
    @Override
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA, outputChannel = ChannelName.OUTBOUND)
    public void handleHighestPriorityUploadFlightTaskMedia(CommonTopicReceiver receiver, MessageHeaders headers) {
    /**
     * Handles the highest priority message about media uploads.
     * @param receiver
     * @param headers
     * @return
     */
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA, outputChannel = ChannelName.OUTBOUND_EVENTS)
    public CommonTopicReceiver handleHighestPriorityUploadFlightTaskMedia(CommonTopicReceiver receiver, MessageHeaders headers) {
        Map map = objectMapper.convertValue(receiver.getData(), Map.class);
        if (map.isEmpty() || !map.containsKey(MapKeyConst.FLIGHT_ID)) {
            return;
            return null;
        }
        messageSenderService.publish(headers.get(MqttHeaders.RECEIVED_TOPIC) + TopicConst._REPLY_SUF,
                CommonTopicResponse.builder()
                        .data(RequestsReply.success())
                        .method(receiver.getMethod())
                        .timestamp(System.currentTimeMillis())
                        .bid(receiver.getBid())
                        .tid(receiver.getTid())
                        .build());
        String dockSn = receiver.getGateway();
        String jobId = map.get(MapKeyConst.FLIGHT_ID).toString();
@@ -209,7 +203,7 @@
            countDTO = (MediaFileCountDTO) RedisOpsUtils.get(key);
            if (jobId.equals(countDTO.getJobId())) {
                RedisOpsUtils.setWithExpire(key, countDTO, RedisConst.DEVICE_ALIVE_SECOND * 5);
                return;
                return null;
            }
            countDTO.setPreJobId(countDTO.getJobId());
@@ -218,13 +212,13 @@
        RedisOpsUtils.setWithExpire(key, countDTO, RedisConst.DEVICE_ALIVE_SECOND * 5);
        DeviceDTO dock = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + dockSn);
        sendMessageService.sendBatch(webSocketManageService.getValueWithWorkspaceAndUserType(dock.getWorkspaceId(), UserTypeEnum.WEB.getVal()),
                CustomWebSocketMessage.builder()
                        .timestamp(System.currentTimeMillis())
                        .bizCode(BizCodeEnum.HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA.getCode())
                        .data(countDTO)
                        .build());
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(receiver.getGateway());
        if (deviceOpt.isEmpty()) {
            return null;
        }
        sendMessageService.sendBatch(deviceOpt.get().getWorkspaceId(), UserTypeEnum.WEB.getVal(),
                        BizCodeEnum.HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA.getCode(), countDTO);
        return receiver;
    }
}
src/main/java/com/dji/sample/wayline/controller/WaylineJobController.java
@@ -5,6 +5,7 @@
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.wayline.model.dto.WaylineJobDTO;
import com.dji.sample.wayline.model.param.CreateJobParam;
import com.dji.sample.wayline.model.param.UpdateJobParam;
import com.dji.sample.wayline.service.IWaylineJobService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -86,4 +87,12 @@
        waylineJobService.uploadMediaHighestPriority(workspaceId, jobId);
        return ResponseResult.success();
    }
    @PutMapping("/{workspace_id}/jobs/{job_id}")
    public ResponseResult updateJobStatus(@PathVariable(name = "workspace_id") String workspaceId,
                                          @PathVariable(name = "job_id") String jobId,
                                          @Valid @RequestBody UpdateJobParam param) {
        waylineJobService.updateJobStatus(workspaceId, jobId, param);
        return ResponseResult.success();
    }
}
src/main/java/com/dji/sample/wayline/model/dto/ConditionalWaylineJobKey.java
New file
@@ -0,0 +1,39 @@
package com.dji.sample.wayline.model.dto;
import com.dji.sample.component.redis.RedisConst;
import lombok.Data;
import java.util.Objects;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/28
 */
@Data
public class ConditionalWaylineJobKey {
    private String workspaceId;
    private String dockSn;
    private String jobId;
    public ConditionalWaylineJobKey(String workspaceId, String dockSn, String jobId) {
        this.workspaceId = workspaceId;
        this.dockSn = dockSn;
        this.jobId = jobId;
    }
    public ConditionalWaylineJobKey(String key) {
        if (Objects.isNull(key)) {
            return;
        }
        String[] keyArr = key.split(RedisConst.DELIMITER);
        new ConditionalWaylineJobKey(keyArr[0], keyArr[1], keyArr[2]);
    }
    public String getKey() {
        return String.join(RedisConst.DELIMITER, workspaceId, dockSn, jobId);
    }
}
src/main/java/com/dji/sample/wayline/model/dto/KmzFileProperties.java
@@ -35,5 +35,5 @@
    public static final String TAG_PAYLOAD_SUB_ENUM_VALUE = "payloadSubEnumValue";
    public static final String TAG_TEMPLATE_ID = "templateId";
    public static final String TAG_TEMPLATE_TYPE = "templateType";
}
src/main/java/com/dji/sample/wayline/model/dto/WaylineJobDTO.java
@@ -62,5 +62,7 @@
    private Boolean uploading;
    private WaylineTaskConditionDTO conditions;
    private String parentId;
}
src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskConditionDTO.java
New file
@@ -0,0 +1,22 @@
package com.dji.sample.wayline.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author sean
 * @version 1.3
 * @date 2023/2/16
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WaylineTaskConditionDTO {
    private WaylineTaskReadyConditionDTO readyConditions;
    private WaylineTaskExecutableConditionDTO executableConditions;
}
src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskCreateDTO.java
@@ -30,4 +30,7 @@
    private Integer outOfControlAction;
    private WaylineTaskReadyConditionDTO readyConditions;
    private WaylineTaskExecutableConditionDTO executableConditions;
}
src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskExecutableConditionDTO.java
New file
@@ -0,0 +1,23 @@
package com.dji.sample.wayline.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author sean
 * @version 1.3
 * @date 2023/2/16
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class WaylineTaskExecutableConditionDTO {
    /**
     * unit: MB
     */
    private Integer storageCapacity;
}
src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskReadyConditionDTO.java
New file
@@ -0,0 +1,24 @@
package com.dji.sample.wayline.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author sean
 * @version 1.3
 * @date 2023/2/16
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WaylineTaskReadyConditionDTO {
    private Integer batteryCapacity;
    private Long beginTime;
    private Long endTime;
}
src/main/java/com/dji/sample/wayline/model/enums/WaylineErrorCodeEnum.java
New file
@@ -0,0 +1,83 @@
package com.dji.sample.wayline.model.enums;
import com.dji.sample.common.error.IErrorInfo;
import com.fasterxml.jackson.annotation.JsonCreator;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.3
 * @date 2023/2/17
 */
public enum WaylineErrorCodeEnum implements IErrorInfo {
    SUCCESS(0, "success", false),
    EMERGENCY_BUTTON(316026, "The emergency button at the dock was pressed.", true),
    NOT_IDLE(319001, "Task Center is not currently idle.", true),
    PERFORMING_TASK(319016, "The dock is performing other tasks.", true),
    EXPORTING_LOGS(319018, "The dock is exporting logs.", true),
    PULLING_LOGS(319019, "The dock is pulling logs.", true),
    HEIGHT_LIMIT(321513, "The wayline altitude has exceeded the height limit of the drone.", true),
    DISTANCE_LIMIT(321514, "The wayline distance has exceeded the limit of the drone.", true),
    RESTRICTED_FLIGHT_AREA(321515, "The wayline passes through a restricted flight area.", true),
    SDR_DISCONNECT(514120, "The sdr link between the dock and the drone is disconnected.", true),
    HEAVY_RAIN(514134, "Heavy rain prevented the flight.", true),
    STRONG_WIND(514135, "Strong wind prevented the flight.", true),
    POWER_DISCONNECT(514136, "The dock's power supply is disconnected.", true),
    LOW_TEMPERATURE(514137, "The low temperature of the environment prevented flight.", true),
    DEBUGGING(514145, "The dock is being debugged.", true),
    REMOTE_DEBUGGING(514146, "The dock is being debugged remotely.", true),
    DOCK_UPGRADING(514147, "The dock is being upgraded.", true),
    DOCK_WORKING(514148, "The dock is working and cannot perform new tasks.", true),
    UNKNOWN(-1, "Unknown wayline error.", false);
    private String msg;
    private int code;
    boolean block;
    WaylineErrorCodeEnum(int code, String msg, boolean block) {
        this.code = code;
        this.msg = msg;
        this.block = block;
    }
    @Override
    public String getErrorMsg() {
        return msg;
    }
    @Override
    public Integer getErrorCode() {
        return code;
    }
    public boolean isBlock() {
        return block;
    }
    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
    public static WaylineErrorCodeEnum find(int code) {
        return Arrays.stream(WaylineErrorCodeEnum.values()).filter(error -> error.code == code).findAny().orElse(UNKNOWN);
    }
}
src/main/java/com/dji/sample/wayline/model/enums/WaylineMethodEnum.java
@@ -14,7 +14,11 @@
    FLIGHT_TASK_EXECUTE("flighttask_execute"),
    FLIGHT_TASK_CANCEL("flighttask_undo");
    FLIGHT_TASK_CANCEL("flighttask_undo"),
    FLIGHT_TASK_PAUSE("flighttask_pause"),
    FLIGHT_TASK_RESUME("flighttask_recovery");
    private String method;
src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskStatusEnum.java
New file
@@ -0,0 +1,19 @@
package com.dji.sample.wayline.model.enums;
import com.fasterxml.jackson.annotation.JsonValue;
/**
 * @author sean
 * @version 1.3
 * @date 2023/2/1
 */
public enum WaylineTaskStatusEnum {
    PAUSE, RESUME;
    @JsonValue
    public int getVal() {
        return ordinal();
    }
}
src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskTypeEnum.java
@@ -1,22 +1,36 @@
package com.dji.sample.wayline.model.enums;
import lombok.Getter;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.3
 * @date 2022/9/26
 */
@Getter
public enum WaylineTaskTypeEnum {
    IMMEDIATE(0),
    TIMED(1);
    TIMED(1),
    CONDITION(2);
    int val;
    WaylineTaskTypeEnum(int val) {
        this.val = val;
    }
    @JsonValue
    public int getVal() {
        return val;
    }
    @JsonCreator
    public static WaylineTaskTypeEnum find(Integer val) {
        return Arrays.stream(values()).filter(taskTypeEnum -> taskTypeEnum.val == val).findAny().get();
    }
}
src/main/java/com/dji/sample/wayline/model/enums/WaylineTemplateTypeEnum.java
@@ -1,26 +1,46 @@
package com.dji.sample.wayline.model.enums;
import lombok.Getter;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import java.util.Optional;
/**
 * @author sean
 * @version 1.3
 * @date 2022/9/26
 */
@Getter
public enum WaylineTemplateTypeEnum {
    WAYPOINT(0),
    WAYPOINT(0, "waypoint"),
    MAPPING_2D(1),
    MAPPING_2D(1, "mapping2d"),
    MAPPING_3D(2),
    MAPPING_3D(2, "mapping3d"),
    MAPPING_STRIP(4);
    MAPPING_STRIP(4, "mappingStrip");
    int val;
    WaylineTemplateTypeEnum(int val) {
    String type;
    WaylineTemplateTypeEnum(int val, String type) {
        this.val = val;
        this.type = type;
    }
    @JsonValue
    public int getVal() {
        return val;
    }
    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
    public static WaylineTemplateTypeEnum find(Integer val) {
        return Arrays.stream(values()).filter(templateTypeEnum -> templateTypeEnum.val == val).findAny().get();
    }
    public static Optional<WaylineTemplateTypeEnum> find(String type) {
        return Arrays.stream(values()).filter(templateTypeEnum -> templateTypeEnum.type.equals(type)).findAny();
    }
}
src/main/java/com/dji/sample/wayline/model/param/CreateJobParam.java
@@ -1,10 +1,13 @@
package com.dji.sample.wayline.model.param;
import com.dji.sample.wayline.model.enums.WaylineTaskTypeEnum;
import com.dji.sample.wayline.model.enums.WaylineTemplateTypeEnum;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
 * @author sean
@@ -23,15 +26,11 @@
    @NotBlank
    private String dockSn;
    @Range(max = 0)
    @NotNull
    private Integer waylineType;
    private WaylineTemplateTypeEnum waylineType;
    @Range(max = 1)
    @NotNull
    private Integer taskType;
    private Long executeTime;
    private WaylineTaskTypeEnum taskType;
    @Range(min = 20, max = 500)
    @NotNull
@@ -40,4 +39,13 @@
    @NotNull
    @Range(max = 2)
    private Integer outOfControlAction;
    @Range(min = 50, max = 90)
    private Integer minBatteryCapacity;
    private Integer minStorageCapacity;
    private List<Long> taskDays;
    private List<List<Long>> taskPeriods;
}
src/main/java/com/dji/sample/wayline/model/param/UpdateJobParam.java
New file
@@ -0,0 +1,22 @@
package com.dji.sample.wayline.model.param;
import com.dji.sample.wayline.model.enums.WaylineTaskStatusEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author sean
 * @version 1.3
 * @date 2023/2/1
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UpdateJobParam {
    private WaylineTaskStatusEnum status;
}
src/main/java/com/dji/sample/wayline/service/IFlightTaskService.java
@@ -1,8 +1,5 @@
package com.dji.sample.wayline.service;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import org.springframework.messaging.MessageHeaders;
/**
 * @author sean
 * @version 1.1
@@ -10,9 +7,5 @@
 */
public interface IFlightTaskService {
    /**
     * Handle the progress messages of the flight tasks reported by the dock.
     * @param receiver
     */
    void handleProgress(CommonTopicReceiver receiver, MessageHeaders headers);
}
src/main/java/com/dji/sample/wayline/service/IWaylineJobService.java
@@ -7,6 +7,7 @@
import com.dji.sample.wayline.model.dto.WaylineJobDTO;
import com.dji.sample.wayline.model.enums.WaylineJobStatusEnum;
import com.dji.sample.wayline.model.param.CreateJobParam;
import com.dji.sample.wayline.model.param.UpdateJobParam;
import org.springframework.messaging.MessageHeaders;
import java.sql.SQLException;
@@ -47,6 +48,14 @@
     * @return
     */
    ResponseResult publishFlightTask(CreateJobParam param, CustomClaim customClaim) throws SQLException;
    /**
     * Issue wayline mission to the dock.
     * @param waylineJob
     * @return
     * @throws SQLException
     */
    ResponseResult publishOneFlightTask(WaylineJobDTO waylineJob) throws SQLException;
    /**
     * Execute the task immediately.
@@ -118,4 +127,19 @@
     * @param jobId
     */
    void uploadMediaHighestPriority(String workspaceId, String jobId);
    /**
     * Manually control the execution status of wayline job.
     * @param workspaceId
     * @param jobId
     * @param param
     */
    void updateJobStatus(String workspaceId, String jobId, UpdateJobParam param);
    /**
     * Query the wayline execution status of the dock.
     * @param dockSn
     * @return
     */
    WaylineJobStatusEnum getWaylineState(String dockSn);
}
src/main/java/com/dji/sample/wayline/service/IWaylineRedisService.java
New file
@@ -0,0 +1,100 @@
package com.dji.sample.wayline.service;
import com.dji.sample.component.mqtt.model.EventsReceiver;
import com.dji.sample.wayline.model.dto.ConditionalWaylineJobKey;
import com.dji.sample.wayline.model.dto.WaylineJobDTO;
import com.dji.sample.wayline.model.dto.WaylineTaskProgressReceiver;
import java.util.Optional;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/24
 */
public interface IWaylineRedisService {
    /**
     * Save the status of the wayline job performed by the dock into redis.
     * @param dockSn
     * @param data
     */
    void setRunningWaylineJob(String dockSn, EventsReceiver<WaylineTaskProgressReceiver> data);
    /**
     * Query the status of wayline job performed by the dock in redis.
     * @param dockSn
     * @return
     */
    Optional<EventsReceiver<WaylineTaskProgressReceiver>> getRunningWaylineJob(String dockSn);
    /**
     * Delete the wayline job status of the dock operation in redis.
     * @param dockSn
     * @return
     */
    Boolean delRunningWaylineJob(String dockSn);
    /**
     * Save the wayline job suspended by the dock to redis.
     * @param dockSn
     * @param jobId
     */
    void setPausedWaylineJob(String dockSn, String jobId);
    /**
     * Query the wayline job id suspended by the dock in redis.
     * @param dockSn
     * @return
     */
    String getPausedWaylineJobId(String dockSn);
    /**
     * Delete the wayline job suspended by the dock in redis.
     * @param dockSn
     * @return
     */
    Boolean delPausedWaylineJob(String dockSn);
    /**
     * Save the wayline job blocked by the dock to redis.
     * @param dockSn
     * @param jobId
     */
    void setBlockedWaylineJob(String dockSn, String jobId);
    /**
     * Query the wayline job id blocked by the dock in redis.
     * @param dockSn
     * @return
     */
    String getBlockedWaylineJobId(String dockSn);
    /**
     * Save the conditional wayline job by the dock to redis.
     * @param waylineJob
     */
    void setConditionalWaylineJob(WaylineJobDTO waylineJob);
    /**
     * Query the conditional wayline job id by the dock in redis.
     * @param jobId
     * @return
     */
    Optional<WaylineJobDTO> getConditionalWaylineJob(String jobId);
    /**
     * Delete the conditional wayline job by the dock in redis.
     * @param jobId
     * @return
     */
    Boolean delConditionalWaylineJob(String jobId);
    Boolean addPrepareConditionalWaylineJob(WaylineJobDTO waylineJob);
    Optional<ConditionalWaylineJobKey> getNearestConditionalWaylineJob();
    Double getConditionalWaylineJobTime(ConditionalWaylineJobKey jobKey);
    Boolean removePrepareConditionalWaylineJob(ConditionalWaylineJobKey jobKey);
}
src/main/java/com/dji/sample/wayline/service/impl/FlightTaskServiceImpl.java
@@ -1,22 +1,24 @@
package com.dji.sample.wayline.service.impl;
import com.dji.sample.common.error.CommonErrorEnum;
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.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.component.websocket.model.BizCodeEnum;
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
import com.dji.sample.component.websocket.service.ISendMessageService;
import com.dji.sample.component.websocket.service.IWebSocketManageService;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import com.dji.sample.manage.service.IDeviceRedisService;
import com.dji.sample.media.model.MediaFileCountDTO;
import com.dji.sample.wayline.model.dto.ConditionalWaylineJobKey;
import com.dji.sample.wayline.model.dto.WaylineJobDTO;
import com.dji.sample.wayline.model.dto.WaylineTaskProgressReceiver;
import com.dji.sample.wayline.model.enums.WaylineJobStatusEnum;
import com.dji.sample.wayline.service.IFlightTaskService;
import com.dji.sample.wayline.service.IWaylineJobService;
import com.dji.sample.wayline.service.IWaylineRedisService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
@@ -27,8 +29,10 @@
import org.springframework.messaging.MessageHeaders;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.TimeUnit;
@@ -51,14 +55,21 @@
    private ISendMessageService websocketMessageService;
    @Autowired
    private IWebSocketManageService webSocketManageService;
    @Autowired
    private IWaylineJobService waylineJobService;
    @Override
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FLIGHT_TASK_PROGRESS, outputChannel = ChannelName.OUTBOUND)
    public void handleProgress(CommonTopicReceiver receiver, MessageHeaders headers) {
    @Autowired
    private IDeviceRedisService deviceRedisService;
    @Autowired
    private IWaylineRedisService waylineRedisService;
    /**
     * Handle the progress messages of the flight tasks reported by the dock.
     * @param receiver
     * @param headers
     */
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FLIGHT_TASK_PROGRESS, outputChannel = ChannelName.OUTBOUND_EVENTS)
    public CommonTopicReceiver handleProgress(CommonTopicReceiver receiver, MessageHeaders headers) {
        String receivedTopic = String.valueOf(headers.get(MqttHeaders.RECEIVED_TOPIC));
        String dockSn  = receivedTopic.substring((TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT).length(),
                receivedTopic.indexOf(TopicConst.EVENTS_SUF));
@@ -76,8 +87,7 @@
        }
        EventsResultStatusEnum statusEnum = EventsResultStatusEnum.find(output.getStatus());
        String key = RedisConst.WAYLINE_JOB_RUNNING_PREFIX + dockSn;
        RedisOpsUtils.setWithExpire(key, eventsReceiver, RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND);
        waylineRedisService.setRunningWaylineJob(dockSn, eventsReceiver);
        if (statusEnum.getEnd()) {
            WaylineJobDTO job = WaylineJobDTO.builder()
@@ -99,30 +109,66 @@
            }
            waylineJobService.updateJob(job);
            RedisOpsUtils.del(key);
            RedisOpsUtils.del(RedisConst.WAYLINE_JOB_PAUSED_PREFIX + receiver.getBid());
            waylineRedisService.delRunningWaylineJob(dockSn);
            waylineRedisService.delPausedWaylineJob(receiver.getBid());
        }
        DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + receiver.getGateway());
        websocketMessageService.sendBatch(
                webSocketManageService.getValueWithWorkspaceAndUserType(
                        device.getWorkspaceId(), UserTypeEnum.WEB.getVal()),
                CustomWebSocketMessage.builder()
                        .data(eventsReceiver)
                        .timestamp(System.currentTimeMillis())
                        .bizCode(BizCodeEnum.FLIGHT_TASK_PROGRESS.getCode())
                        .build());
        if (receiver.getNeedReply() == 1) {
            messageSender.publish(receivedTopic + TopicConst._REPLY_SUF,
                    CommonTopicResponse.builder()
                            .tid(receiver.getTid())
                            .bid(receiver.getBid())
                            .method(EventsMethodEnum.FLIGHT_TASK_PROGRESS.getMethod())
                            .timestamp(System.currentTimeMillis())
                            .data(RequestsReply.success())
                            .build());
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(receiver.getGateway());
        if (deviceOpt.isEmpty()) {
            return null;
        }
        websocketMessageService.sendBatch(deviceOpt.get().getWorkspaceId(), UserTypeEnum.WEB.getVal(),
                        BizCodeEnum.FLIGHT_TASK_PROGRESS.getCode(), eventsReceiver);
        return receiver;
    }
    /**
     * Notifications will be received through this interface when tasks are ready on the device.
     * @param receiver
     * @param headers
     */
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FLIGHT_TASK_READY, outputChannel = ChannelName.OUTBOUND_EVENTS)
    public CommonTopicReceiver handleTaskNotifications(CommonTopicReceiver receiver, MessageHeaders headers) {
        String receivedTopic = String.valueOf(headers.get(MqttHeaders.RECEIVED_TOPIC));
        String dockSn  = receivedTopic.substring((TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT).length(),
                receivedTopic.indexOf(TopicConst.EVENTS_SUF));
        List<String> flightIds = mapper.convertValue(receiver.getData(),
                new TypeReference<Map<String, List<String>>>(){}).get(MapKeyConst.FLIGHT_IDS);
        log.info("ready task list:{}", Arrays.toString(flightIds.toArray()) );
        // Check conditional task blocking status.
        String blockedId = waylineRedisService.getBlockedWaylineJobId(dockSn);
        if (!StringUtils.hasText(blockedId)) {
            return null;
        }
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(dockSn);
        if (deviceOpt.isEmpty()) {
            return null;
        }
        DeviceDTO device = deviceOpt.get();
        try {
            for (String jobId : flightIds) {
                boolean isExecute = waylineJobService.executeFlightTask(device.getWorkspaceId(), jobId);
                if (!isExecute) {
                    return null;
                }
                Optional<WaylineJobDTO> waylineJobOpt = waylineRedisService.getConditionalWaylineJob(jobId);
                if (waylineJobOpt.isEmpty()) {
                    log.info("The conditional job has expired and will no longer be executed.");
                    return receiver;
                }
                WaylineJobDTO waylineJob = waylineJobOpt.get();
                this.retryPrepareJob(new ConditionalWaylineJobKey(device.getWorkspaceId(), dockSn, jobId), waylineJob);
                return receiver;
            }
        } catch (Exception e) {
            log.error("Failed to execute conditional task.");
            e.printStackTrace();
        }
        return receiver;
    }
    @Scheduled(initialDelay = 10, fixedRate = 5, timeUnit = TimeUnit.SECONDS)
@@ -166,4 +212,80 @@
            }
        }
    }
    @Scheduled(initialDelay = 10, fixedRate = 5, timeUnit = TimeUnit.SECONDS)
    private void prepareConditionJob() {
        Optional<ConditionalWaylineJobKey> jobKeyOpt = waylineRedisService.getNearestConditionalWaylineJob();
        if (jobKeyOpt.isEmpty()) {
            return;
        }
        ConditionalWaylineJobKey jobKey = jobKeyOpt.get();
        log.info("Check the conditional tasks of the wayline. {}", jobKey.toString());
        // format: {workspace_id}:{dock_sn}:{job_id}
        double time = waylineRedisService.getConditionalWaylineJobTime(jobKey);
        long now = System.currentTimeMillis();
        // prepare the task one day in advance.
        int offset = 86_400_000;
        if (now + offset < time) {
            return;
        }
        WaylineJobDTO job = WaylineJobDTO.builder()
                .jobId(jobKey.getJobId())
                .status(WaylineJobStatusEnum.FAILED.getVal())
                .executeTime(LocalDateTime.now())
                .completedTime(LocalDateTime.now())
                .code(HttpStatus.SC_INTERNAL_SERVER_ERROR).build();
        try {
            Optional<WaylineJobDTO> waylineJobOpt = waylineRedisService.getConditionalWaylineJob(jobKey.getJobId());
            if (waylineJobOpt.isEmpty()) {
                job.setCode(CommonErrorEnum.REDIS_DATA_NOT_FOUND.getErrorCode());
                waylineJobService.updateJob(job);
                waylineRedisService.removePrepareConditionalWaylineJob(jobKey);
                return;
            }
            WaylineJobDTO waylineJob = waylineJobOpt.get();
            ResponseResult result = waylineJobService.publishOneFlightTask(waylineJob);
            waylineRedisService.removePrepareConditionalWaylineJob(jobKey);
            if (ResponseResult.CODE_SUCCESS == result.getCode()) {
                return;
            }
            // If the end time is exceeded, no more retries will be made.
            waylineRedisService.delConditionalWaylineJob(jobKey.getJobId());
            if (waylineJob.getEndTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - RedisConst.WAYLINE_JOB_BLOCK_TIME * 1000 < now) {
                return;
            }
            // Retry if the end time has not been exceeded.
            this.retryPrepareJob(jobKey, waylineJob);
        } catch (Exception e) {
            log.info("Failed to prepare the conditional task.");
            waylineJobService.updateJob(job);
        }
    }
    private void retryPrepareJob(ConditionalWaylineJobKey jobKey, WaylineJobDTO waylineJob) {
        Optional<WaylineJobDTO> childJobOpt = waylineJobService.createWaylineJobByParent(jobKey.getWorkspaceId(), jobKey.getJobId());
        if (childJobOpt.isEmpty()) {
            log.error("Failed to create wayline job.");
            return;
        }
        WaylineJobDTO newJob = childJobOpt.get();
        newJob.setBeginTime(LocalDateTime.now().plusSeconds(RedisConst.WAYLINE_JOB_BLOCK_TIME));
        boolean isAdd = waylineRedisService.addPrepareConditionalWaylineJob(newJob);
        if (!isAdd) {
            log.error("Failed to create wayline job. {}", newJob.getJobId());
            return;
        }
        waylineJob.setJobId(newJob.getJobId());
        waylineRedisService.setConditionalWaylineJob(waylineJob);
    }
}
src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java
@@ -12,6 +12,7 @@
import com.dji.sample.wayline.model.dto.KmzFileProperties;
import com.dji.sample.wayline.model.dto.WaylineFileDTO;
import com.dji.sample.wayline.model.entity.WaylineFileEntity;
import com.dji.sample.wayline.model.enums.WaylineTemplateTypeEnum;
import com.dji.sample.wayline.model.param.WaylineQueryParam;
import com.dji.sample.wayline.service.IWaylineFileService;
import org.dom4j.Document;
@@ -187,7 +188,7 @@
            ZipEntry nextEntry = unzipFile.getNextEntry();
            while (Objects.nonNull(nextEntry)) {
                boolean isWaylines = (KmzFileProperties.FILE_DIR_FIRST + "/" + KmzFileProperties.FILE_DIR_SECOND_WAYLINES).equals(nextEntry.getName());
                boolean isWaylines = (KmzFileProperties.FILE_DIR_FIRST + "/" + KmzFileProperties.FILE_DIR_SECOND_TEMPLATE).equals(nextEntry.getName());
                if (!isWaylines) {
                    nextEntry = unzipFile.getNextEntry();
                    continue;
@@ -208,11 +209,11 @@
                String subType = droneNode.valueOf(KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_DRONE_SUB_ENUM_VALUE);
                String payloadType = payloadNode.valueOf(KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_PAYLOAD_ENUM_VALUE);
                String payloadSubType = payloadNode.valueOf(KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_PAYLOAD_SUB_ENUM_VALUE);
                String templateId = document.valueOf("//" + KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_TEMPLATE_ID);
                String templateType = document.valueOf("//" + KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_TEMPLATE_TYPE);
                if (!StringUtils.hasText(type) || !StringUtils.hasText(subType) ||
                        !StringUtils.hasText(payloadSubType) || !StringUtils.hasText(payloadType) ||
                        !StringUtils.hasText(templateId)) {
                        !StringUtils.hasText(templateType)) {
                    throw new RuntimeException("The file format is incorrect.");
                }
@@ -222,7 +223,7 @@
                        .objectKey(OssConfiguration.objectDirPrefix + File.separator + filename)
                        .name(filename.substring(0, filename.lastIndexOf(WAYLINE_FILE_SUFFIX)))
                        .sign(DigestUtils.md5DigestAsHex(file.getInputStream()))
                        .templateTypes(List.of(Integer.parseInt(templateId)))
                        .templateTypes(List.of(WaylineTemplateTypeEnum.find(templateType).map(WaylineTemplateTypeEnum::getVal).orElse(-1)))
                        .build());
            }
src/main/java/com/dji/sample/wayline/service/impl/WaylineJobServiceImpl.java
@@ -12,7 +12,14 @@
import com.dji.sample.component.mqtt.service.IMessageSenderService;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.control.model.param.DrcModeParam;
import com.dji.sample.control.service.IDrcService;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.enums.DeviceModeCodeEnum;
import com.dji.sample.manage.model.enums.DockModeCodeEnum;
import com.dji.sample.manage.model.receiver.OsdDockReceiver;
import com.dji.sample.manage.model.receiver.OsdSubDeviceReceiver;
import com.dji.sample.manage.service.IDeviceRedisService;
import com.dji.sample.manage.service.IDeviceService;
import com.dji.sample.media.model.MediaFileCountDTO;
import com.dji.sample.media.model.MediaMethodEnum;
@@ -20,12 +27,15 @@
import com.dji.sample.wayline.dao.IWaylineJobMapper;
import com.dji.sample.wayline.model.dto.*;
import com.dji.sample.wayline.model.entity.WaylineJobEntity;
import com.dji.sample.wayline.model.enums.WaylineErrorCodeEnum;
import com.dji.sample.wayline.model.enums.WaylineJobStatusEnum;
import com.dji.sample.wayline.model.enums.WaylineMethodEnum;
import com.dji.sample.wayline.model.enums.WaylineTaskTypeEnum;
import com.dji.sample.wayline.model.param.CreateJobParam;
import com.dji.sample.wayline.model.param.UpdateJobParam;
import com.dji.sample.wayline.service.IWaylineFileService;
import com.dji.sample.wayline.service.IWaylineJobService;
import com.dji.sample.wayline.service.IWaylineRedisService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
@@ -37,6 +47,7 @@
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.net.URL;
import java.sql.SQLException;
@@ -72,6 +83,15 @@
    @Autowired
    private IFileService fileService;
    @Autowired
    private IDrcService drcService;
    @Autowired
    private IDeviceRedisService deviceRedisService;
    @Autowired
    private IWaylineRedisService waylineRedisService;
    private Optional<WaylineJobDTO> insertWaylineJob(WaylineJobEntity jobEntity) {
        int id = mapper.insert(jobEntity);
        if (id <= 0) {
@@ -96,8 +116,8 @@
                .beginTime(beginTime)
                .endTime(endTime)
                .status(WaylineJobStatusEnum.PENDING.getVal())
                .taskType(param.getTaskType())
                .waylineType(param.getWaylineType())
                .taskType(param.getTaskType().getVal())
                .waylineType(param.getWaylineType().getVal())
                .outOfControlAction(param.getOutOfControlAction())
                .rthAltitude(param.getRthAltitude())
                .mediaCount(0)
@@ -123,24 +143,107 @@
        return this.insertWaylineJob(jobEntity);
    }
    /**
     * For immediate tasks, the server time shall prevail.
     * @param param
     */
    private void fillImmediateTime(CreateJobParam param) {
        if (WaylineTaskTypeEnum.IMMEDIATE != param.getTaskType()) {
            return;
        }
        long now = System.currentTimeMillis() / 1000;
        if (CollectionUtils.isEmpty(param.getTaskDays())) {
            param.setTaskDays(List.of(now));
        }
        if (CollectionUtils.isEmpty(param.getTaskPeriods())) {
            param.setTaskPeriods(List.of(List.of(now)));
        }
    }
    @Override
    public ResponseResult publishFlightTask(CreateJobParam param, CustomClaim customClaim) throws SQLException {
        if (WaylineTaskTypeEnum.IMMEDIATE.getVal() == param.getTaskType()) {
            param.setExecuteTime(System.currentTimeMillis());
        }
        Optional<WaylineJobDTO> waylineJobOpt = this.createWaylineJob(param,
                customClaim.getWorkspaceId(), customClaim.getUsername(),
                param.getExecuteTime(), param.getExecuteTime());
        if (waylineJobOpt.isEmpty()) {
            throw new SQLException("Failed to create wayline job.");
        }
        WaylineJobDTO waylineJob = waylineJobOpt.get();
        fillImmediateTime(param);
        boolean isOnline = deviceService.checkDeviceOnline(waylineJob.getDockSn());
        for (Long taskDay : param.getTaskDays()) {
            LocalDate date = LocalDate.ofInstant(Instant.ofEpochSecond(taskDay), ZoneId.systemDefault());
            for (List<Long> taskPeriod : param.getTaskPeriods()) {
                long beginTime = LocalDateTime.of(date, LocalTime.ofInstant(Instant.ofEpochSecond(taskPeriod.get(0)), ZoneId.systemDefault()))
                        .atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
                long endTime = taskPeriod.size() > 1 ?
                        LocalDateTime.of(date, LocalTime.ofInstant(Instant.ofEpochSecond(taskPeriod.get(1)), ZoneId.systemDefault()))
                                .atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : beginTime;
                Optional<WaylineJobDTO> waylineJobOpt = this.createWaylineJob(param, customClaim.getWorkspaceId(), customClaim.getUsername(), beginTime, endTime);
                if (waylineJobOpt.isEmpty()) {
                    throw new SQLException("Failed to create wayline job.");
                }
                WaylineJobDTO waylineJob = waylineJobOpt.get();
                // If it is a conditional task type, add conditions to the job parameters.
                addConditions(waylineJob, param, beginTime, endTime);
                return this.publishOneFlightTask(waylineJob);
            }
        }
        return ResponseResult.error();
    }
    private void addConditions(WaylineJobDTO waylineJob, CreateJobParam param, Long beginTime, Long endTime) {
        if (WaylineTaskTypeEnum.CONDITION != param.getTaskType()) {
            return;
        }
        waylineJob.setConditions(
                WaylineTaskConditionDTO.builder()
                        .executableConditions(Objects.nonNull(param.getMinStorageCapacity()) ?
                                WaylineTaskExecutableConditionDTO.builder().storageCapacity(param.getMinStorageCapacity()).build() : null)
                        .readyConditions(WaylineTaskReadyConditionDTO.builder()
                                .batteryCapacity(param.getMinBatteryCapacity())
                                .beginTime(beginTime)
                                .endTime(endTime)
                                .build())
                        .build());
        waylineRedisService.setConditionalWaylineJob(waylineJob);
        // key: wayline_job_condition, value: {workspace_id}:{dock_sn}:{job_id}
        boolean isAdd = waylineRedisService.addPrepareConditionalWaylineJob(waylineJob);
        if (!isAdd) {
            throw new RuntimeException("Failed to create conditional job.");
        }
    }
    public ResponseResult publishOneFlightTask(WaylineJobDTO waylineJob) throws SQLException {
        boolean isOnline = deviceRedisService.checkDeviceOnline(waylineJob.getDockSn());
        if (!isOnline) {
            throw new RuntimeException("Dock is offline.");
        }
        boolean isSuccess = this.prepareFlightTask(waylineJob);
        if (!isSuccess) {
            return ResponseResult.error("Failed to prepare job.");
        }
        // Issue an immediate task execution command.
        if (WaylineTaskTypeEnum.IMMEDIATE.getVal() == waylineJob.getTaskType()) {
            if (!executeFlightTask(waylineJob.getWorkspaceId(), waylineJob.getJobId())) {
                return ResponseResult.error("Failed to execute job.");
            }
        }
        if (WaylineTaskTypeEnum.TIMED.getVal() == waylineJob.getTaskType()) {
            // key: wayline_job_timed, value: {workspace_id}:{dock_sn}:{job_id}
            boolean isAdd = RedisOpsUtils.zAdd(RedisConst.WAYLINE_JOB_TIMED_EXECUTE,
                    waylineJob.getWorkspaceId() + RedisConst.DELIMITER + waylineJob.getDockSn() + RedisConst.DELIMITER + waylineJob.getJobId(),
                    waylineJob.getBeginTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
            if (!isAdd) {
                return ResponseResult.error("Failed to create scheduled job.");
            }
        }
        return ResponseResult.success();
    }
    private Boolean prepareFlightTask(WaylineJobDTO waylineJob) throws SQLException {
        // get wayline file
        Optional<WaylineFileDTO> waylineFile = waylineFileService.getWaylineByWaylineId(waylineJob.getWorkspaceId(), waylineJob.getFileId());
        if (waylineFile.isEmpty()) {
@@ -163,17 +266,16 @@
                        .build())
                .build();
        String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT +
                waylineJob.getDockSn() + TopicConst.SERVICES_SUF;
        CommonTopicResponse<Object> response = CommonTopicResponse.builder()
                .tid(UUID.randomUUID().toString())
                .bid(waylineJob.getJobId())
                .timestamp(System.currentTimeMillis())
                .data(flightTask)
                .method(WaylineMethodEnum.FLIGHT_TASK_PREPARE.getMethod())
                .build();
        if (WaylineTaskTypeEnum.CONDITION.getVal() == waylineJob.getTaskType()) {
            if (Objects.isNull(waylineJob.getConditions())) {
                throw new IllegalArgumentException();
            }
            flightTask.setReadyConditions(waylineJob.getConditions().getReadyConditions());
            flightTask.setExecutableConditions(waylineJob.getConditions().getExecutableConditions());
        }
        ServiceReply serviceReply = messageSender.publishWithReply(topic, response);
        ServiceReply serviceReply = messageSender.publishServicesTopic(
                waylineJob.getDockSn(), WaylineMethodEnum.FLIGHT_TASK_PREPARE.getMethod(), flightTask, waylineJob.getJobId());
        if (ResponseResult.CODE_SUCCESS != serviceReply.getResult()) {
            log.info("Prepare task ====> Error code: {}", serviceReply.getResult());
            this.updateJob(WaylineJobDTO.builder()
@@ -183,26 +285,9 @@
                    .status(WaylineJobStatusEnum.FAILED.getVal())
                    .completedTime(LocalDateTime.now())
                    .code(serviceReply.getResult()).build());
            return ResponseResult.error("Prepare task ====> Error code: " + serviceReply.getResult());
            return false;
        }
        // Issue an immediate task execution command.
        if (WaylineTaskTypeEnum.IMMEDIATE.getVal() == waylineJob.getTaskType()) {
            if (!executeFlightTask(waylineJob.getWorkspaceId(), waylineJob.getJobId())) {
                return ResponseResult.error("Failed to execute job.");
            }
        }
        if (WaylineTaskTypeEnum.TIMED.getVal() == waylineJob.getTaskType()) {
            boolean isAdd = RedisOpsUtils.zAdd(RedisConst.WAYLINE_JOB_TIMED_EXECUTE,
                    waylineJob.getWorkspaceId() + RedisConst.DELIMITER + waylineJob.getDockSn() + RedisConst.DELIMITER + waylineJob.getJobId(),
                    waylineJob.getBeginTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
            if (!isAdd) {
                return ResponseResult.error("Failed to create scheduled job.");
            }
        }
        return ResponseResult.success();
        return true;
    }
    @Override
@@ -213,7 +298,7 @@
            throw new IllegalArgumentException("Job doesn't exist.");
        }
        boolean isOnline = deviceService.checkDeviceOnline(waylineJob.get().getDockSn());
        boolean isOnline = deviceRedisService.checkDeviceOnline(waylineJob.get().getDockSn());
        if (!isOnline) {
            throw new RuntimeException("Dock is offline.");
        }
@@ -221,17 +306,8 @@
        WaylineJobDTO job = waylineJob.get();
        WaylineTaskCreateDTO flightTask = WaylineTaskCreateDTO.builder().flightId(jobId).build();
        String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT +
                job.getDockSn() + TopicConst.SERVICES_SUF;
        CommonTopicResponse<Object> response = CommonTopicResponse.builder()
                .tid(UUID.randomUUID().toString())
                .bid(jobId)
                .timestamp(System.currentTimeMillis())
                .data(flightTask)
                .method(WaylineMethodEnum.FLIGHT_TASK_EXECUTE.getMethod())
                .build();
        ServiceReply serviceReply = messageSender.publishWithReply(topic, response);
        ServiceReply serviceReply = messageSender.publishServicesTopic(
                job.getDockSn(), WaylineMethodEnum.FLIGHT_TASK_EXECUTE.getMethod(), flightTask, jobId);
        if (ResponseResult.CODE_SUCCESS != serviceReply.getResult()) {
            log.info("Execute job ====> Error code: {}", serviceReply.getResult());
            this.updateJob(WaylineJobDTO.builder()
@@ -240,6 +316,11 @@
                    .status(WaylineJobStatusEnum.FAILED.getVal())
                    .completedTime(LocalDateTime.now())
                    .code(serviceReply.getResult()).build());
            // The conditional task fails and enters the blocking status.
            if (WaylineTaskTypeEnum.CONDITION.getVal() == job.getTaskType()
                    && WaylineErrorCodeEnum.find(serviceReply.getResult()).isBlock()) {
                waylineRedisService.setBlockedWaylineJob(job.getDockSn(), jobId);
            }
            return false;
        }
@@ -248,9 +329,7 @@
                .executeTime(LocalDateTime.now())
                .status(WaylineJobStatusEnum.IN_PROGRESS.getVal())
                .build());
        RedisOpsUtils.setWithExpire(RedisConst.WAYLINE_JOB_RUNNING_PREFIX + job.getDockSn(),
                EventsReceiver.<WaylineTaskProgressReceiver>builder().bid(jobId).sn(job.getDockSn()).build(),
                RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND);
        waylineRedisService.setRunningWaylineJob(job.getDockSn(), EventsReceiver.<WaylineTaskProgressReceiver>builder().bid(jobId).sn(job.getDockSn()).build());
        return true;
    }
@@ -262,8 +341,7 @@
        // Check if the task status is correct.
        boolean isErr = !jobIds.removeAll(waylineJobIds) || !jobIds.isEmpty() ;
        if (isErr) {
            throw new IllegalArgumentException("These tasks have an incorrect status and cannot be canceled. "
                    + Arrays.toString(jobIds.toArray()));
            throw new IllegalArgumentException("These tasks have an incorrect status and cannot be canceled. " + Arrays.toString(jobIds.toArray()));
        }
        // Group job id by dock sn.
@@ -275,21 +353,13 @@
    }
    public void publishCancelTask(String workspaceId, String dockSn, List<String> jobIds) {
        boolean isOnline = deviceService.checkDeviceOnline(dockSn);
        boolean isOnline = deviceRedisService.checkDeviceOnline(dockSn);
        if (!isOnline) {
            throw new RuntimeException("Dock is offline.");
        }
        String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + dockSn + TopicConst.SERVICES_SUF;
        CommonTopicResponse<Object> response = CommonTopicResponse.builder()
                .tid(UUID.randomUUID().toString())
                .bid(UUID.randomUUID().toString())
                .timestamp(System.currentTimeMillis())
                .data(Map.of(MapKeyConst.FLIGHT_IDS, jobIds))
                .method(WaylineMethodEnum.FLIGHT_TASK_CANCEL.getMethod())
                .build();
        ServiceReply serviceReply = messageSender.publishWithReply(topic, response);
        ServiceReply serviceReply = messageSender.publishServicesTopic(
                dockSn, WaylineMethodEnum.FLIGHT_TASK_CANCEL.getMethod(), Map.of(MapKeyConst.FLIGHT_IDS, jobIds));
        if (ResponseResult.CODE_SUCCESS != serviceReply.getResult()) {
            log.info("Cancel job ====> Error code: {}", serviceReply.getResult());
            throw new RuntimeException("Failed to cancel the wayline job of " + dockSn);
@@ -366,8 +436,11 @@
        String topic = headers.get(MqttHeaders.RECEIVED_TOPIC).toString() + TopicConst._REPLY_SUF;
        DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + receiver.getGateway());
        Optional<WaylineJobDTO> waylineJobOpt = this.getJobByJobId(device.getWorkspaceId(), jobId);
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(receiver.getGateway());
        if (deviceOpt.isEmpty()) {
            return;
        }
        Optional<WaylineJobDTO> waylineJobOpt = this.getJobByJobId(deviceOpt.get().getWorkspaceId(), jobId);
        if (waylineJobOpt.isEmpty()) {
            builder.data(RequestsReply.error(CommonErrorEnum.ILLEGAL_ARGUMENT));
            messageSender.publish(topic, builder.build());
@@ -415,19 +488,12 @@
        String dockSn = jobOpt.get().getDockSn();
        String key = RedisConst.MEDIA_HIGHEST_PRIORITY_PREFIX + dockSn;
        if (RedisOpsUtils.checkExist(key) &&
                jobId.equals(((MediaFileCountDTO) RedisOpsUtils.get(key)).getJobId())) {
        if (RedisOpsUtils.checkExist(key) && jobId.equals(((MediaFileCountDTO) RedisOpsUtils.get(key)).getJobId())) {
            return;
        }
        ServiceReply reply = messageSender.publishWithReply(TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + dockSn + TopicConst.SERVICES_SUF,
                CommonTopicResponse.builder()
                        .tid(UUID.randomUUID().toString())
                        .bid(UUID.randomUUID().toString())
                        .timestamp(System.currentTimeMillis())
                        .method(MediaMethodEnum.UPLOAD_FLIGHT_TASK_MEDIA_PRIORITIZE.getMethod())
                        .data(Map.of(MapKeyConst.FLIGHT_ID, jobId))
                        .build());
        ServiceReply reply = messageSender.publishServicesTopic(
                dockSn, MediaMethodEnum.UPLOAD_FLIGHT_TASK_MEDIA_PRIORITIZE.getMethod(), Map.of(MapKeyConst.FLIGHT_ID, jobId));
        if (ResponseResult.CODE_SUCCESS != reply.getResult()) {
            throw new RuntimeException("Failed to set media job upload priority. Error Code: " + reply.getResult());
        }
@@ -467,6 +533,91 @@
                .build();
    }
    @Override
    public void updateJobStatus(String workspaceId, String jobId, UpdateJobParam param) {
        Optional<WaylineJobDTO> waylineJobOpt = this.getJobByJobId(workspaceId, jobId);
        if (waylineJobOpt.isEmpty()) {
            throw new RuntimeException("The job does not exist.");
        }
        WaylineJobDTO waylineJob = waylineJobOpt.get();
        WaylineJobStatusEnum statusEnum = this.getWaylineState(waylineJob.getDockSn());
        if (statusEnum.getEnd() || WaylineJobStatusEnum.PENDING == statusEnum) {
            throw new RuntimeException("The wayline job status does not match, and the operation cannot be performed.");
        }
        switch (param.getStatus()) {
            case PAUSE:
                pauseJob(workspaceId, waylineJob.getDockSn(), jobId, statusEnum);
                break;
            case RESUME:
                resumeJob(workspaceId, waylineJob.getDockSn(), jobId, statusEnum);
                break;
        }
    }
    public WaylineJobStatusEnum getWaylineState(String dockSn) {
        Optional<DeviceDTO> dockOpt = deviceRedisService.getDeviceOnline(dockSn);
        if (dockOpt.isEmpty() || !StringUtils.hasText(dockOpt.get().getChildDeviceSn())) {
            return WaylineJobStatusEnum.UNKNOWN;
        }
        Optional<OsdDockReceiver> dockOsdOpt = deviceRedisService.getDeviceOsd(dockSn, OsdDockReceiver.class);
        Optional<OsdSubDeviceReceiver> deviceOsdOpt = deviceRedisService.getDeviceOsd(dockOpt.get().getChildDeviceSn(), OsdSubDeviceReceiver.class);
        if (dockOsdOpt.isEmpty() || deviceOsdOpt.isEmpty() || DockModeCodeEnum.WORKING != dockOsdOpt.get().getModeCode()) {
            return WaylineJobStatusEnum.UNKNOWN;
        }
        OsdSubDeviceReceiver osdDevice = deviceOsdOpt.get();
        if (DeviceModeCodeEnum.WAYLINE == osdDevice.getModeCode()
                || DeviceModeCodeEnum.MANUAL == osdDevice.getModeCode()
                || DeviceModeCodeEnum.TAKEOFF_AUTO == osdDevice.getModeCode()) {
            if (StringUtils.hasText(waylineRedisService.getPausedWaylineJobId(dockSn))) {
                return WaylineJobStatusEnum.PAUSED;
            }
            if (waylineRedisService.getRunningWaylineJob(dockSn).isPresent()) {
                return WaylineJobStatusEnum.IN_PROGRESS;
            }
        }
        return WaylineJobStatusEnum.UNKNOWN;
    }
    private void pauseJob(String workspaceId, String dockSn, String jobId, WaylineJobStatusEnum statusEnum) {
        if (WaylineJobStatusEnum.PAUSED == statusEnum && jobId.equals(waylineRedisService.getPausedWaylineJobId(dockSn))) {
            waylineRedisService.setPausedWaylineJob(dockSn, jobId);
            return;
        }
        ServiceReply reply = messageSender.publishServicesTopic(
                dockSn, WaylineMethodEnum.FLIGHT_TASK_PAUSE.getMethod(), "", jobId);
        if (ResponseResult.CODE_SUCCESS != reply.getResult()) {
            throw new RuntimeException("Failed to pause wayline job. Error Code: " + reply.getResult());
        }
        waylineRedisService.delRunningWaylineJob(dockSn);
        waylineRedisService.setPausedWaylineJob(dockSn, jobId);
    }
    private void resumeJob(String workspaceId, String dockSn, String jobId, WaylineJobStatusEnum statusEnum) {
        Optional<EventsReceiver<WaylineTaskProgressReceiver>> runningDataOpt = waylineRedisService.getRunningWaylineJob(dockSn);
        if (WaylineJobStatusEnum.IN_PROGRESS == statusEnum && jobId.equals(runningDataOpt.map(EventsReceiver::getSn).get())) {
            waylineRedisService.setRunningWaylineJob(dockSn, runningDataOpt.get());
            return;
        }
        ServiceReply reply = messageSender.publishServicesTopic(
                dockSn, WaylineMethodEnum.FLIGHT_TASK_RESUME.getMethod(), "", jobId);
        if (ResponseResult.CODE_SUCCESS != reply.getResult()) {
            throw new RuntimeException("Failed to resume wayline job. Error Code: " + reply.getResult());
        }
        runningDataOpt.ifPresent(runningData -> waylineRedisService.setRunningWaylineJob(dockSn, runningData));
        waylineRedisService.delPausedWaylineJob(dockSn);
        if (deviceService.checkDockDrcMode(dockSn)) {
            drcService.deviceDrcExit(workspaceId, DrcModeParam.builder().dockSn(dockSn)
                    .clientId(drcService.getDrcModeInRedis(dockSn)).build());
        }
    }
    private WaylineJobDTO entity2Dto(WaylineJobEntity entity) {
        if (entity == null) {
            return null;
@@ -484,8 +635,8 @@
                .username(entity.getUsername())
                .workspaceId(entity.getWorkspaceId())
                .status(WaylineJobStatusEnum.IN_PROGRESS.getVal() == entity.getStatus() &&
                        RedisOpsUtils.checkExist(RedisConst.WAYLINE_JOB_PAUSED_PREFIX + entity.getJobId()) ?
                        WaylineJobStatusEnum.PAUSED.getVal() : entity.getStatus())
                        entity.getJobId().equals(waylineRedisService.getPausedWaylineJobId(entity.getDockSn())) ?
                                WaylineJobStatusEnum.PAUSED.getVal() : entity.getStatus())
                .code(entity.getErrorCode())
                .beginTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getBeginTime()), ZoneId.systemDefault()))
                .endTime(Objects.nonNull(entity.getEndTime()) ?
@@ -503,11 +654,12 @@
        if (Objects.nonNull(entity.getEndTime())) {
            builder.endTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getEndTime()), ZoneId.systemDefault()));
        }
        if (WaylineJobStatusEnum.IN_PROGRESS.getVal() == entity.getStatus() && RedisOpsUtils.getExpire(entity.getJobId()) > 0) {
            EventsReceiver<WaylineTaskProgressReceiver> taskProgress = (EventsReceiver<WaylineTaskProgressReceiver>) RedisOpsUtils.get(RedisConst.WAYLINE_JOB_RUNNING_PREFIX + entity.getDockSn());
            if (Objects.nonNull(taskProgress.getOutput()) && Objects.nonNull(taskProgress.getOutput().getProgress())) {
                builder.progress(taskProgress.getOutput().getProgress().getPercent());
            }
        if (WaylineJobStatusEnum.IN_PROGRESS.getVal() == entity.getStatus()) {
            builder.progress(waylineRedisService.getRunningWaylineJob(entity.getDockSn())
                    .map(EventsReceiver::getOutput)
                    .map(WaylineTaskProgressReceiver::getProgress)
                    .map(WaylineTaskProgress::getPercent)
                    .orElse(null));
        }
        if (entity.getMediaCount() == 0) {
src/main/java/com/dji/sample/wayline/service/impl/WaylineRedisServiceImpl.java
New file
@@ -0,0 +1,112 @@
package com.dji.sample.wayline.service.impl;
import com.dji.sample.component.mqtt.model.EventsReceiver;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.wayline.model.dto.ConditionalWaylineJobKey;
import com.dji.sample.wayline.model.dto.WaylineJobDTO;
import com.dji.sample.wayline.model.dto.WaylineTaskProgressReceiver;
import com.dji.sample.wayline.service.IWaylineRedisService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Objects;
import java.util.Optional;
/**
 * @author sean
 * @version 1.4
 * @date 2023/3/24
 */
@Service
public class WaylineRedisServiceImpl implements IWaylineRedisService {
    @Override
    public void setRunningWaylineJob(String dockSn, EventsReceiver<WaylineTaskProgressReceiver> data) {
        RedisOpsUtils.setWithExpire(RedisConst.WAYLINE_JOB_RUNNING_PREFIX + dockSn, data, RedisConst.DRC_MODE_ALIVE_SECOND);
    }
    @Override
    public Optional<EventsReceiver<WaylineTaskProgressReceiver>> getRunningWaylineJob(String dockSn) {
        return Optional.ofNullable((EventsReceiver<WaylineTaskProgressReceiver>) RedisOpsUtils.get(RedisConst.WAYLINE_JOB_RUNNING_PREFIX + dockSn));
    }
    @Override
    public Boolean delRunningWaylineJob(String dockSn) {
        return RedisOpsUtils.del(RedisConst.WAYLINE_JOB_RUNNING_PREFIX + dockSn);
    }
    @Override
    public void setPausedWaylineJob(String dockSn, String jobId) {
        RedisOpsUtils.setWithExpire(RedisConst.WAYLINE_JOB_PAUSED_PREFIX + dockSn, jobId, RedisConst.DRC_MODE_ALIVE_SECOND);
    }
    @Override
    public String getPausedWaylineJobId(String dockSn) {
        return (String) RedisOpsUtils.get(RedisConst.WAYLINE_JOB_PAUSED_PREFIX + dockSn);
    }
    @Override
    public Boolean delPausedWaylineJob(String dockSn) {
        return RedisOpsUtils.del(RedisConst.WAYLINE_JOB_PAUSED_PREFIX + dockSn);
    }
    @Override
    public void setBlockedWaylineJob(String dockSn, String jobId) {
        RedisOpsUtils.setWithExpire(RedisConst.WAYLINE_JOB_BLOCK_PREFIX + dockSn, jobId, RedisConst.WAYLINE_JOB_BLOCK_TIME);
    }
    @Override
    public String getBlockedWaylineJobId(String dockSn) {
        return (String) RedisOpsUtils.get(RedisConst.WAYLINE_JOB_BLOCK_PREFIX + dockSn);
    }
    @Override
    public void setConditionalWaylineJob(WaylineJobDTO waylineJob) {
        if (!StringUtils.hasText(waylineJob.getJobId())) {
            throw new RuntimeException("Job id can't be null.");
        }
        RedisOpsUtils.setWithExpire(RedisConst.WAYLINE_JOB_CONDITION_PREFIX + waylineJob.getJobId(), waylineJob,
                (Duration.between(waylineJob.getEndTime(), LocalDateTime.now()).getSeconds()));
    }
    @Override
    public Optional<WaylineJobDTO> getConditionalWaylineJob(String jobId) {
        return Optional.ofNullable((WaylineJobDTO) RedisOpsUtils.get(RedisConst.WAYLINE_JOB_CONDITION_PREFIX + jobId));
    }
    @Override
    public Boolean delConditionalWaylineJob(String jobId) {
        return RedisOpsUtils.del(RedisConst.WAYLINE_JOB_CONDITION_PREFIX + jobId);
    }
    @Override
    public Boolean addPrepareConditionalWaylineJob(WaylineJobDTO waylineJob) {
        if (Objects.isNull(waylineJob.getBeginTime())) {
            return false;
        }
        // value: {workspace_id}:{dock_sn}:{job_id}
        return RedisOpsUtils.zAdd(RedisConst.WAYLINE_JOB_CONDITION_PREPARE,
                waylineJob.getWorkspaceId() + RedisConst.DELIMITER + waylineJob.getDockSn() + RedisConst.DELIMITER + waylineJob.getJobId(),
                waylineJob.getBeginTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
    }
    @Override
    public Optional<ConditionalWaylineJobKey> getNearestConditionalWaylineJob() {
        return Optional.ofNullable(RedisOpsUtils.zGetMin(RedisConst.WAYLINE_JOB_CONDITION_PREPARE))
                .map(Object::toString).map(ConditionalWaylineJobKey::new);
    }
    @Override
    public Double getConditionalWaylineJobTime(ConditionalWaylineJobKey jobKey) {
        return RedisOpsUtils.zScore(RedisConst.WAYLINE_JOB_CONDITION_PREPARE, jobKey.getKey());
    }
    @Override
    public Boolean removePrepareConditionalWaylineJob(ConditionalWaylineJobKey jobKey) {
        return RedisOpsUtils.zRemove(RedisConst.WAYLINE_JOB_CONDITION_PREPARE, jobKey.getKey());
    }
}
src/main/resources/application.yml
@@ -52,6 +52,11 @@
    path:
    # Topics that need to be subscribed when initially connecting to mqtt, multiple topics are divided by ",".
    inbound-topic: sys/product/+/status,thing/product/+/requests
  DRC:
    protocol: WS # @see com.dji.sample.component.mqtt.model.MqttProtocolEnum
    host: Please enter your ip.
    port: 8083
    path: /mqtt
url:
  manage:
@@ -75,7 +80,7 @@
# Tutorial: https://www.alibabacloud.com/help/en/object-storage-service/latest/use-a-temporary-credential-provided-by-sts-to-access-oss
oss:
  enable: true
  enable: false
  provider: ali # @see com.dji.sample.component.OssConfiguration.model.enums.OssTypeEnum
  endpoint: https://oss-cn-hangzhou.aliyuncs.com
  access-key: Please enter your access key.