From 694b9483c7a551626244cbc222c602ea9ff74094 Mon Sep 17 00:00:00 2001
From: sean.zhou <sean.zhou@dji.com>
Date: Tue, 25 Apr 2023 21:44:00 +0800
Subject: [PATCH] What's new? 1. Wayline management: added `pause wayline task` and `recover wayline task`. 2. Added command flight function. 3. Fixed some issues.

---
 src/main/java/com/dji/sample/manage/model/dto/DeviceAuthorityDTO.java                     |   26 
 src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java           |    8 
 src/main/java/com/dji/sample/control/model/dto/PointDTO.java                              |   31 
 src/main/java/com/dji/sample/control/service/impl/PayloadCommandsHandler.java             |   82 
 src/main/java/com/dji/sample/manage/model/receiver/OutOfControlActionReceiver.java        |   29 
 src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java         |    4 
 src/main/java/com/dji/sample/control/model/param/DrcConnectParam.java                     |   19 
 src/main/java/com/dji/sample/manage/service/impl/DeviceRedisServiceImpl.java              |   80 
 src/main/java/com/dji/sample/control/model/enums/DroneAuthorityEnum.java                  |   25 
 src/main/java/com/dji/sample/manage/model/enums/DroneRcLostActionEnum.java                |   26 
 src/main/java/com/dji/sample/control/model/dto/TakeoffProgressReceiver.java               |   25 
 src/main/java/com/dji/sample/component/GlobalExceptionHandler.java                        |    2 
 src/main/java/com/dji/sample/control/model/dto/BatteryStoreMode.java                      |    4 
 src/main/java/com/dji/sample/control/model/dto/LinkWorkMode.java                          |    4 
 src/main/java/com/dji/sample/component/mqtt/model/EventsMethodEnum.java                   |   10 
 src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java |   27 
 src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskConditionDTO.java               |   22 
 src/main/java/com/dji/sample/manage/model/enums/ControlSourceEnum.java                    |   26 
 src/main/java/com/dji/sample/wayline/model/enums/WaylineTemplateTypeEnum.java             |   34 
 src/main/java/com/dji/sample/manage/model/enums/DockModeCodeEnum.java                     |   42 
 src/main/java/com/dji/sample/control/controller/DrcController.java                        |   55 
 src/main/java/com/dji/sample/control/model/param/FlyToPointParam.java                     |   37 
 src/main/java/com/dji/sample/wayline/service/impl/WaylineJobServiceImpl.java              |  330 ++
 src/main/java/com/dji/sample/manage/model/receiver/BasicDeviceProperty.java               |    5 
 src/main/java/com/dji/sample/wayline/model/dto/WaylineJobDTO.java                         |    2 
 src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java                |   29 
 src/main/java/com/dji/sample/control/model/param/PayloadCommandsParam.java                |   27 
 src/main/java/com/dji/sample/control/service/impl/DrcServiceImpl.java                     |  249 ++
 src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java             |    9 
 src/main/java/com/dji/sample/control/model/dto/AlarmState.java                            |    4 
 src/main/java/com/dji/sample/manage/service/IDeviceRedisService.java                      |   95 +
 src/main/java/com/dji/sample/manage/service/impl/DeviceLogsServiceImpl.java               |  110 
 src/main/java/com/dji/sample/wayline/service/IFlightTaskService.java                      |    9 
 src/main/java/com/dji/sample/manage/service/IDeviceFirmwareService.java                   |    9 
 src/main/java/com/dji/sample/control/service/impl/GimbalResetImpl.java                    |   22 
 src/main/java/com/dji/sample/manage/model/enums/DeviceModeCodeEnum.java                   |   64 
 src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java                    |   15 
 src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java                 |   27 
 src/main/java/com/dji/sample/control/service/impl/CameraRecordingStartImpl.java           |   25 
 src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskExecutableConditionDTO.java     |   23 
 pom.xml                                                                                   |    2 
 src/main/java/com/dji/sample/control/model/dto/DrcStatusNotifyReceiver.java               |   18 
 src/main/java/com/dji/sample/manage/model/receiver/OsdDockReceiver.java                   |    8 
 src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java    |   78 
 src/main/java/com/dji/sample/control/model/dto/ResultNotifyDTO.java                       |   24 
 src/main/java/com/dji/sample/control/service/impl/CameraRecordingStopImpl.java            |   22 
 src/main/java/com/dji/sample/wayline/service/impl/WaylineRedisServiceImpl.java            |  112 +
 src/main/java/com/dji/sample/control/model/dto/RemoteDebugOpenState.java                  |   25 
 src/main/java/com/dji/sample/control/model/enums/PayloadCommandsEnum.java                 |   52 
 src/main/java/com/dji/sample/control/model/enums/TakeoffStatusEnum.java                   |   47 
 src/main/java/com/dji/sample/control/service/impl/CameraFocalLengthSetImpl.java           |   41 
 src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java            |   50 
 src/main/java/com/dji/sample/control/model/enums/MqttAclAccessEnum.java                   |   24 
 src/main/java/com/dji/sample/control/model/enums/DrcModeReasonEnum.java                   |   47 
 src/main/java/com/dji/sample/control/service/impl/CameraAimImpl.java                      |   24 
 src/main/java/com/dji/sample/media/service/IMediaService.java                             |   15 
 src/main/java/com/dji/sample/wayline/service/impl/FlightTaskServiceImpl.java              |  184 +
 src/main/java/com/dji/sample/control/model/enums/DroneControlMethodEnum.java              |   28 
 src/main/java/com/dji/sample/wayline/model/enums/WaylineMethodEnum.java                   |    6 
 src/main/java/com/dji/sample/control/model/enums/CameraTypeEnum.java                      |   38 
 src/main/java/com/dji/sample/control/service/impl/CameraModeSwitchImpl.java               |   31 
 src/main/java/com/dji/sample/wayline/service/IWaylineRedisService.java                    |  100 +
 src/main/java/com/dji/sample/control/service/impl/RemoteDebugHandler.java                 |   23 
 src/main/java/com/dji/sample/manage/model/enums/HmsEnum.java                              |   14 
 src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java                     |   10 
 src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java               |   45 
 src/main/java/com/dji/sample/control/model/enums/DrcMethodEnum.java                       |   22 
 src/main/java/com/dji/sample/wayline/model/dto/ConditionalWaylineJobKey.java              |   39 
 src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java              |   16 
 src/main/resources/application.yml                                                        |    7 
 src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java               |   22 
 src/main/java/com/dji/sample/control/model/dto/DrcModeDTO.java                            |   32 
 src/main/java/com/dji/sample/control/model/dto/MqttBrokerDTO.java                         |   31 
 src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskCreateDTO.java                  |    3 
 src/main/java/com/dji/sample/manage/model/receiver/DeviceHmsReceiver.java                 |    2 
 src/main/java/com/dji/sample/control/model/dto/DrcModeReasonReceiver.java                 |   15 
 src/main/java/com/dji/sample/wayline/model/param/UpdateJobParam.java                      |   22 
 src/main/java/com/dji/sample/manage/service/impl/DeviceFirmwareServiceImpl.java           |  103 
 src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java                        |   12 
 src/main/java/com/dji/sample/control/model/param/DeviceDrcInfoParam.java                  |   19 
 src/main/java/com/dji/sample/wayline/model/enums/WaylineErrorCodeEnum.java                |   83 
 sql/cloud_sample.sql                                                                      |    5 
 src/main/java/com/dji/sample/control/service/impl/CameraPhotoTakeImpl.java                |   22 
 src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java                   |  321 ++-
 src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java                 |    3 
 src/main/java/com/dji/sample/manage/model/enums/DeviceSetPropertyEnum.java                |    5 
 src/main/java/com/dji/sample/wayline/model/param/CreateJobParam.java                      |   20 
 api/Cloud API Demo.postman_collection.json                                                |  317 +++
 src/main/java/com/dji/sample/manage/model/receiver/LiveviewWorldRegionReceiver.java       |   20 
 src/main/java/com/dji/sample/manage/model/enums/DockDrcStateEnum.java                     |   36 
 src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java                              |    4 
 src/main/java/com/dji/sample/control/model/enums/FlyToStatusEnum.java                     |   39 
 src/main/java/com/dji/sample/control/model/enums/CameraStateEnum.java                     |   26 
 src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java                |    2 
 src/main/java/com/dji/sample/control/model/enums/DrcStatusErrorEnum.java                  |   52 
 src/main/java/com/dji/sample/wayline/service/IWaylineJobService.java                      |   24 
 src/main/java/com/dji/sample/component/GlobalScheduleService.java                         |   11 
 src/main/java/com/dji/sample/control/service/impl/ControlServiceImpl.java                 |  273 ++
 src/main/java/com/dji/sample/manage/model/enums/WaylineRcLostActionEnum.java              |   26 
 src/main/java/com/dji/sample/component/redis/RedisConst.java                              |   12 
 src/main/java/com/dji/sample/control/model/enums/GimbalResetModeEnum.java                 |   26 
 src/main/java/com/dji/sample/component/mqtt/handler/ServicesReplyHandler.java             |    9 
 src/main/java/com/dji/sample/control/model/enums/RemoteDebugMethodEnum.java               |   20 
 src/main/java/com/dji/sample/control/model/param/TakeoffToPointParam.java                 |   49 
 src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskReadyConditionDTO.java          |   24 
 src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java                         |    5 
 src/main/java/com/dji/sample/control/controller/DockController.java                       |   40 
 src/main/java/com/dji/sample/control/model/dto/JwtAclDTO.java                             |   26 
 src/main/java/com/dji/sample/manage/service/impl/DockOSDServiceImpl.java                  |   17 
 src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java            |  108 
 src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskStatusEnum.java               |   19 
 src/main/java/com/dji/sample/control/service/IControlService.java                         |   56 
 src/main/java/com/dji/sample/manage/model/receiver/RthAltitudeReceiver.java               |   32 
 src/main/java/com/dji/sample/component/mqtt/handler/EventsRouter.java                     |   29 
 src/main/java/com/dji/sample/manage/service/IWorkspaceService.java                        |    2 
 src/main/java/com/dji/sample/manage/service/IDeviceService.java                           |   57 
 src/main/java/com/dji/sample/manage/service/impl/DeviceHmsServiceImpl.java                |   41 
 src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java                       |    6 
 src/main/java/com/dji/sample/wayline/model/dto/KmzFileProperties.java                     |    2 
 src/main/java/com/dji/sample/manage/service/IDeviceLogsService.java                       |    3 
 src/main/java/com/dji/sample/control/model/dto/ReturnHomeState.java                       |   27 
 src/main/java/com/dji/sample/manage/model/receiver/OsdCameraReceiver.java                 |   34 
 src/main/java/com/dji/sample/control/service/IDrcService.java                             |   60 
 src/main/java/com/dji/sample/control/model/dto/FlyToProgressReceiver.java                 |   22 
 /dev/null                                                                                 |   35 
 src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskTypeEnum.java                 |   20 
 src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java                     |  110 
 src/main/java/com/dji/sample/control/model/enums/CameraModeEnum.java                      |   26 
 src/main/java/com/dji/sample/manage/model/receiver/FirmwareProgressExtReceiver.java       |   14 
 src/main/java/com/dji/sample/component/mqtt/model/EventsOutputProgressReceiver.java       |    4 
 src/main/java/com/dji/sample/wayline/controller/WaylineJobController.java                 |    9 
 src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java                   |   17 
 src/main/java/com/dji/sample/control/model/param/DrcModeParam.java                        |   35 
 src/main/java/com/dji/sample/control/model/param/DronePayloadParam.java                   |   54 
 134 files changed, 4,602 insertions(+), 822 deletions(-)

diff --git a/api/Cloud API Demo.postman_collection.json b/api/Cloud API Demo.postman_collection.json
index a1535ea..6bfd878 100644
--- a/api/Cloud API Demo.postman_collection.json
+++ b/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"
 					},
 					{
diff --git a/pom.xml b/pom.xml
index 66e3efa..4f0fea6 100644
--- a/pom.xml
+++ b/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>
diff --git a/sql/cloud_sample.sql b/sql/cloud_sample.sql
index 3ff45db..74fe68c 100644
--- a/sql/cloud_sample.sql
+++ b/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',
diff --git a/src/main/java/com/dji/sample/component/GlobalExceptionHandler.java b/src/main/java/com/dji/sample/component/GlobalExceptionHandler.java
index 953fd2d..0b6b6b0 100644
--- a/src/main/java/com/dji/sample/component/GlobalExceptionHandler.java
+++ b/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());
     }
 
 }
diff --git a/src/main/java/com/dji/sample/component/GlobalScheduleService.java b/src/main/java/com/dji/sample/component/GlobalScheduleService.java
index e092ad4..7e77a34 100644
--- a/src/main/java/com/dji/sample/component/GlobalScheduleService.java
+++ b/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);
             }
diff --git a/src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java b/src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java
index 69c6398..ba8d939 100644
--- a/src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java
+++ b/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();
diff --git a/src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java b/src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java
index 8e168e8..8ff8629 100644
--- a/src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java
+++ b/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();
+    }
 }
diff --git a/src/main/java/com/dji/sample/component/mqtt/handler/EventsRouter.java b/src/main/java/com/dji/sample/component/mqtt/handler/EventsRouter.java
index 49034b3..152ae81 100644
--- a/src/main/java/com/dji/sample/component/mqtt/handler/EventsRouter.java
+++ b/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());
+
+    }
+
 }
diff --git a/src/main/java/com/dji/sample/component/mqtt/handler/ServicesReplyHandler.java b/src/main/java/com/dji/sample/component/mqtt/handler/ServicesReplyHandler.java
index c0115ab..8d8030d 100644
--- a/src/main/java/com/dji/sample/component/mqtt/handler/ServicesReplyHandler.java
+++ b/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);
diff --git a/src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java b/src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java
index aeee108..cf0ef71 100644
--- a/src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java
+++ b/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";
 }
diff --git a/src/main/java/com/dji/sample/component/mqtt/model/EventsMethodEnum.java b/src/main/java/com/dji/sample/component/mqtt/model/EventsMethodEnum.java
index d0782e0..37205bc 100644
--- a/src/main/java/com/dji/sample/component/mqtt/model/EventsMethodEnum.java
+++ b/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;
diff --git a/src/main/java/com/dji/sample/component/mqtt/model/EventsOutputReceiver.java b/src/main/java/com/dji/sample/component/mqtt/model/EventsOutputProgressReceiver.java
similarity index 75%
rename from src/main/java/com/dji/sample/component/mqtt/model/EventsOutputReceiver.java
rename to src/main/java/com/dji/sample/component/mqtt/model/EventsOutputProgressReceiver.java
index 87667e8..499d85a 100644
--- a/src/main/java/com/dji/sample/component/mqtt/model/EventsOutputReceiver.java
+++ b/src/main/java/com/dji/sample/component/mqtt/model/EventsOutputProgressReceiver.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;
 }
diff --git a/src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java b/src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java
index 215995a..9ee3f9a 100644
--- a/src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java
+++ b/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";
 }
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java b/src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java
index 40f7f18..e21816c 100644
--- a/src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java
+++ b/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);
 }
diff --git a/src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java b/src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java
index 2a76cf5..fdaf62c 100644
--- a/src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java
+++ b/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);
+    }
+
 }
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/component/redis/RedisConst.java b/src/main/java/com/dji/sample/component/redis/RedisConst.java
index 7ae29db..cb0ca7a 100644
--- a/src/main/java/com/dji/sample/component/redis/RedisConst.java
+++ b/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;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java b/src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java
index 76727ca..2094d48 100644
--- a/src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java
+++ b/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;
 
diff --git a/src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java b/src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java
index e1d67e7..a9b3d8f 100644
--- a/src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java
+++ b/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);
 }
diff --git a/src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java b/src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java
index 0546728..b605cf9 100644
--- a/src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java
+++ b/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);
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/control/controller/DockController.java b/src/main/java/com/dji/sample/control/controller/DockController.java
index 7836e5f..53d07f5 100644
--- a/src/main/java/com/dji/sample/control/controller/DockController.java
+++ b/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);
+    }
+
+
 }
diff --git a/src/main/java/com/dji/sample/control/controller/DrcController.java b/src/main/java/com/dji/sample/control/controller/DrcController.java
new file mode 100644
index 0000000..6b66089
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/controller/DrcController.java
@@ -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();
+    }
+
+
+}
diff --git a/src/main/java/com/dji/sample/control/model/dto/AlarmState.java b/src/main/java/com/dji/sample/control/model/dto/AlarmState.java
index f670415..1fb590b 100644
--- a/src/main/java/com/dji/sample/control/model/dto/AlarmState.java
+++ b/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;
 
diff --git a/src/main/java/com/dji/sample/control/model/dto/BatteryStoreMode.java b/src/main/java/com/dji/sample/control/model/dto/BatteryStoreMode.java
index fa76703..9656a9e 100644
--- a/src/main/java/com/dji/sample/control/model/dto/BatteryStoreMode.java
+++ b/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;
 
diff --git a/src/main/java/com/dji/sample/control/model/dto/DrcModeDTO.java b/src/main/java/com/dji/sample/control/model/dto/DrcModeDTO.java
new file mode 100644
index 0000000..771e85a
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/dto/DrcModeDTO.java
@@ -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;
+}
diff --git a/src/main/java/com/dji/sample/control/model/dto/DrcModeReasonReceiver.java b/src/main/java/com/dji/sample/control/model/dto/DrcModeReasonReceiver.java
new file mode 100644
index 0000000..26429a9
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/dto/DrcModeReasonReceiver.java
@@ -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;
+}
diff --git a/src/main/java/com/dji/sample/control/model/dto/DrcStatusNotifyReceiver.java b/src/main/java/com/dji/sample/control/model/dto/DrcStatusNotifyReceiver.java
new file mode 100644
index 0000000..1ef8c04
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/dto/DrcStatusNotifyReceiver.java
@@ -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;
+}
diff --git a/src/main/java/com/dji/sample/control/model/dto/FlyToProgressReceiver.java b/src/main/java/com/dji/sample/control/model/dto/FlyToProgressReceiver.java
new file mode 100644
index 0000000..f51fbf6
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/dto/FlyToProgressReceiver.java
@@ -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;
+}
diff --git a/src/main/java/com/dji/sample/control/model/dto/JwtAclDTO.java b/src/main/java/com/dji/sample/control/model/dto/JwtAclDTO.java
new file mode 100644
index 0000000..9bc6959
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/dto/JwtAclDTO.java
@@ -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;
+}
diff --git a/src/main/java/com/dji/sample/control/model/dto/LinkWorkMode.java b/src/main/java/com/dji/sample/control/model/dto/LinkWorkMode.java
index 00cb354..884330d 100644
--- a/src/main/java/com/dji/sample/control/model/dto/LinkWorkMode.java
+++ b/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;
diff --git a/src/main/java/com/dji/sample/control/model/dto/MqttBrokerDTO.java b/src/main/java/com/dji/sample/control/model/dto/MqttBrokerDTO.java
new file mode 100644
index 0000000..af19dba
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/dto/MqttBrokerDTO.java
@@ -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;
+}
diff --git a/src/main/java/com/dji/sample/control/model/dto/PointDTO.java b/src/main/java/com/dji/sample/control/model/dto/PointDTO.java
new file mode 100644
index 0000000..8c6acaf
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/dto/PointDTO.java
@@ -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;
+}
diff --git a/src/main/java/com/dji/sample/control/model/dto/RemoteDebugOpenState.java b/src/main/java/com/dji/sample/control/model/dto/RemoteDebugOpenState.java
new file mode 100644
index 0000000..069307d
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/dto/RemoteDebugOpenState.java
@@ -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;
+    }
+}
diff --git a/src/main/java/com/dji/sample/control/model/dto/ResultNotifyDTO.java b/src/main/java/com/dji/sample/control/model/dto/ResultNotifyDTO.java
new file mode 100644
index 0000000..0de9461
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/dto/ResultNotifyDTO.java
@@ -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;
+}
diff --git a/src/main/java/com/dji/sample/control/model/dto/ReturnHomeState.java b/src/main/java/com/dji/sample/control/model/dto/ReturnHomeState.java
new file mode 100644
index 0000000..66957ab
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/dto/ReturnHomeState.java
@@ -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);
+    }
+}
diff --git a/src/main/java/com/dji/sample/control/model/dto/TakeoffProgressReceiver.java b/src/main/java/com/dji/sample/control/model/dto/TakeoffProgressReceiver.java
new file mode 100644
index 0000000..e4f5a83
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/dto/TakeoffProgressReceiver.java
@@ -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;
+
+}
diff --git a/src/main/java/com/dji/sample/control/model/enums/CameraModeEnum.java b/src/main/java/com/dji/sample/control/model/enums/CameraModeEnum.java
new file mode 100644
index 0000000..a6dfe3c
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/enums/CameraModeEnum.java
@@ -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();
+    }
+}
diff --git a/src/main/java/com/dji/sample/control/model/enums/CameraStateEnum.java b/src/main/java/com/dji/sample/control/model/enums/CameraStateEnum.java
new file mode 100644
index 0000000..fbe9f1a
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/enums/CameraStateEnum.java
@@ -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();
+    }
+}
diff --git a/src/main/java/com/dji/sample/control/model/enums/CameraTypeEnum.java b/src/main/java/com/dji/sample/control/model/enums/CameraTypeEnum.java
new file mode 100644
index 0000000..cda43e1
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/enums/CameraTypeEnum.java
@@ -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();
+    }
+}
diff --git a/src/main/java/com/dji/sample/control/model/enums/DrcMethodEnum.java b/src/main/java/com/dji/sample/control/model/enums/DrcMethodEnum.java
new file mode 100644
index 0000000..93697d7
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/enums/DrcMethodEnum.java
@@ -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;
+    }
+}
diff --git a/src/main/java/com/dji/sample/control/model/enums/DrcModeReasonEnum.java b/src/main/java/com/dji/sample/control/model/enums/DrcModeReasonEnum.java
new file mode 100644
index 0000000..ceb20ac
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/enums/DrcModeReasonEnum.java
@@ -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);
+    }
+}
diff --git a/src/main/java/com/dji/sample/control/model/enums/DrcStatusErrorEnum.java b/src/main/java/com/dji/sample/control/model/enums/DrcStatusErrorEnum.java
new file mode 100644
index 0000000..1cee634
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/enums/DrcStatusErrorEnum.java
@@ -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);
+    }
+}
diff --git a/src/main/java/com/dji/sample/control/model/enums/DroneAuthorityEnum.java b/src/main/java/com/dji/sample/control/model/enums/DroneAuthorityEnum.java
new file mode 100644
index 0000000..e751f7e
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/enums/DroneAuthorityEnum.java
@@ -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;
+    }
+
+}
diff --git a/src/main/java/com/dji/sample/control/model/enums/DroneControlMethodEnum.java b/src/main/java/com/dji/sample/control/model/enums/DroneControlMethodEnum.java
new file mode 100644
index 0000000..dcb6a21
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/enums/DroneControlMethodEnum.java
@@ -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;
+    }
+}
diff --git a/src/main/java/com/dji/sample/control/model/enums/FlyToStatusEnum.java b/src/main/java/com/dji/sample/control/model/enums/FlyToStatusEnum.java
new file mode 100644
index 0000000..4291769
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/enums/FlyToStatusEnum.java
@@ -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();
+    }
+}
diff --git a/src/main/java/com/dji/sample/control/model/enums/GimbalResetModeEnum.java b/src/main/java/com/dji/sample/control/model/enums/GimbalResetModeEnum.java
new file mode 100644
index 0000000..2c7e9dd
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/enums/GimbalResetModeEnum.java
@@ -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();
+    }
+}
diff --git a/src/main/java/com/dji/sample/control/model/enums/MqttAclAccessEnum.java b/src/main/java/com/dji/sample/control/model/enums/MqttAclAccessEnum.java
new file mode 100644
index 0000000..771e4dc
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/enums/MqttAclAccessEnum.java
@@ -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;
+    }
+}
diff --git a/src/main/java/com/dji/sample/control/model/enums/PayloadCommandsEnum.java b/src/main/java/com/dji/sample/control/model/enums/PayloadCommandsEnum.java
new file mode 100644
index 0000000..1f1241f
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/enums/PayloadCommandsEnum.java
@@ -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();
+    }
+}
diff --git a/src/main/java/com/dji/sample/control/model/enums/RemoteControlMethodEnum.java b/src/main/java/com/dji/sample/control/model/enums/RemoteDebugMethodEnum.java
similarity index 70%
rename from src/main/java/com/dji/sample/control/model/enums/RemoteControlMethodEnum.java
rename to src/main/java/com/dji/sample/control/model/enums/RemoteDebugMethodEnum.java
index 130239c..3bfed6e 100644
--- a/src/main/java/com/dji/sample/control/model/enums/RemoteControlMethodEnum.java
+++ b/src/main/java/com/dji/sample/control/model/enums/RemoteDebugMethodEnum.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);
diff --git a/src/main/java/com/dji/sample/control/model/enums/TakeoffStatusEnum.java b/src/main/java/com/dji/sample/control/model/enums/TakeoffStatusEnum.java
new file mode 100644
index 0000000..94ae3de
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/enums/TakeoffStatusEnum.java
@@ -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();
+    }
+}
diff --git a/src/main/java/com/dji/sample/control/model/param/DeviceDrcInfoParam.java b/src/main/java/com/dji/sample/control/model/param/DeviceDrcInfoParam.java
new file mode 100644
index 0000000..05930f0
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/param/DeviceDrcInfoParam.java
@@ -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;
+}
diff --git a/src/main/java/com/dji/sample/control/model/param/DrcConnectParam.java b/src/main/java/com/dji/sample/control/model/param/DrcConnectParam.java
new file mode 100644
index 0000000..3aa7926
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/param/DrcConnectParam.java
@@ -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;
+}
diff --git a/src/main/java/com/dji/sample/control/model/param/DrcModeParam.java b/src/main/java/com/dji/sample/control/model/param/DrcModeParam.java
new file mode 100644
index 0000000..a750dc5
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/param/DrcModeParam.java
@@ -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();
+}
diff --git a/src/main/java/com/dji/sample/control/model/param/DronePayloadParam.java b/src/main/java/com/dji/sample/control/model/param/DronePayloadParam.java
new file mode 100644
index 0000000..97d156e
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/param/DronePayloadParam.java
@@ -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;
+}
diff --git a/src/main/java/com/dji/sample/control/model/param/FlyToPointParam.java b/src/main/java/com/dji/sample/control/model/param/FlyToPointParam.java
new file mode 100644
index 0000000..98b03e1
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/param/FlyToPointParam.java
@@ -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;
+}
diff --git a/src/main/java/com/dji/sample/control/model/param/PayloadCommandsParam.java b/src/main/java/com/dji/sample/control/model/param/PayloadCommandsParam.java
new file mode 100644
index 0000000..f5c4a6e
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/param/PayloadCommandsParam.java
@@ -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;
+
+}
diff --git a/src/main/java/com/dji/sample/control/model/param/TakeoffToPointParam.java b/src/main/java/com/dji/sample/control/model/param/TakeoffToPointParam.java
new file mode 100644
index 0000000..4de1273
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/model/param/TakeoffToPointParam.java
@@ -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;
+}
diff --git a/src/main/java/com/dji/sample/control/service/IControlService.java b/src/main/java/com/dji/sample/control/service/IControlService.java
index f04c138..f9e122b 100644
--- a/src/main/java/com/dji/sample/control/service/IControlService.java
+++ b/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;
 }
diff --git a/src/main/java/com/dji/sample/control/service/IDrcService.java b/src/main/java/com/dji/sample/control/service/IDrcService.java
new file mode 100644
index 0000000..505f526
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/service/IDrcService.java
@@ -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);
+}
diff --git a/src/main/java/com/dji/sample/control/service/impl/CameraAimImpl.java b/src/main/java/com/dji/sample/control/service/impl/CameraAimImpl.java
new file mode 100644
index 0000000..4afb448
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/service/impl/CameraAimImpl.java
@@ -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());
+    }
+
+}
diff --git a/src/main/java/com/dji/sample/control/service/impl/CameraFocalLengthSetImpl.java b/src/main/java/com/dji/sample/control/service/impl/CameraFocalLengthSetImpl.java
new file mode 100644
index 0000000..d8b1450
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/service/impl/CameraFocalLengthSetImpl.java
@@ -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;
+    }
+}
diff --git a/src/main/java/com/dji/sample/control/service/impl/CameraModeSwitchImpl.java b/src/main/java/com/dji/sample/control/service/impl/CameraModeSwitchImpl.java
new file mode 100644
index 0000000..b20dbf0
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/service/impl/CameraModeSwitchImpl.java
@@ -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();
+    }
+}
diff --git a/src/main/java/com/dji/sample/control/service/impl/CameraPhotoTakeImpl.java b/src/main/java/com/dji/sample/control/service/impl/CameraPhotoTakeImpl.java
new file mode 100644
index 0000000..79034b3
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/service/impl/CameraPhotoTakeImpl.java
@@ -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;
+    }
+}
diff --git a/src/main/java/com/dji/sample/control/service/impl/CameraRecordingStartImpl.java b/src/main/java/com/dji/sample/control/service/impl/CameraRecordingStartImpl.java
new file mode 100644
index 0000000..afe7f79
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/service/impl/CameraRecordingStartImpl.java
@@ -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;
+    }
+}
diff --git a/src/main/java/com/dji/sample/control/service/impl/CameraRecordingStopImpl.java b/src/main/java/com/dji/sample/control/service/impl/CameraRecordingStopImpl.java
new file mode 100644
index 0000000..929816b
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/service/impl/CameraRecordingStopImpl.java
@@ -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();
+    }
+}
diff --git a/src/main/java/com/dji/sample/control/service/impl/ControlServiceImpl.java b/src/main/java/com/dji/sample/control/service/impl/ControlServiceImpl.java
index beb1e29..d9d43a7 100644
--- a/src/main/java/com/dji/sample/control/service/impl/ControlServiceImpl.java
+++ b/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();
+    }
+
 }
diff --git a/src/main/java/com/dji/sample/control/service/impl/DrcServiceImpl.java b/src/main/java/com/dji/sample/control/service/impl/DrcServiceImpl.java
new file mode 100644
index 0000000..7e089cc
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/service/impl/DrcServiceImpl.java
@@ -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;
+    }
+
+}
diff --git a/src/main/java/com/dji/sample/control/service/impl/GimbalResetImpl.java b/src/main/java/com/dji/sample/control/service/impl/GimbalResetImpl.java
new file mode 100644
index 0000000..4e7a310
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/service/impl/GimbalResetImpl.java
@@ -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());
+    }
+
+}
diff --git a/src/main/java/com/dji/sample/control/service/impl/PayloadCommandsHandler.java b/src/main/java/com/dji/sample/control/service/impl/PayloadCommandsHandler.java
new file mode 100644
index 0000000..c0eeee6
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/service/impl/PayloadCommandsHandler.java
@@ -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.");
+        }
+    }
+
+}
diff --git a/src/main/java/com/dji/sample/control/service/impl/RemoteDebugHandler.java b/src/main/java/com/dji/sample/control/service/impl/RemoteDebugHandler.java
new file mode 100644
index 0000000..ed33e51
--- /dev/null
+++ b/src/main/java/com/dji/sample/control/service/impl/RemoteDebugHandler.java
@@ -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;
+    }
+}
diff --git a/src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java b/src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java
deleted file mode 100644
index 17a7658..0000000
--- a/src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.dji.sample.manage.controller;
-
-import com.dji.sample.component.mqtt.model.ChannelName;
-import com.dji.sample.manage.model.receiver.DeviceBasicReceiver;
-import com.dji.sample.manage.service.IDevicePayloadService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.integration.annotation.ServiceActivator;
-import org.springframework.messaging.MessageHeaders;
-import org.springframework.web.bind.annotation.RestController;
-
-/**
- * @author sean.zhou
- * @date 2021/11/19
- * @version 0.1
- */
-@RestController
-public class DevicePayloadController {
-
-    @Autowired
-    private IDevicePayloadService devicePayloadService;
-
-    /**
-     * 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
-     * @param headers
-     */
-    @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_BASIC)
-    public void stateBasic(DeviceBasicReceiver deviceBasic, MessageHeaders headers) {
-
-        devicePayloadService.saveDeviceBasicPayload(deviceBasic.getPayloads(), headers.getTimestamp());
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/dto/DeviceAuthorityDTO.java b/src/main/java/com/dji/sample/manage/model/dto/DeviceAuthorityDTO.java
new file mode 100644
index 0000000..72621e1
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/DeviceAuthorityDTO.java
@@ -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;
+
+}
diff --git a/src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java b/src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java
index 68466d7..d4c7399 100644
--- a/src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java
+++ b/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;
 }
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java b/src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java
index bb16641..555eb90 100644
--- a/src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java
+++ b/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;
 }
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java b/src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java
index a54ff0d..c50db1e 100644
--- a/src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java
+++ b/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;
+
 }
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/enums/ControlSourceEnum.java b/src/main/java/com/dji/sample/manage/model/enums/ControlSourceEnum.java
new file mode 100644
index 0000000..764bc3c
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/enums/ControlSourceEnum.java
@@ -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();
+    }
+}
diff --git a/src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java b/src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java
index 22475b2..2ce85a2 100644
--- a/src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java
+++ b/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);
+    }
 }
diff --git a/src/main/java/com/dji/sample/manage/model/enums/DeviceModeCodeEnum.java b/src/main/java/com/dji/sample/manage/model/enums/DeviceModeCodeEnum.java
new file mode 100644
index 0000000..9957032
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/enums/DeviceModeCodeEnum.java
@@ -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);
+    }
+}
diff --git a/src/main/java/com/dji/sample/manage/model/enums/DeviceSetPropertyEnum.java b/src/main/java/com/dji/sample/manage/model/enums/DeviceSetPropertyEnum.java
index 214767c..07c6658 100644
--- a/src/main/java/com/dji/sample/manage/model/enums/DeviceSetPropertyEnum.java
+++ b/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;
 
diff --git a/src/main/java/com/dji/sample/manage/model/enums/DockDrcStateEnum.java b/src/main/java/com/dji/sample/manage/model/enums/DockDrcStateEnum.java
new file mode 100644
index 0000000..9683f1f
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/enums/DockDrcStateEnum.java
@@ -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);
+    }
+}
diff --git a/src/main/java/com/dji/sample/manage/model/enums/DockModeCodeEnum.java b/src/main/java/com/dji/sample/manage/model/enums/DockModeCodeEnum.java
new file mode 100644
index 0000000..ae1aa91
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/enums/DockModeCodeEnum.java
@@ -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);
+    }
+}
diff --git a/src/main/java/com/dji/sample/manage/model/enums/DroneRcLostActionEnum.java b/src/main/java/com/dji/sample/manage/model/enums/DroneRcLostActionEnum.java
new file mode 100644
index 0000000..116847e
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/enums/DroneRcLostActionEnum.java
@@ -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();
+    }
+}
diff --git a/src/main/java/com/dji/sample/manage/model/enums/HmsEnum.java b/src/main/java/com/dji/sample/manage/model/enums/HmsEnum.java
index 04b083a..20af72f 100644
--- a/src/main/java/com/dji/sample/manage/model/enums/HmsEnum.java
+++ b/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_"),
diff --git a/src/main/java/com/dji/sample/manage/model/enums/WaylineRcLostActionEnum.java b/src/main/java/com/dji/sample/manage/model/enums/WaylineRcLostActionEnum.java
new file mode 100644
index 0000000..3632452
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/enums/WaylineRcLostActionEnum.java
@@ -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();
+    }
+}
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/BasicDeviceProperty.java b/src/main/java/com/dji/sample/manage/model/receiver/BasicDeviceProperty.java
index b232ce8..f32273d 100644
--- a/src/main/java/com/dji/sample/manage/model/receiver/BasicDeviceProperty.java
+++ b/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;
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/DeviceHmsReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/DeviceHmsReceiver.java
index f2557a5..cc0d66c 100644
--- a/src/main/java/com/dji/sample/manage/model/receiver/DeviceHmsReceiver.java
+++ b/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;
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/FirmwareProgressExtReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/FirmwareProgressExtReceiver.java
new file mode 100644
index 0000000..9818b93
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/FirmwareProgressExtReceiver.java
@@ -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;
+}
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/LiveviewWorldRegionReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/LiveviewWorldRegionReceiver.java
new file mode 100644
index 0000000..b6a09e9
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/LiveviewWorldRegionReceiver.java
@@ -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;
+}
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/OsdCameraReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/OsdCameraReceiver.java
new file mode 100644
index 0000000..96de1bc
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/OsdCameraReceiver.java
@@ -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;
+}
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/OsdDockReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/OsdDockReceiver.java
index 0cd5196..8826d35 100644
--- a/src/main/java/com/dji/sample/manage/model/receiver/OsdDockReceiver.java
+++ b/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;
 }
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java
index b39fb09..148ad9a 100644
--- a/src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java
+++ b/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;
 }
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/OutOfControlActionReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/OutOfControlActionReceiver.java
new file mode 100644
index 0000000..836242e
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/OutOfControlActionReceiver.java
@@ -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;
+    }
+
+}
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/RthAltitudeReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/RthAltitudeReceiver.java
new file mode 100644
index 0000000..1727944
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/RthAltitudeReceiver.java
@@ -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;
+    }
+
+}
diff --git a/src/main/java/com/dji/sample/manage/service/IDeviceFirmwareService.java b/src/main/java/com/dji/sample/manage/service/IDeviceFirmwareService.java
index 2d82e7b..72f50ab 100644
--- a/src/main/java/com/dji/sample/manage/service/IDeviceFirmwareService.java
+++ b/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.
diff --git a/src/main/java/com/dji/sample/manage/service/IDeviceLogsService.java b/src/main/java/com/dji/sample/manage/service/IDeviceLogsService.java
index f115719..aded702 100644
--- a/src/main/java/com/dji/sample/manage/service/IDeviceLogsService.java
+++ b/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.
diff --git a/src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java b/src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java
index 04296c3..11fd20c 100644
--- a/src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java
+++ b/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);
 }
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/service/IDeviceRedisService.java b/src/main/java/com/dji/sample/manage/service/IDeviceRedisService.java
new file mode 100644
index 0000000..074101a
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/IDeviceRedisService.java
@@ -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);
+}
diff --git a/src/main/java/com/dji/sample/manage/service/IDeviceService.java b/src/main/java/com/dji/sample/manage/service/IDeviceService.java
index 11bb1d7..7cf1a17 100644
--- a/src/main/java/com/dji/sample/manage/service/IDeviceService.java
+++ b/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);
+
 }
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/service/IWorkspaceService.java b/src/main/java/com/dji/sample/manage/service/IWorkspaceService.java
index 2d80777..306760d 100644
--- a/src/main/java/com/dji/sample/manage/service/IWorkspaceService.java
+++ b/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);
diff --git a/src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java
index 926fa36..ec8ac77 100644
--- a/src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java
+++ b/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
diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceFirmwareServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceFirmwareServiceImpl.java
index 6b99d38..1747040 100644
--- a/src/main/java/com/dji/sample/manage/service/impl/DeviceFirmwareServiceImpl.java
+++ b/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
diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceHmsServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceHmsServiceImpl.java
index de1d03f..e201155 100644
--- a/src/main/java/com/dji/sample/manage/service/impl/DeviceHmsServiceImpl.java
+++ b/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;
         }
diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceLogsServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceLogsServiceImpl.java
index b94a0ad..a5aca9a 100644
--- a/src/main/java/com/dji/sample/manage/service/impl/DeviceLogsServiceImpl.java
+++ b/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();
     }
diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java
index b540e08..e5a596e 100644
--- a/src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java
+++ b/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()));
 
diff --git a/src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java
index 89a7308..04169bd 100644
--- a/src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java
+++ b/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();
     }
 
diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceRedisServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceRedisServiceImpl.java
new file mode 100644
index 0000000..4629892
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/impl/DeviceRedisServiceImpl.java
@@ -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);
+    }
+}
diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java
index 6677ca8..809fd7f 100644
--- a/src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java
+++ b/src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java
@@ -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();
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/service/impl/DockOSDServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DockOSDServiceImpl.java
index 36cd9b3..afb9fc0 100644
--- a/src/main/java/com/dji/sample/manage/service/impl/DockOSDServiceImpl.java
+++ b/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);
+            }
         }
     }
 }
diff --git a/src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java
index 8b441ba..5d2c8cd 100644
--- a/src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java
+++ b/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);
     }
 
 }
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java b/src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java
index 8afaf16..1970479 100644
--- a/src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java
+++ b/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;
     }
diff --git a/src/main/java/com/dji/sample/media/service/IMediaService.java b/src/main/java/com/dji/sample/media/service/IMediaService.java
index d1b5021..15a80cc 100644
--- a/src/main/java/com/dji/sample/media/service/IMediaService.java
+++ b/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);
 }
diff --git a/src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java b/src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java
index 8265d48..5a75fa0 100644
--- a/src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java
+++ b/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;
     }
 }
diff --git a/src/main/java/com/dji/sample/wayline/controller/WaylineJobController.java b/src/main/java/com/dji/sample/wayline/controller/WaylineJobController.java
index f24bdb2..481b6a2 100644
--- a/src/main/java/com/dji/sample/wayline/controller/WaylineJobController.java
+++ b/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();
+    }
 }
diff --git a/src/main/java/com/dji/sample/wayline/model/dto/ConditionalWaylineJobKey.java b/src/main/java/com/dji/sample/wayline/model/dto/ConditionalWaylineJobKey.java
new file mode 100644
index 0000000..2dd1ca2
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/model/dto/ConditionalWaylineJobKey.java
@@ -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);
+    }
+}
diff --git a/src/main/java/com/dji/sample/wayline/model/dto/KmzFileProperties.java b/src/main/java/com/dji/sample/wayline/model/dto/KmzFileProperties.java
index 64e29a2..f729961 100644
--- a/src/main/java/com/dji/sample/wayline/model/dto/KmzFileProperties.java
+++ b/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";
 }
diff --git a/src/main/java/com/dji/sample/wayline/model/dto/WaylineJobDTO.java b/src/main/java/com/dji/sample/wayline/model/dto/WaylineJobDTO.java
index b797819..718923b 100644
--- a/src/main/java/com/dji/sample/wayline/model/dto/WaylineJobDTO.java
+++ b/src/main/java/com/dji/sample/wayline/model/dto/WaylineJobDTO.java
@@ -62,5 +62,7 @@
 
     private Boolean uploading;
 
+    private WaylineTaskConditionDTO conditions;
+
     private String parentId;
 }
diff --git a/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskConditionDTO.java b/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskConditionDTO.java
new file mode 100644
index 0000000..6a1dc18
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskConditionDTO.java
@@ -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;
+}
diff --git a/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskCreateDTO.java b/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskCreateDTO.java
index b920530..3297a08 100644
--- a/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskCreateDTO.java
+++ b/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskCreateDTO.java
@@ -30,4 +30,7 @@
 
     private Integer outOfControlAction;
 
+    private WaylineTaskReadyConditionDTO readyConditions;
+
+    private WaylineTaskExecutableConditionDTO executableConditions;
 }
diff --git a/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskExecutableConditionDTO.java b/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskExecutableConditionDTO.java
new file mode 100644
index 0000000..24cfc4c
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskExecutableConditionDTO.java
@@ -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;
+}
diff --git a/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskReadyConditionDTO.java b/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskReadyConditionDTO.java
new file mode 100644
index 0000000..61fe5a7
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskReadyConditionDTO.java
@@ -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;
+}
diff --git a/src/main/java/com/dji/sample/wayline/model/enums/WaylineErrorCodeEnum.java b/src/main/java/com/dji/sample/wayline/model/enums/WaylineErrorCodeEnum.java
new file mode 100644
index 0000000..8643380
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/model/enums/WaylineErrorCodeEnum.java
@@ -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);
+    }
+}
diff --git a/src/main/java/com/dji/sample/wayline/model/enums/WaylineMethodEnum.java b/src/main/java/com/dji/sample/wayline/model/enums/WaylineMethodEnum.java
index 4951d2c..6ad9807 100644
--- a/src/main/java/com/dji/sample/wayline/model/enums/WaylineMethodEnum.java
+++ b/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;
 
diff --git a/src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskStatusEnum.java b/src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskStatusEnum.java
new file mode 100644
index 0000000..9bdf4f6
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskStatusEnum.java
@@ -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();
+    }
+
+}
diff --git a/src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskTypeEnum.java b/src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskTypeEnum.java
index 89e556a..db81356 100644
--- a/src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskTypeEnum.java
+++ b/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();
+    }
 }
diff --git a/src/main/java/com/dji/sample/wayline/model/enums/WaylineTemplateTypeEnum.java b/src/main/java/com/dji/sample/wayline/model/enums/WaylineTemplateTypeEnum.java
index 2388e32..760fc8d 100644
--- a/src/main/java/com/dji/sample/wayline/model/enums/WaylineTemplateTypeEnum.java
+++ b/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();
     }
 }
diff --git a/src/main/java/com/dji/sample/wayline/model/param/CreateJobParam.java b/src/main/java/com/dji/sample/wayline/model/param/CreateJobParam.java
index f8fe651..558a9d2 100644
--- a/src/main/java/com/dji/sample/wayline/model/param/CreateJobParam.java
+++ b/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;
 }
diff --git a/src/main/java/com/dji/sample/wayline/model/param/UpdateJobParam.java b/src/main/java/com/dji/sample/wayline/model/param/UpdateJobParam.java
new file mode 100644
index 0000000..dfa8e37
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/model/param/UpdateJobParam.java
@@ -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;
+
+}
diff --git a/src/main/java/com/dji/sample/wayline/service/IFlightTaskService.java b/src/main/java/com/dji/sample/wayline/service/IFlightTaskService.java
index 8bc6e63..1b0f4e2 100644
--- a/src/main/java/com/dji/sample/wayline/service/IFlightTaskService.java
+++ b/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);
+
 }
diff --git a/src/main/java/com/dji/sample/wayline/service/IWaylineJobService.java b/src/main/java/com/dji/sample/wayline/service/IWaylineJobService.java
index a18da9f..608c93f 100644
--- a/src/main/java/com/dji/sample/wayline/service/IWaylineJobService.java
+++ b/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);
 }
diff --git a/src/main/java/com/dji/sample/wayline/service/IWaylineRedisService.java b/src/main/java/com/dji/sample/wayline/service/IWaylineRedisService.java
new file mode 100644
index 0000000..fa2b99b
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/service/IWaylineRedisService.java
@@ -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);
+}
diff --git a/src/main/java/com/dji/sample/wayline/service/impl/FlightTaskServiceImpl.java b/src/main/java/com/dji/sample/wayline/service/impl/FlightTaskServiceImpl.java
index 224602d..9418b94 100644
--- a/src/main/java/com/dji/sample/wayline/service/impl/FlightTaskServiceImpl.java
+++ b/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);
+    }
 }
diff --git a/src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java b/src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java
index f540b66..bcea104 100644
--- a/src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java
+++ b/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());
             }
 
diff --git a/src/main/java/com/dji/sample/wayline/service/impl/WaylineJobServiceImpl.java b/src/main/java/com/dji/sample/wayline/service/impl/WaylineJobServiceImpl.java
index acbeabe..4d4cbb2 100644
--- a/src/main/java/com/dji/sample/wayline/service/impl/WaylineJobServiceImpl.java
+++ b/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) {
diff --git a/src/main/java/com/dji/sample/wayline/service/impl/WaylineRedisServiceImpl.java b/src/main/java/com/dji/sample/wayline/service/impl/WaylineRedisServiceImpl.java
new file mode 100644
index 0000000..b93e155
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/service/impl/WaylineRedisServiceImpl.java
@@ -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());
+    }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 75c4872..a5c937a 100644
--- a/src/main/resources/application.yml
+++ b/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.

--
Gitblit v1.9.3