From f3e92fa13107b569e0aa4d3ffff013fd2983660b Mon Sep 17 00:00:00 2001
From: sean.zhou <sean.zhou@dji.com>
Date: Tue, 25 Apr 2023 20:28:45 +0800
Subject: [PATCH] initial v1.4.0

---
 src/components/GMap.vue                                         |   54 
 src/mqtt/config.ts                                              |   12 
 src/components/task/TaskPanel.vue                               |   59 
 src/components/devices/device-hms/DeviceHmsDrawer.vue           |   16 
 src/components/g-map/use-drone-control.ts                       |   40 
 package-lock.json                                               |  453 +++++++
 src/components/g-map/DockControlPanel.vue                       |   10 
 src/components/g-map/use-mqtt.ts                                |  132 ++
 src/pages/page-web/projects/livestream.vue                      |    5 
 src/pages/page-web/projects/tsa.vue                             |   43 
 src/components/g-map/use-manual-control.ts                      |  162 +++
 src/pages/page-web/projects/dock.vue                            |    2 
 src/types/device.ts                                             |  103 +
 src/mqtt/index.ts                                               |  117 ++
 src/pages/page-web/projects/Firmwares.vue                       |    6 
 src/types/drone-control.ts                                      |   68 +
 src/components/g-map/DroneControlInfoPanel.vue                  |   34 
 src/types/enums.ts                                              |    9 
 src/api/wayline.ts                                              |   23 
 src/types/drc.ts                                                |   71 +
 src/pages/page-web/projects/workspace.vue                       |    9 
 src/shims-mqtt.d.ts                                             |    4 
 src/api/drone-control/drone.ts                                  |   58 +
 src/components/g-map/DroneControlPanel.vue                      |  756 ++++++++++++++
 src/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue |    2 
 src/components/g-map/use-device-setting.ts                      |    0 
 src/hooks/use-g-map-tsa.ts                                      |    3 
 src/types/task.ts                                               |    6 
 src/api/drone-control/payload.ts                                |   93 +
 src/components/g-map/use-payload-control.ts                     |  120 ++
 src/event-bus/index.ts                                          |    2 
 src/components/g-map/use-dock-control.ts                        |   20 
 src/pages/page-web/projects/devices.vue                         |   17 
 src/store/index.ts                                              |   22 
 src/types/live-stream.ts                                        |   71 +
 src/components/g-map/DeviceSettingBox.vue                       |    4 
 src/components/g-map/use-drone-control-ws-event.ts              |   97 +
 src/components/g-map/use-connect-mqtt.ts                        |   71 +
 src/components/task/CreatePlan.vue                              |    3 
 yarn.lock                                                       |  189 +++
 package.json                                                    |    5 
 src/components/g-map/use-drone-control-mqtt-event.ts            |   61 +
 src/types/device-cmd.ts                                         |   21 
 src/components/g-map/DroneControlPopover.vue                    |  115 ++
 src/api/drc.ts                                                  |   58 +
 45 files changed, 3,048 insertions(+), 178 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index f3082b8..47e9d0c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,7 +15,9 @@
         "agora-rtc-sdk-ng": "^4.12.1",
         "ant-design-vue": "^2.2.8",
         "axios": "^0.21.1",
+        "eventemitter3": "^5.0.0",
         "mitt": "^3.0.0",
+        "mqtt": "^4.3.7",
         "query-string": "^7.0.1",
         "reconnecting-websocket": "^4.4.0",
         "vconsole": "^3.8.1",
@@ -1535,6 +1537,11 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/base64-js": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
+      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
+    },
     "node_modules/big-integer": {
       "version": "1.6.51",
       "resolved": "https://registry.npmmirror.com/big-integer/download/big-integer-1.6.51.tgz",
@@ -1574,6 +1581,16 @@
       "license": "MIT",
       "engines": {
         "node": ">=8"
+      }
+    },
+    "node_modules/bl": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz",
+      "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+      "dependencies": {
+        "buffer": "^5.5.0",
+        "inherits": "^2.0.4",
+        "readable-stream": "^3.4.0"
       }
     },
     "node_modules/bluebird": {
@@ -1634,6 +1651,20 @@
         "type": "opencollective",
         "url": "https://opencollective.com/browserslist"
       }
+    },
+    "node_modules/buffer": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz",
+      "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+      "dependencies": {
+        "base64-js": "^1.3.1",
+        "ieee754": "^1.1.13"
+      }
+    },
+    "node_modules/buffer-from": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz",
+      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
     },
     "node_modules/buffer-indexof-polyfill": {
       "version": "1.0.2",
@@ -2000,6 +2031,15 @@
         "node": ">= 10"
       }
     },
+    "node_modules/commist": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/commist/-/commist-1.1.0.tgz",
+      "integrity": "sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==",
+      "dependencies": {
+        "leven": "^2.1.0",
+        "minimist": "^1.1.0"
+      }
+    },
     "node_modules/component-emitter": {
       "version": "1.3.0",
       "resolved": "https://registry.npm.taobao.org/component-emitter/download/component-emitter-1.3.0.tgz",
@@ -2018,6 +2058,20 @@
       "resolved": "https://registry.npm.taobao.org/concat-map/download/concat-map-0.0.1.tgz",
       "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
       "license": "MIT"
+    },
+    "node_modules/concat-stream": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/concat-stream/-/concat-stream-2.0.0.tgz",
+      "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
+      "engines": [
+        "node >= 6.0"
+      ],
+      "dependencies": {
+        "buffer-from": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^3.0.2",
+        "typedarray": "^0.0.6"
+      }
     },
     "node_modules/constant-case": {
       "version": "3.0.4",
@@ -2467,6 +2521,17 @@
         "safe-buffer": "~5.1.0"
       }
     },
+    "node_modules/duplexify": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmmirror.com/duplexify/-/duplexify-4.1.2.tgz",
+      "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==",
+      "dependencies": {
+        "end-of-stream": "^1.4.1",
+        "inherits": "^2.0.3",
+        "readable-stream": "^3.1.1",
+        "stream-shift": "^1.0.0"
+      }
+    },
     "node_modules/electron-to-chromium": {
       "version": "1.4.43",
       "resolved": "https://registry.npmmirror.com/electron-to-chromium/download/electron-to-chromium-1.4.43.tgz",
@@ -2488,6 +2553,14 @@
       "license": "MIT",
       "engines": {
         "node": ">= 4"
+      }
+    },
+    "node_modules/end-of-stream": {
+      "version": "1.4.4",
+      "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz",
+      "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+      "dependencies": {
+        "once": "^1.4.0"
       }
     },
     "node_modules/enquirer": {
@@ -3421,6 +3494,11 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/eventemitter3": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.0.tgz",
+      "integrity": "sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg=="
+    },
     "node_modules/expand-brackets": {
       "version": "2.1.4",
       "resolved": "https://registry.npm.taobao.org/expand-brackets/download/expand-brackets-2.1.4.tgz",
@@ -3745,7 +3823,6 @@
       "version": "1.0.0",
       "resolved": "https://registry.npm.taobao.org/fs.realpath/download/fs.realpath-1.0.0.tgz",
       "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
-      "dev": true,
       "license": "ISC"
     },
     "node_modules/fsevents": {
@@ -3858,7 +3935,6 @@
       "version": "7.2.0",
       "resolved": "https://registry.npmmirror.com/glob/download/glob-7.2.0.tgz",
       "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
-      "dev": true,
       "license": "ISC",
       "dependencies": {
         "fs.realpath": "^1.0.0",
@@ -4120,6 +4196,15 @@
       "dev": true,
       "license": "0BSD"
     },
+    "node_modules/help-me": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/help-me/-/help-me-3.0.0.tgz",
+      "integrity": "sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==",
+      "dependencies": {
+        "glob": "^7.1.6",
+        "readable-stream": "^3.6.0"
+      }
+    },
     "node_modules/htmlparser2": {
       "version": "3.10.1",
       "resolved": "https://registry.npmmirror.com/htmlparser2/download/htmlparser2-3.10.1.tgz?cache=0&sync_timestamp=1636640933377&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fhtmlparser2%2Fdownload%2Fhtmlparser2-3.10.1.tgz",
@@ -4134,6 +4219,11 @@
         "inherits": "^2.0.1",
         "readable-stream": "^3.1.1"
       }
+    },
+    "node_modules/ieee754": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz",
+      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
     },
     "node_modules/ignore": {
       "version": "5.2.0",
@@ -4196,7 +4286,6 @@
       "version": "1.0.6",
       "resolved": "https://registry.npm.taobao.org/inflight/download/inflight-1.0.6.tgz",
       "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
-      "dev": true,
       "license": "ISC",
       "dependencies": {
         "once": "^1.3.0",
@@ -4207,7 +4296,6 @@
       "version": "2.0.4",
       "resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.4.tgz",
       "integrity": "sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w=",
-      "dev": true,
       "license": "ISC"
     },
     "node_modules/internal-slot": {
@@ -4611,6 +4699,11 @@
       "dev": true,
       "license": "BSD-3-Clause"
     },
+    "node_modules/js-sdsl": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmmirror.com/js-sdsl/-/js-sdsl-4.3.0.tgz",
+      "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ=="
+    },
     "node_modules/js-tokens": {
       "version": "4.0.0",
       "resolved": "https://registry.nlark.com/js-tokens/download/js-tokens-4.0.0.tgz?cache=0&sync_timestamp=1619345098261&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fjs-tokens%2Fdownload%2Fjs-tokens-4.0.0.tgz",
@@ -4688,6 +4781,14 @@
       "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=",
       "dev": true,
       "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/leven": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/leven/-/leven-2.1.0.tgz",
+      "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==",
       "engines": {
         "node": ">=0.10.0"
       }
@@ -4801,7 +4902,6 @@
       "version": "6.0.0",
       "resolved": "https://registry.npm.taobao.org/lru-cache/download/lru-cache-6.0.0.tgz?cache=0&sync_timestamp=1599054167787&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flru-cache%2Fdownload%2Flru-cache-6.0.0.tgz",
       "integrity": "sha1-bW/mVw69lqr5D8rR2vo7JWbbOpQ=",
-      "dev": true,
       "license": "ISC",
       "dependencies": {
         "yallist": "^4.0.0"
@@ -4977,6 +5077,48 @@
       "license": "MIT",
       "engines": {
         "node": "*"
+      }
+    },
+    "node_modules/mqtt": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmmirror.com/mqtt/-/mqtt-4.3.7.tgz",
+      "integrity": "sha512-ew3qwG/TJRorTz47eW46vZ5oBw5MEYbQZVaEji44j5lAUSQSqIEoul7Kua/BatBW0H0kKQcC9kwUHa1qzaWHSw==",
+      "dependencies": {
+        "commist": "^1.0.0",
+        "concat-stream": "^2.0.0",
+        "debug": "^4.1.1",
+        "duplexify": "^4.1.1",
+        "help-me": "^3.0.0",
+        "inherits": "^2.0.3",
+        "lru-cache": "^6.0.0",
+        "minimist": "^1.2.5",
+        "mqtt-packet": "^6.8.0",
+        "number-allocator": "^1.0.9",
+        "pump": "^3.0.0",
+        "readable-stream": "^3.6.0",
+        "reinterval": "^1.1.0",
+        "rfdc": "^1.3.0",
+        "split2": "^3.1.0",
+        "ws": "^7.5.5",
+        "xtend": "^4.0.2"
+      },
+      "bin": {
+        "mqtt": "bin/mqtt.js",
+        "mqtt_pub": "bin/pub.js",
+        "mqtt_sub": "bin/sub.js"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/mqtt-packet": {
+      "version": "6.10.0",
+      "resolved": "https://registry.npmmirror.com/mqtt-packet/-/mqtt-packet-6.10.0.tgz",
+      "integrity": "sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA==",
+      "dependencies": {
+        "bl": "^4.0.2",
+        "debug": "^4.1.1",
+        "process-nextick-args": "^2.0.1"
       }
     },
     "node_modules/ms": {
@@ -5157,6 +5299,15 @@
       },
       "funding": {
         "url": "https://github.com/fb55/nth-check?sponsor=1"
+      }
+    },
+    "node_modules/number-allocator": {
+      "version": "1.0.14",
+      "resolved": "https://registry.npmmirror.com/number-allocator/-/number-allocator-1.0.14.tgz",
+      "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==",
+      "dependencies": {
+        "debug": "^4.3.1",
+        "js-sdsl": "4.3.0"
       }
     },
     "node_modules/object-assign": {
@@ -5374,7 +5525,6 @@
       "version": "1.4.0",
       "resolved": "https://registry.npm.taobao.org/once/download/once-1.4.0.tgz",
       "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
-      "dev": true,
       "license": "ISC",
       "dependencies": {
         "wrappy": "1"
@@ -5525,7 +5675,6 @@
       "version": "1.0.1",
       "resolved": "https://registry.npm.taobao.org/path-is-absolute/download/path-is-absolute-1.0.1.tgz",
       "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=0.10.0"
@@ -5695,7 +5844,6 @@
       "version": "2.0.1",
       "resolved": "https://registry.npm.taobao.org/process-nextick-args/download/process-nextick-args-2.0.1.tgz",
       "integrity": "sha1-eCDZsWEgzFXKmud5JoCufbptf+I=",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/progress": {
@@ -5706,6 +5854,15 @@
       "license": "MIT",
       "engines": {
         "node": ">=0.4.0"
+      }
+    },
+    "node_modules/pump": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.0.tgz",
+      "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+      "dependencies": {
+        "end-of-stream": "^1.1.0",
+        "once": "^1.3.1"
       }
     },
     "node_modules/punycode": {
@@ -5760,7 +5917,6 @@
       "version": "3.6.0",
       "resolved": "https://registry.npm.taobao.org/readable-stream/download/readable-stream-3.6.0.tgz",
       "integrity": "sha1-M3u9o63AcGvT4CRCaihtS0sskZg=",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "inherits": "^2.0.3",
@@ -5873,6 +6029,11 @@
         "url": "https://github.com/sponsors/mysticatea"
       }
     },
+    "node_modules/reinterval": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/reinterval/-/reinterval-1.1.0.tgz",
+      "integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ=="
+    },
     "node_modules/repeat-element": {
       "version": "1.1.4",
       "resolved": "https://registry.nlark.com/repeat-element/download/repeat-element-1.1.4.tgz",
@@ -5962,6 +6123,11 @@
         "iojs": ">=1.0.0",
         "node": ">=0.10.0"
       }
+    },
+    "node_modules/rfdc": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.3.0.tgz",
+      "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA=="
     },
     "node_modules/rimraf": {
       "version": "3.0.2",
@@ -6541,6 +6707,14 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/split2": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmmirror.com/split2/-/split2-3.2.2.tgz",
+      "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==",
+      "dependencies": {
+        "readable-stream": "^3.0.0"
+      }
+    },
     "node_modules/sprintf-js": {
       "version": "1.0.3",
       "resolved": "https://registry.npm.taobao.org/sprintf-js/download/sprintf-js-1.0.3.tgz",
@@ -6649,6 +6823,11 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/stream-shift": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/stream-shift/-/stream-shift-1.0.1.tgz",
+      "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ=="
+    },
     "node_modules/strict-uri-encode": {
       "version": "2.0.0",
       "resolved": "https://registry.npm.taobao.org/strict-uri-encode/download/strict-uri-encode-2.0.0.tgz",
@@ -6662,7 +6841,6 @@
       "version": "1.3.0",
       "resolved": "https://registry.npm.taobao.org/string_decoder/download/string_decoder-1.3.0.tgz",
       "integrity": "sha1-QvEUWUpGzxqOMLCoT1bHjD7awh4=",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "safe-buffer": "~5.2.0"
@@ -6672,7 +6850,6 @@
       "version": "5.2.1",
       "resolved": "https://registry.nlark.com/safe-buffer/download/safe-buffer-5.2.1.tgz",
       "integrity": "sha1-Hq+fqb2x/dTsdfWPnNtOa3gn7sY=",
-      "dev": true,
       "funding": [
         {
           "type": "github",
@@ -7343,6 +7520,11 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/typedarray": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz",
+      "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
+    },
     "node_modules/typescript": {
       "version": "4.5.4",
       "resolved": "https://registry.npmmirror.com/typescript/download/typescript-4.5.4.tgz",
@@ -7577,7 +7759,6 @@
       "version": "1.0.2",
       "resolved": "https://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz",
       "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/v8-compile-cache": {
@@ -7970,14 +8151,40 @@
       "version": "1.0.2",
       "resolved": "https://registry.nlark.com/wrappy/download/wrappy-1.0.2.tgz?cache=0&sync_timestamp=1619133505879&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fwrappy%2Fdownload%2Fwrappy-1.0.2.tgz",
       "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
-      "dev": true,
       "license": "ISC"
+    },
+    "node_modules/ws": {
+      "version": "7.5.9",
+      "resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz",
+      "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+      "engines": {
+        "node": ">=8.3.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": "^5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+      "engines": {
+        "node": ">=0.4"
+      }
     },
     "node_modules/yallist": {
       "version": "4.0.0",
       "resolved": "https://registry.npm.taobao.org/yallist/download/yallist-4.0.0.tgz",
       "integrity": "sha1-m7knkNnA7/7GO+c1GeEaNQGaOnI=",
-      "dev": true,
       "license": "ISC"
     }
   },
@@ -9002,6 +9209,11 @@
         }
       }
     },
+    "base64-js": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
+      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
+    },
     "big-integer": {
       "version": "1.6.51",
       "resolved": "https://registry.npmmirror.com/big-integer/download/big-integer-1.6.51.tgz",
@@ -9029,6 +9241,16 @@
       "resolved": "https://registry.nlark.com/binary-extensions/download/binary-extensions-2.2.0.tgz",
       "integrity": "sha1-dfUC7q+f/eQvyYgpZFvk6na9ni0=",
       "devOptional": true
+    },
+    "bl": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz",
+      "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+      "requires": {
+        "buffer": "^5.5.0",
+        "inherits": "^2.0.4",
+        "readable-stream": "^3.4.0"
+      }
     },
     "bluebird": {
       "version": "3.7.2",
@@ -9070,6 +9292,20 @@
         "node-releases": "^2.0.1",
         "picocolors": "^1.0.0"
       }
+    },
+    "buffer": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz",
+      "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+      "requires": {
+        "base64-js": "^1.3.1",
+        "ieee754": "^1.1.13"
+      }
+    },
+    "buffer-from": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz",
+      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
     },
     "buffer-indexof-polyfill": {
       "version": "1.0.2",
@@ -9354,6 +9590,15 @@
       "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
       "dev": true
     },
+    "commist": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/commist/-/commist-1.1.0.tgz",
+      "integrity": "sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==",
+      "requires": {
+        "leven": "^2.1.0",
+        "minimist": "^1.1.0"
+      }
+    },
     "component-emitter": {
       "version": "1.3.0",
       "resolved": "https://registry.npm.taobao.org/component-emitter/download/component-emitter-1.3.0.tgz",
@@ -9369,6 +9614,17 @@
       "version": "0.0.1",
       "resolved": "https://registry.npm.taobao.org/concat-map/download/concat-map-0.0.1.tgz",
       "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+    },
+    "concat-stream": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/concat-stream/-/concat-stream-2.0.0.tgz",
+      "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^3.0.2",
+        "typedarray": "^0.0.6"
+      }
     },
     "constant-case": {
       "version": "3.0.4",
@@ -9700,6 +9956,17 @@
         }
       }
     },
+    "duplexify": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmmirror.com/duplexify/-/duplexify-4.1.2.tgz",
+      "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==",
+      "requires": {
+        "end-of-stream": "^1.4.1",
+        "inherits": "^2.0.3",
+        "readable-stream": "^3.1.1",
+        "stream-shift": "^1.0.0"
+      }
+    },
     "electron-to-chromium": {
       "version": "1.4.43",
       "resolved": "https://registry.npmmirror.com/electron-to-chromium/download/electron-to-chromium-1.4.43.tgz",
@@ -9716,6 +9983,14 @@
       "resolved": "https://registry.npm.taobao.org/emojis-list/download/emojis-list-3.0.0.tgz",
       "integrity": "sha1-VXBmIEatKeLpFucariYKvf9Pang=",
       "dev": true
+    },
+    "end-of-stream": {
+      "version": "1.4.4",
+      "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz",
+      "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+      "requires": {
+        "once": "^1.4.0"
+      }
     },
     "enquirer": {
       "version": "2.3.6",
@@ -10310,6 +10585,11 @@
       "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
       "dev": true
     },
+    "eventemitter3": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.0.tgz",
+      "integrity": "sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg=="
+    },
     "expand-brackets": {
       "version": "2.1.4",
       "resolved": "https://registry.npm.taobao.org/expand-brackets/download/expand-brackets-2.1.4.tgz",
@@ -10545,8 +10825,7 @@
     "fs.realpath": {
       "version": "1.0.0",
       "resolved": "https://registry.npm.taobao.org/fs.realpath/download/fs.realpath-1.0.0.tgz",
-      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
-      "dev": true
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
     },
     "fsevents": {
       "version": "2.3.2",
@@ -10624,7 +10903,6 @@
       "version": "7.2.0",
       "resolved": "https://registry.npmmirror.com/glob/download/glob-7.2.0.tgz",
       "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
-      "dev": true,
       "requires": {
         "fs.realpath": "^1.0.0",
         "inflight": "^1.0.4",
@@ -10806,6 +11084,15 @@
         }
       }
     },
+    "help-me": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/help-me/-/help-me-3.0.0.tgz",
+      "integrity": "sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==",
+      "requires": {
+        "glob": "^7.1.6",
+        "readable-stream": "^3.6.0"
+      }
+    },
     "htmlparser2": {
       "version": "3.10.1",
       "resolved": "https://registry.npmmirror.com/htmlparser2/download/htmlparser2-3.10.1.tgz?cache=0&sync_timestamp=1636640933377&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fhtmlparser2%2Fdownload%2Fhtmlparser2-3.10.1.tgz",
@@ -10819,6 +11106,11 @@
         "inherits": "^2.0.1",
         "readable-stream": "^3.1.1"
       }
+    },
+    "ieee754": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz",
+      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
     },
     "ignore": {
       "version": "5.2.0",
@@ -10858,7 +11150,6 @@
       "version": "1.0.6",
       "resolved": "https://registry.npm.taobao.org/inflight/download/inflight-1.0.6.tgz",
       "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
-      "dev": true,
       "requires": {
         "once": "^1.3.0",
         "wrappy": "1"
@@ -10867,8 +11158,7 @@
     "inherits": {
       "version": "2.0.4",
       "resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.4.tgz",
-      "integrity": "sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w=",
-      "dev": true
+      "integrity": "sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w="
     },
     "internal-slot": {
       "version": "1.0.3",
@@ -11132,6 +11422,11 @@
       "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==",
       "dev": true
     },
+    "js-sdsl": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmmirror.com/js-sdsl/-/js-sdsl-4.3.0.tgz",
+      "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ=="
+    },
     "js-tokens": {
       "version": "4.0.0",
       "resolved": "https://registry.nlark.com/js-tokens/download/js-tokens-4.0.0.tgz?cache=0&sync_timestamp=1619345098261&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fjs-tokens%2Fdownload%2Fjs-tokens-4.0.0.tgz",
@@ -11187,6 +11482,11 @@
       "resolved": "https://registry.npm.taobao.org/kind-of/download/kind-of-5.1.0.tgz",
       "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=",
       "dev": true
+    },
+    "leven": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/leven/-/leven-2.1.0.tgz",
+      "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA=="
     },
     "levn": {
       "version": "0.4.1",
@@ -11276,7 +11576,6 @@
       "version": "6.0.0",
       "resolved": "https://registry.npm.taobao.org/lru-cache/download/lru-cache-6.0.0.tgz?cache=0&sync_timestamp=1599054167787&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flru-cache%2Fdownload%2Flru-cache-6.0.0.tgz",
       "integrity": "sha1-bW/mVw69lqr5D8rR2vo7JWbbOpQ=",
-      "dev": true,
       "requires": {
         "yallist": "^4.0.0"
       }
@@ -11400,6 +11699,40 @@
       "version": "2.29.1",
       "resolved": "https://registry.npmmirror.com/moment/download/moment-2.29.1.tgz",
       "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
+    },
+    "mqtt": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmmirror.com/mqtt/-/mqtt-4.3.7.tgz",
+      "integrity": "sha512-ew3qwG/TJRorTz47eW46vZ5oBw5MEYbQZVaEji44j5lAUSQSqIEoul7Kua/BatBW0H0kKQcC9kwUHa1qzaWHSw==",
+      "requires": {
+        "commist": "^1.0.0",
+        "concat-stream": "^2.0.0",
+        "debug": "^4.1.1",
+        "duplexify": "^4.1.1",
+        "help-me": "^3.0.0",
+        "inherits": "^2.0.3",
+        "lru-cache": "^6.0.0",
+        "minimist": "^1.2.5",
+        "mqtt-packet": "^6.8.0",
+        "number-allocator": "^1.0.9",
+        "pump": "^3.0.0",
+        "readable-stream": "^3.6.0",
+        "reinterval": "^1.1.0",
+        "rfdc": "^1.3.0",
+        "split2": "^3.1.0",
+        "ws": "^7.5.5",
+        "xtend": "^4.0.2"
+      }
+    },
+    "mqtt-packet": {
+      "version": "6.10.0",
+      "resolved": "https://registry.npmmirror.com/mqtt-packet/-/mqtt-packet-6.10.0.tgz",
+      "integrity": "sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA==",
+      "requires": {
+        "bl": "^4.0.2",
+        "debug": "^4.1.1",
+        "process-nextick-args": "^2.0.1"
+      }
     },
     "ms": {
       "version": "2.1.2",
@@ -11534,6 +11867,15 @@
       "dev": true,
       "requires": {
         "boolbase": "^1.0.0"
+      }
+    },
+    "number-allocator": {
+      "version": "1.0.14",
+      "resolved": "https://registry.npmmirror.com/number-allocator/-/number-allocator-1.0.14.tgz",
+      "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==",
+      "requires": {
+        "debug": "^4.3.1",
+        "js-sdsl": "4.3.0"
       }
     },
     "object-assign": {
@@ -11688,7 +12030,6 @@
       "version": "1.4.0",
       "resolved": "https://registry.npm.taobao.org/once/download/once-1.4.0.tgz",
       "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
-      "dev": true,
       "requires": {
         "wrappy": "1"
       }
@@ -11809,8 +12150,7 @@
     "path-is-absolute": {
       "version": "1.0.1",
       "resolved": "https://registry.npm.taobao.org/path-is-absolute/download/path-is-absolute-1.0.1.tgz",
-      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
-      "dev": true
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
     },
     "path-key": {
       "version": "3.1.1",
@@ -11926,14 +12266,22 @@
     "process-nextick-args": {
       "version": "2.0.1",
       "resolved": "https://registry.npm.taobao.org/process-nextick-args/download/process-nextick-args-2.0.1.tgz",
-      "integrity": "sha1-eCDZsWEgzFXKmud5JoCufbptf+I=",
-      "dev": true
+      "integrity": "sha1-eCDZsWEgzFXKmud5JoCufbptf+I="
     },
     "progress": {
       "version": "2.0.3",
       "resolved": "https://registry.npmmirror.com/progress/download/progress-2.0.3.tgz",
       "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
       "dev": true
+    },
+    "pump": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.0.tgz",
+      "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+      "requires": {
+        "end-of-stream": "^1.1.0",
+        "once": "^1.3.1"
+      }
     },
     "punycode": {
       "version": "2.1.1",
@@ -11961,7 +12309,6 @@
       "version": "3.6.0",
       "resolved": "https://registry.npm.taobao.org/readable-stream/download/readable-stream-3.6.0.tgz",
       "integrity": "sha1-M3u9o63AcGvT4CRCaihtS0sskZg=",
-      "dev": true,
       "requires": {
         "inherits": "^2.0.3",
         "string_decoder": "^1.1.1",
@@ -12039,6 +12386,11 @@
       "integrity": "sha1-BCWido2PI7rXDKS5BGH6LxIT4bI=",
       "dev": true
     },
+    "reinterval": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/reinterval/-/reinterval-1.1.0.tgz",
+      "integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ=="
+    },
     "repeat-element": {
       "version": "1.1.4",
       "resolved": "https://registry.nlark.com/repeat-element/download/repeat-element-1.1.4.tgz",
@@ -12094,6 +12446,11 @@
       "version": "1.0.4",
       "resolved": "https://registry.npm.taobao.org/reusify/download/reusify-1.0.4.tgz",
       "integrity": "sha1-kNo4Kx4SbvwCFG6QhFqI2xKSXXY="
+    },
+    "rfdc": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.3.0.tgz",
+      "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA=="
     },
     "rimraf": {
       "version": "3.0.2",
@@ -12516,6 +12873,14 @@
         }
       }
     },
+    "split2": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmmirror.com/split2/-/split2-3.2.2.tgz",
+      "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==",
+      "requires": {
+        "readable-stream": "^3.0.0"
+      }
+    },
     "sprintf-js": {
       "version": "1.0.3",
       "resolved": "https://registry.npm.taobao.org/sprintf-js/download/sprintf-js-1.0.3.tgz",
@@ -12600,6 +12965,11 @@
         }
       }
     },
+    "stream-shift": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/stream-shift/-/stream-shift-1.0.1.tgz",
+      "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ=="
+    },
     "strict-uri-encode": {
       "version": "2.0.0",
       "resolved": "https://registry.npm.taobao.org/strict-uri-encode/download/strict-uri-encode-2.0.0.tgz",
@@ -12609,7 +12979,6 @@
       "version": "1.3.0",
       "resolved": "https://registry.npm.taobao.org/string_decoder/download/string_decoder-1.3.0.tgz",
       "integrity": "sha1-QvEUWUpGzxqOMLCoT1bHjD7awh4=",
-      "dev": true,
       "requires": {
         "safe-buffer": "~5.2.0"
       },
@@ -12617,8 +12986,7 @@
         "safe-buffer": {
           "version": "5.2.1",
           "resolved": "https://registry.nlark.com/safe-buffer/download/safe-buffer-5.2.1.tgz",
-          "integrity": "sha1-Hq+fqb2x/dTsdfWPnNtOa3gn7sY=",
-          "dev": true
+          "integrity": "sha1-Hq+fqb2x/dTsdfWPnNtOa3gn7sY="
         }
       }
     },
@@ -13093,6 +13461,11 @@
       "integrity": "sha1-G/IH9LKPkVg2ZstfvTJ4hzAc1fQ=",
       "dev": true
     },
+    "typedarray": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz",
+      "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
+    },
     "typescript": {
       "version": "4.5.4",
       "resolved": "https://registry.npmmirror.com/typescript/download/typescript-4.5.4.tgz",
@@ -13282,8 +13655,7 @@
     "util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz",
-      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
-      "dev": true
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
     },
     "v8-compile-cache": {
       "version": "2.3.0",
@@ -13544,14 +13916,23 @@
     "wrappy": {
       "version": "1.0.2",
       "resolved": "https://registry.nlark.com/wrappy/download/wrappy-1.0.2.tgz?cache=0&sync_timestamp=1619133505879&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fwrappy%2Fdownload%2Fwrappy-1.0.2.tgz",
-      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
-      "dev": true
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+    },
+    "ws": {
+      "version": "7.5.9",
+      "resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz",
+      "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+      "requires": {}
+    },
+    "xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
     },
     "yallist": {
       "version": "4.0.0",
       "resolved": "https://registry.npm.taobao.org/yallist/download/yallist-4.0.0.tgz",
-      "integrity": "sha1-m7knkNnA7/7GO+c1GeEaNQGaOnI=",
-      "dev": true
+      "integrity": "sha1-m7knkNnA7/7GO+c1GeEaNQGaOnI="
     }
   }
 }
diff --git a/package.json b/package.json
index 2b5640f..34c4aa0 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,9 @@
     "agora-rtc-sdk-ng": "^4.12.1",
     "ant-design-vue": "^2.2.8",
     "axios": "^0.21.1",
+    "eventemitter3": "^5.0.0",
     "mitt": "^3.0.0",
+    "mqtt": "^4.3.7",
     "query-string": "^7.0.1",
     "reconnecting-websocket": "^4.4.0",
     "vconsole": "^3.8.1",
@@ -97,9 +99,12 @@
         "ant-design-vue/es/tree/style/css",
         "ant-design-vue/es/upload/style/css",
         "axios",
+        "eventemitter3",
         "lodash",
         "mitt",
         "moment",
+        "mqtt",
+        "mqtt/dist/mqtt.min",
         "reconnecting-websocket",
         "vconsole",
         "vue",
diff --git a/src/api/drc.ts b/src/api/drc.ts
new file mode 100644
index 0000000..ea415a9
--- /dev/null
+++ b/src/api/drc.ts
@@ -0,0 +1,58 @@
+import request, { IWorkspaceResponse } from '/@/api/http/request'
+import { ELocalStorageKey } from '/@/types'
+
+// DRC 链路
+const DRC_API_PREFIX = '/control/api/v1'
+const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''
+
+export interface PostDrcBody {
+  client_id?: string // token过期时,用于续期则必填
+  expire_sec?: number // 过期时间,单位秒,默认3600
+}
+
+export interface DrcParams {
+  address: string
+  username: string
+  password: string
+  client_id: string
+  expire_time: number // 过期时间
+  enable_tls: boolean // 是否开启tls
+}
+
+// 获取 mqtt 连接认证
+export async function postDrc (body: PostDrcBody): Promise<IWorkspaceResponse<DrcParams>> {
+  const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/connect`, body)
+  return resp.data
+}
+
+export interface DrcEnterBody {
+  client_id: string
+  dock_sn: string
+  expire_sec?: number // 过期时间,单位秒,默认3600
+  device_info?: {
+    osd_frequency?: number
+    hsi_frequency?: number
+  }
+}
+
+export interface DrcEnterResp {
+  sub: string[] // 需要订阅接收的topic
+  pub: string[] // 推送的topic地址
+}
+
+// 进入飞行控制 (建立drc连接&获取云控控制权)
+export async function postDrcEnter (body: DrcEnterBody): Promise<IWorkspaceResponse<DrcEnterResp>> {
+  const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/enter`, body)
+  return resp.data
+}
+
+export interface DrcExitBody {
+  client_id: string
+  dock_sn: string
+}
+
+// 退出飞行控制 (退出drc连接&退出云控控制权)
+export async function postDrcExit (body: DrcExitBody): Promise<IWorkspaceResponse<null>> {
+  const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/exit`, body)
+  return resp.data
+}
diff --git a/src/api/drone-control/drone.ts b/src/api/drone-control/drone.ts
new file mode 100644
index 0000000..7539a4d
--- /dev/null
+++ b/src/api/drone-control/drone.ts
@@ -0,0 +1,58 @@
+import request, { IWorkspaceResponse } from '/@/api/http/request'
+// import { ELocalStorageKey } from '/@/types'
+
+const API_PREFIX = '/control/api/v1'
+// const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || '
+
+// 获取飞行控制权
+export async function postFlightAuth (sn: string): Promise<IWorkspaceResponse<null>> {
+  const resp = await request.post(`${API_PREFIX}/devices/${sn}/authority/flight`)
+  return resp.data
+}
+export enum WaylineLostControlActionInCommandFlight {
+  CONTINUE = 0,
+  EXEC_LOST_ACTION = 1
+}
+export enum LostControlActionInCommandFLight {
+  HOVER = 0, // 悬停
+  Land = 1, // 着陆
+  RETURN_HOME = 2, // 返航
+}
+export interface PointBody {
+  latitude: number;
+  longitude: number;
+  height: number;
+}
+export interface PostFlyToPointBody {
+  max_speed: number,
+  points: PointBody[]
+}
+
+// 飞向目标点
+export async function postFlyToPoint (sn: string, body: PostFlyToPointBody): Promise<IWorkspaceResponse<null>> {
+  const resp = await request.post(`${API_PREFIX}/devices/${sn}/jobs/fly-to-point`, body)
+  return resp.data
+}
+
+// 停止飞向目标点
+export async function deleteFlyToPoint (sn: string): Promise<IWorkspaceResponse<null>> {
+  const resp = await request.delete(`${API_PREFIX}/devices/${sn}/jobs/fly-to-point`)
+  return resp.data
+}
+
+export interface PostTakeoffToPointBody{
+  target_height: number;
+  target_latitude: number;
+  target_longitude: number;
+  security_takeoff_height: number; // 安全起飞高
+  max_speed: number; // flyto过程中能达到的最大速度, 单位m/s 跟飞机档位有关
+  rc_lost_action: LostControlActionInCommandFLight; // 失控行为
+  rth_altitude: number; // 返航高度
+  exit_wayline_when_rc_lost: WaylineLostControlActionInCommandFlight
+}
+
+// 一键起飞
+export async function postTakeoffToPoint (sn: string, body: PostTakeoffToPointBody): Promise<IWorkspaceResponse<null>> {
+  const resp = await request.post(`${API_PREFIX}/devices/${sn}/jobs/takeoff-to-point`, body)
+  return resp.data
+}
diff --git a/src/api/drone-control/payload.ts b/src/api/drone-control/payload.ts
new file mode 100644
index 0000000..03b89c5
--- /dev/null
+++ b/src/api/drone-control/payload.ts
@@ -0,0 +1,93 @@
+import request, { IWorkspaceResponse } from '/@/api/http/request'
+import { CameraType, CameraMode } from '/@/types/live-stream'
+import { GimbalResetMode } from '/@/types/drone-control'
+// import { ELocalStorageKey } from '/@/types'
+
+const API_PREFIX = '/control/api/v1'
+// const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || '
+
+export interface PostPayloadAuthBody {
+  payload_index: string
+}
+
+// 获取负载控制权
+export async function postPayloadAuth (sn: string, body: PostPayloadAuthBody): Promise<IWorkspaceResponse<null>> {
+  const resp = await request.post(`${API_PREFIX}/devices/${sn}/authority/payload`, body)
+  return resp.data
+}
+
+// TODO: 画面拖动控制
+export enum PayloadCommandsEnum {
+  CameraModeSwitch = 'camera_mode_switch',
+  CameraPhotoTake = 'camera_photo_take',
+  CameraRecordingStart = 'camera_recording_start',
+  CameraRecordingStop = 'camera_recording_stop',
+  CameraFocalLengthSet = 'camera_focal_length_set',
+  GimbalReset = 'gimbal_reset',
+  CameraAim = 'camera_aim'
+}
+
+export interface PostCameraModeBody {
+  payload_index: string
+  camera_mode: CameraMode
+}
+
+export interface PostCameraPhotoBody {
+  payload_index: string
+}
+
+export interface PostCameraRecordingBody {
+  payload_index: string
+}
+
+export interface DeleteCameraRecordingParams {
+  payload_index: string
+}
+
+export interface PostCameraFocalLengthBody {
+  payload_index: string,
+  camera_type: CameraType,
+  zoom_factor: number
+}
+
+export interface PostGimbalResetBody{
+  payload_index: string,
+  reset_mode: GimbalResetMode,
+}
+
+export interface PostCameraAimBody{
+  payload_index: string,
+  camera_type: CameraType,
+  locked: boolean,
+  x: number,
+  y: number,
+}
+
+export type PostPayloadCommandsBody = {
+  cmd: PayloadCommandsEnum.CameraModeSwitch,
+  data: PostCameraModeBody
+} | {
+  cmd: PayloadCommandsEnum.CameraPhotoTake,
+  data: PostCameraPhotoBody
+} | {
+  cmd: PayloadCommandsEnum.CameraRecordingStart,
+  data: PostCameraRecordingBody
+} | {
+  cmd: PayloadCommandsEnum.CameraRecordingStop,
+  data: DeleteCameraRecordingParams
+} | {
+  cmd: PayloadCommandsEnum.CameraFocalLengthSet,
+  data: PostCameraFocalLengthBody
+} | {
+  cmd: PayloadCommandsEnum.GimbalReset,
+  data: PostGimbalResetBody
+} | {
+  cmd: PayloadCommandsEnum.CameraAim,
+  data: PostCameraAimBody
+}
+
+// 发送负载名称
+export async function postPayloadCommands (sn: string, body: PostPayloadCommandsBody): Promise<IWorkspaceResponse<null>> {
+  const resp = await request.post(`${API_PREFIX}/devices/${sn}/payload/commands`, body)
+  return resp.data
+}
diff --git a/src/api/wayline.ts b/src/api/wayline.ts
index b7d4987..a917804 100644
--- a/src/api/wayline.ts
+++ b/src/api/wayline.ts
@@ -42,7 +42,8 @@
   dock_sn: string,
   task_type: TaskType, // 任务类型
   wayline_type: WaylineType, // 航线类型
-  execute_time?: number // 执行时间(毫秒)
+  task_days?: number[] // 执行任务的日期(秒)
+  task_periods?: number[][] // 执行任务的时间点(秒)
   rth_altitude: number // 相对机场返航高度 20 - 500
   out_of_control_action: OutOfControlAction // 失控动作
 }
@@ -90,7 +91,7 @@
   job_id: string
 }
 
-// 取消机场任务
+//  删除机场任务
 export async function deleteTask (workspaceId: string, params: DeleteTaskParams): Promise<IWorkspaceResponse<{}>> {
   const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs`
   const result = await request.delete(url, {
@@ -99,6 +100,24 @@
   return result.data
 }
 
+export enum UpdateTaskStatus {
+  Suspend = 0, // 暂停
+  Resume = 1, // 恢复
+}
+export interface UpdateTaskStatusBody {
+  job_id: string
+  status: UpdateTaskStatus
+}
+
+// 更新机场任务状态
+export async function updateTaskStatus (workspaceId: string, body: UpdateTaskStatusBody): Promise<IWorkspaceResponse<{}>> {
+  const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs/${body.job_id}`
+  const result = await request.put(url, {
+    status: body.status
+  })
+  return result.data
+}
+
 // Upload Wayline file
 export const importKmzFile = async function (workspaceId: string, file: {}): Promise<IWorkspaceResponse<any>> {
   const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/waylines/file/upload`
diff --git a/src/components/GMap.vue b/src/components/GMap.vue
index cb8653e..1a99fcc 100644
--- a/src/components/GMap.vue
+++ b/src/components/GMap.vue
@@ -1,8 +1,10 @@
 <template>
   <div class="g-map-wrapper">
+    <!-- 地图区域 -->
     <div id="g-container" :style="{ width: '100%', height: '100%' }" />
+    <!-- 绘制面板 -->
     <div
-      class="g-action-panle"
+      class="g-action-panel"
       :style="{ right: drawVisible ? '316px' : '16px' }"
     >
       <div :class="state.currentType === 'pin' ? 'g-action-item selection' : 'g-action-item'" @click="draw('pin', true)">
@@ -18,6 +20,7 @@
         <a style="color: red;"><CloseOutlined /></a>
       </div>
     </div>
+    <!-- 飞机OSD -->
     <div v-if="osdVisible.visible && !osdVisible.is_dock" class="osd-panel fz12">
       <div class="pl5 pr5 flex-align-center flex-row flex-justify-between" style="border-bottom: 1px solid #515151; height: 18%;">
         <span>{{ osdVisible.callsign }}</span>
@@ -270,12 +273,13 @@
             </a-row>
             <a-row class="p5">
               <a-col span="24">
-                <a-button type="primary" :disabled="controlPanelVisible" size="small" @click="setControlPanelVisible(true)">
-                  设备操作
+                <a-button type="primary" :disabled="dockControlPanelVisible" size="small" @click="setDockControlPanelVisible(true)">
+                  Actions
                 </a-button>
               </a-col>
             </a-row>
-            <DockControlPanel v-if="controlPanelVisible" :sn="osdVisible.gateway_sn"  :deviceInfo="deviceInfo" @close-control-panel="dockDebugOnOff">
+            <!-- 机场控制面板 -->
+            <DockControlPanel v-if="dockControlPanelVisible" :sn="osdVisible.gateway_sn"  :deviceInfo="deviceInfo" @close-control-panel="onCloseControlPanel">
             </DockControlPanel>
         </div>
       </div>
@@ -406,7 +410,8 @@
           {{ 10 > (deviceInfo.device.battery.remain_flight_time % 60) ? '0' : ''}}{{deviceInfo.device.battery.remain_flight_time % 60 }}
         </div>
       </div>
-
+      <!-- 飞行指令 -->
+      <DroneControlPanel :sn="osdVisible.gateway_sn" :deviceInfo="deviceInfo" :payloads="osdVisible.payloads"></DroneControlPanel>
     </div>
   </div>
 </template>
@@ -444,7 +449,9 @@
 } from '@ant-design/icons-vue'
 import { EDeviceTypeName } from '../types'
 import DockControlPanel from './g-map/DockControlPanel.vue'
-import { useDockControl } from './g-map/useDockControl'
+import { useDockControl } from './g-map/use-dock-control'
+import DroneControlPanel from './g-map/DroneControlPanel.vue'
+import { useConnectMqtt } from './g-map/use-connect-mqtt'
 
 export default defineComponent({
   components: {
@@ -466,6 +473,7 @@
     ArrowUpOutlined,
     ArrowDownOutlined,
     DockControlPanel,
+    DroneControlPanel,
     CarryOutOutlined,
     RocketOutlined
   },
@@ -567,7 +575,7 @@
         deviceTsaUpdateHook.initMarker(EDeviceTypeName.Dock, [EDeviceTypeName.Dock], data.currentSn, data.dockInfo[data.currentSn].basic_osd?.longitude, data.dockInfo[data.currentSn].basic_osd?.latitude)
         if (osdVisible.value.visible && osdVisible.value.is_dock && osdVisible.value.gateway_sn !== '') {
           deviceInfo.dock = data.dockInfo[osdVisible.value.gateway_sn]
-          deviceInfo.device = data.deviceInfo[deviceInfo.dock.basic_osd?.sub_device?.device_sn ?? osdVisible.value.sn]
+          deviceInfo.device = data.deviceInfo[deviceInfo.dock.basic_osd.sub_device?.device_sn ?? osdVisible.value.sn]
         }
       }
     }, {
@@ -634,21 +642,21 @@
       mouseMode.value = bool
     }
 
-    // dock 控制指令
+    // dock 控制面板
     const {
-      controlPanelVisible,
-      setControlPanelVisible,
-      sendDockControlCmd,
-      dockDebugOnOff,
+      dockControlPanelVisible,
+      setDockControlPanelVisible,
+      onCloseControlPanel,
     } = useDockControl()
+
+    // 连接或断开drc
+    useConnectMqtt()
 
     onMounted(() => {
       const app = getApp()
       useGMapManageHook.globalPropertiesConfig(app)
-      setInterval(() => {
-        console.info(deviceInfo.dock)
-      }, 1000)
     })
+
     function getDrawCallback ({ obj }) {
       switch (state.currentType) {
         case MapDoodleEnum.PIN:
@@ -816,9 +824,9 @@
       EModeCode,
       str,
       EDockModeCode,
-      controlPanelVisible,
-      dockDebugOnOff,
-      setControlPanelVisible,
+      dockControlPanelVisible,
+      setDockControlPanelVisible,
+      onCloseControlPanel,
       NetworkStateTypeEnum,
       NetworkStateQualityEnum,
       RainfallEnum,
@@ -834,7 +842,7 @@
   height: 100%;
   width: 100%;
 
-  .g-action-panle {
+  .g-action-panel {
     position: absolute;
     top: 16px;
     right: 16px;
@@ -870,12 +878,12 @@
   left: 10px;
   top: 10px;
   width: 480px;
-  background: black;
-  color: white;
+  background: #000;
+  color: #fff;
   border-radius: 2px;
-  opacity: 0.7;
+  opacity: 0.8;
 }
-.osd > div {
+.osd > div:not(.dock-control-panel) {
   margin-top: 5px;
   padding-left: 5px;
 }
diff --git a/src/components/devices/device-hms/DeviceHmsDrawer.vue b/src/components/devices/device-hms/DeviceHmsDrawer.vue
index 7ff68fc..808b7cb 100644
--- a/src/components/devices/device-hms/DeviceHmsDrawer.vue
+++ b/src/components/devices/device-hms/DeviceHmsDrawer.vue
@@ -67,7 +67,12 @@
         </template>
         <template v-for="col in ['code', 'message']" #[col]="{ text }" :key="col">
           <a-tooltip :title="text">
-              <span>{{ text }}</span>
+              <div >{{ text }}</div>
+          </a-tooltip>
+        </template>
+        <template #domain="{text}">
+          <a-tooltip :title="EDeviceTypeName[text]">
+              <div >{{ EDeviceTypeName[text] }}</div>
           </a-tooltip>
         </template>
       </a-table>
@@ -77,7 +82,7 @@
 
 <!-- 暂时只抽取该组件 -->
 <script lang="ts" setup>
-import { watchEffect, reactive, ref, defineProps, defineEmits } from 'vue'
+import { watchEffect, reactive, ref, defineProps, defineEmits, watch } from 'vue'
 import { getDeviceHms, HmsQueryBody } from '/@/api/manage'
 import moment, { Moment } from 'moment'
 import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface'
@@ -95,7 +100,7 @@
 // 健康状态
 const sVisible = ref(false)
 
-watchEffect(() => {
+watch(props, () => {
   sVisible.value = props.visible
   // 显示弹框时,获取设备hms信息
   if (props.visible) {
@@ -117,9 +122,10 @@
 const hmsColumns: ColumnProps[] = [
   { title: 'Alarm Begin | End Time', dataIndex: 'create_time', width: '25%', className: 'titleStyle', slots: { customRender: 'time' } },
   { title: 'Level', dataIndex: 'level', width: '120px', className: 'titleStyle', slots: { customRender: 'level' } },
-  { title: 'Device', dataIndex: 'domain', width: '12%', className: 'titleStyle' },
-  { title: 'Error Code', dataIndex: 'key', width: '20%', className: 'titleStyle', slots: { customRender: 'code' } },
+  { title: 'Device', dataIndex: 'domain', width: '12%', className: 'titleStyle', slots: { customRender: 'domain' } },
+  { title: 'Error Code', dataIndex: 'key', width: '20%', className: 'titleStyle', ellipsis: true, slots: { customRender: 'code' } },
   { title: 'Hms Message', dataIndex: 'message_en', className: 'titleStyle', ellipsis: true, slots: { customRender: 'message' } },
+  { title: 'Hms Message', dataIndex: 'message_zh', className: 'titleStyle', ellipsis: true, slots: { customRender: 'message' } },
 ]
 
 interface DeviceHmsData {
diff --git a/src/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue b/src/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue
index 84b0ad8..cbd5d61 100644
--- a/src/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue
+++ b/src/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue
@@ -13,7 +13,7 @@
   </span>
   <!-- 进度 -->
   <span v-if="device.firmware_status === DeviceFirmwareStatusEnum.DuringUpgrade">
-  {{ `${device.firmware_progress}%`}}
+  {{ `${device.firmware_progress}`}}
   </span>
 </div>
 </template>
diff --git a/src/components/g-map/DeviceSettingBox.vue b/src/components/g-map/DeviceSettingBox.vue
index 0f7d508..5f6f330 100644
--- a/src/components/g-map/DeviceSettingBox.vue
+++ b/src/components/g-map/DeviceSettingBox.vue
@@ -1,6 +1,6 @@
 <template>
   <div class="device-setting-wrapper">
-    <div class="device-setting-header">设备属性设置</div>
+    <div class="device-setting-header">Device Property Set</div>
     <div class="device-setting-box">
       <!-- 飞行器夜航灯 -->
       <div class="control-setting-item">
@@ -154,7 +154,7 @@
 import { cloneDeep } from 'lodash'
 import { initDeviceSetting, initDeviceSettingFormModel, DeviceSettingKeyEnum } from '/@/types/device-setting'
 import { updateDeviceSettingInfoByOsd, updateDeviceSettingFormModelByOsd } from '/@/utils/device-setting'
-import { useDeviceSetting } from './useDeviceSetting'
+import { useDeviceSetting } from './use-device-setting'
 import DeviceSettingPopover from './DeviceSettingPopover.vue'
 
 const props = defineProps<{
diff --git a/src/components/g-map/DockControlPanel.vue b/src/components/g-map/DockControlPanel.vue
index 93e5628..ade675b 100644
--- a/src/components/g-map/DockControlPanel.vue
+++ b/src/components/g-map/DockControlPanel.vue
@@ -2,7 +2,7 @@
 <div class="dock-control-panel">
   <!-- title -->
   <div class="dock-control-panel-header fz16 pl5 pr5 flex-align-center flex-row flex-justify-between">
-    <span>设备操作 {{ props.sn}}</span>
+    <span>Device Control<span class="fz12 pl15">{{ props.sn}}</span></span>
     <span @click="closeControlPanel">
     <CloseOutlined />
     </span>
@@ -12,7 +12,7 @@
   <!-- cmd -->
   <div class="control-cmd-wrapper">
     <div class="control-cmd-header">
-      远程调试
+      Device Remote Debug
       <a-switch class="debug-btn" checked-children="开" un-checked-children="关" v-model:checked="debugStatus" @change="onDeviceStatusChange"/>
     </div>
     <div class="control-cmd-box">
@@ -28,7 +28,7 @@
         </div>
       </div>
     </div>
- </div>
+  </div>
 </div>
 
 </template>
@@ -38,7 +38,7 @@
 import {
   CloseOutlined
 } from '@ant-design/icons-vue'
-import { useDockControl } from './useDockControl'
+import { useDockControl } from './use-dock-control'
 import { DeviceInfoType, EDockModeCode } from '/@/types/device'
 import { cmdList as baseCmdList, DeviceCmdItem } from '/@/types/device-cmd'
 import { useMyStore } from '/@/store'
@@ -76,7 +76,7 @@
 const emit = defineEmits(['close-control-panel'])
 
 function closeControlPanel () {
-  emit('close-control-panel', props.sn, false)
+  emit('close-control-panel', props.sn)
 }
 
 // dock 控制指令
diff --git a/src/components/g-map/DroneControlInfoPanel.vue b/src/components/g-map/DroneControlInfoPanel.vue
new file mode 100644
index 0000000..4b10ec5
--- /dev/null
+++ b/src/components/g-map/DroneControlInfoPanel.vue
@@ -0,0 +1,34 @@
+<template>
+<div class="drone-control-info-wrap">
+  <a-textarea v-model:value="info" placeholder="drc info" :rows="5" disabled/>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { ref, defineProps, watch } from 'vue'
+
+const props = defineProps<{
+    message?: string,
+}>()
+
+const info = ref('')
+watch(() => props.message, message => {
+  info.value = message || ''
+}, {
+  immediate: true
+})
+
+// const emit = defineEmits(['cancel', 'confirm'])
+</script>
+
+<style lang="scss" scoped>
+.drone-control-info-wrap {
+  &::v-deep{
+    textarea.ant-input {
+      background-color: #000;
+      color: #fff;
+      white-space: pre-wrap;
+    }
+  }
+}
+</style>
diff --git a/src/components/g-map/DroneControlPanel.vue b/src/components/g-map/DroneControlPanel.vue
new file mode 100644
index 0000000..af2311f
--- /dev/null
+++ b/src/components/g-map/DroneControlPanel.vue
@@ -0,0 +1,756 @@
+<template>
+  <div class="drone-control-wrapper">
+    <div class="drone-control-header">Drone Flight Control</div>
+    <div class="drone-control-box">
+      <div class="box">
+        <div class="row">
+          <div class="drone-control"><Button :ghost="!flightController" size="small"  @click="onClickFightControl">{{ flightController ? 'Exit Remote Control' : 'Enter Remote Control'}}</Button></div>
+        </div>
+        <div class="row">
+          <div class="drone-control-direction">
+            <Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_Q)" @onmouseup="onMouseUp">
+              <template #icon><UndoOutlined /></template><span class="word">Q</span>
+            </Button>
+            <Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_W)" @onmouseup="onMouseUp">
+              <template #icon><UpOutlined/></template><span class="word">W</span>
+            </Button>
+            <Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_E)" @onmouseup="onMouseUp">
+              <template #icon><RedoOutlined /></template><span class="word">E</span>
+            </Button>
+            <Button size="small" ghost @mousedown="onMouseDown(KeyCode.ARROW_UP)" @onmouseup="onMouseUp">
+              <template #icon><ArrowUpOutlined /></template>
+            </Button>
+            <br />
+            <Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_A)" @onmouseup="onMouseUp">
+              <template #icon><LeftOutlined/></template><span class="word">A</span>
+            </Button>
+            <Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_S)" @onmouseup="onMouseUp">
+              <template #icon><DownOutlined/></template><span class="word">S</span>
+            </Button>
+            <Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_D)" @onmouseup="onMouseUp">
+              <template #icon><RightOutlined/></template><span class="word">D</span>
+            </Button>
+            <Button size="small" ghost @mousedown="onMouseDown(KeyCode.ARROW_DOWN)" @onmouseup="onMouseUp">
+              <template #icon><ArrowDownOutlined /></template>
+            </Button>
+          </div>
+          <Button type="primary" size="small" danger ghost @click="handleEmergencyStop" >
+            <template #icon><PauseCircleOutlined/></template><span>Break</span>
+          </Button>
+        </div>
+        <div class="row">
+          <DroneControlPopover
+            :visible="flyToPointPopoverData.visible"
+            :loading="flyToPointPopoverData.loading"
+            @confirm="($event) => onFlyToConfirm(true)"
+            @cancel="($event) =>onFlyToConfirm(false)"
+          >
+            <template #formContent>
+              <div class="form-content">
+                <div>
+                  <span class="form-label">latitude:</span>
+                  <a-input-number v-model:value="flyToPointPopoverData.latitude"/>
+                </div>
+                <div>
+                  <span class="form-label">longitude:</span>
+                  <a-input-number v-model:value="flyToPointPopoverData.longitude"/>
+                </div>
+                <div>
+                  <span class="form-label">height(m):</span>
+                  <a-input-number v-model:value="flyToPointPopoverData.height"/>
+                </div>
+              </div>
+            </template>
+            <Button size="small" ghost @click="onShowFlyToPopover" >
+              <span>Fly to</span>
+            </Button>
+          </DroneControlPopover>
+          <Button size="small" ghost @click="onStopFlyToPoint" >
+            <span>Stop Fly to</span>
+          </Button>
+          <DroneControlPopover
+            :visible="takeoffToPointPopoverData.visible"
+            :loading="takeoffToPointPopoverData.loading"
+            @confirm="($event) => onTakeoffToPointConfirm(true)"
+            @cancel="($event) =>onTakeoffToPointConfirm(false)"
+          >
+            <template #formContent>
+              <div class="form-content">
+                <div>
+                  <span class="form-label">latitude:</span>
+                  <a-input-number v-model:value="takeoffToPointPopoverData.latitude"/>
+                </div>
+                <div>
+                  <span class="form-label">longitude:</span>
+                  <a-input-number v-model:value="takeoffToPointPopoverData.longitude"/>
+                </div>
+                <div>
+                  <span class="form-label">height(m):</span>
+                  <a-input-number v-model:value="takeoffToPointPopoverData.height"/>
+                </div>
+                <div>
+                  <span class="form-label">Safe Takeoff Altitude(m):</span>
+                  <a-input-number v-model:value="takeoffToPointPopoverData.securityTakeoffHeight"/>
+                </div>
+                <div>
+                  <span class="form-label">Return-to-Home Altitude(m):</span>
+                  <a-input-number v-model:value="takeoffToPointPopoverData.rthAltitude"/>
+                </div>
+                <div>
+                  <span class="form-label">Lost Action:</span>
+                  <a-select
+                    v-model:value="takeoffToPointPopoverData.rcLostAction"
+                    style="width: 120px"
+                    :options="LostControlActionInCommandFLightOptions"
+                  ></a-select>
+                </div>
+                <div>
+                  <span class="form-label">Wayline Lost Action:</span>
+                  <a-select
+                    v-model:value="takeoffToPointPopoverData.exitWaylineWhenRcLost"
+                    style="width: 120px"
+                    :options="WaylineLostControlActionInCommandFlightOptions"
+                  ></a-select>
+                </div>
+              </div>
+            </template>
+            <Button size="small" ghost @click="onShowTakeoffToPointPopover" >
+              <span>Take off</span>
+            </Button>
+          </DroneControlPopover>
+          <Button :loading="cmdItem.loading" size="small" ghost @click="sendControlCmd(cmdItem, index)">
+          {{ cmdItem.operateText }}
+          </Button>
+        </div>
+    </div>
+    <div class="box">
+      <div class="row">
+        <Select v-model:value="payloadSelectInfo.value" style="width: 110px; marginRight: 5px" :options="payloadSelectInfo.options" @change="handlePayloadChange"/>
+        <div class="drone-control">
+          <Button type="primary" size="small" @click="onAuthPayload">Payload Control</Button>
+        </div>
+      </div>
+      <div class="row">
+        <DroneControlPopover
+          :visible="gimbalResetPopoverData.visible"
+          :loading="gimbalResetPopoverData.loading"
+          @confirm="($event) => onGimbalResetConfirm(true)"
+          @cancel="($event) =>onGimbalResetConfirm(false)"
+        >
+          <template #formContent>
+            <div class="form-content">
+              <div>
+                <span class="form-label">reset mode:</span>
+                <a-select
+                  v-model:value="gimbalResetPopoverData.resetMode"
+                  style="width: 180px"
+                  :options="GimbalResetModeOptions"
+                ></a-select>
+              </div>
+            </div>
+          </template>
+          <Button size="small" ghost @click="onShowGimbalResetPopover">
+            <span>Gimbal Reset</span>
+          </Button>
+        </DroneControlPopover>
+        <Button size="small" ghost @click="onSwitchCameraMode">
+          <span>Camera Mode Switch</span>
+        </Button>
+      </div>
+      <div class="row">
+        <Button size="small" ghost @click="onStartCameraRecording">
+          <span>Start Recording</span>
+        </Button>
+        <Button size="small" ghost @click="onStopCameraRecording">
+          <span>Stop Recording</span>
+        </Button>
+      </div>
+      <div class="row">
+        <Button size="small" ghost  @click="onTakeCameraPhoto">
+          <span>Take Photo</span>
+        </Button>
+        <DroneControlPopover
+          :visible="zoomFactorPopoverData.visible"
+          :loading="zoomFactorPopoverData.loading"
+          @confirm="($event) => onZoomFactorConfirm(true)"
+          @cancel="($event) =>onZoomFactorConfirm(false)"
+        >
+          <template #formContent>
+            <div class="form-content">
+              <div>
+                <span class="form-label">camera type:</span>
+                <a-select
+                  v-model:value="zoomFactorPopoverData.cameraType"
+                  style="width: 120px"
+                  :options="ZoomCameraTypeOptions"
+                ></a-select>
+              </div>
+              <div>
+                <span class="form-label">zoom factor:</span>
+                <a-input-number v-model:value="zoomFactorPopoverData.zoomFactor" :min="2" :max="200" />
+              </div>
+            </div>
+          </template>
+          <Button size="small" ghost @click="($event) => onShowZoomFactorPopover()">
+            <span class="word" @click=";">Zoom</span>
+          </Button>
+        </DroneControlPopover>
+        <DroneControlPopover
+            :visible="cameraAimPopoverData.visible"
+            :loading="cameraAimPopoverData.loading"
+            @confirm="($event) => onCameraAimConfirm(true)"
+            @cancel="($event) =>onCameraAimConfirm(false)"
+          >
+            <template #formContent>
+              <div class="form-content">
+                <div>
+                  <span class="form-label">camera type:</span>
+                  <a-select
+                    v-model:value="cameraAimPopoverData.cameraType"
+                    style="width: 120px"
+                    :options="CameraTypeOptions"
+                  ></a-select>
+                </div>
+                <div>
+                  <span class="form-label">locked:</span>
+                  <a-switch v-model:checked="cameraAimPopoverData.locked"/>
+                </div>
+                <div>
+                  <span class="form-label">x:</span>
+                  <a-input-number v-model:value="cameraAimPopoverData.x" :min="0" :max="1"/>
+                </div>
+                <div>
+                  <span class="form-label">y:</span>
+                  <a-input-number v-model:value="cameraAimPopoverData.y" :min="0" :max="1"/>
+                </div>
+              </div>
+            </template>
+            <Button size="small" ghost @click="($event) => onShowCameraAimPopover()">
+              <span class="word" @click=";">AIM</span>
+            </Button>
+          </DroneControlPopover>
+      </div>
+    </div>
+    </div>
+    <!-- 信息提示 -->
+    <DroneControlInfoPanel :message="drcInfo"></DroneControlInfoPanel>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { defineProps, reactive, ref, watch, computed, onMounted, watchEffect } from 'vue'
+import { Select, message, Button } from 'ant-design-vue'
+import { PayloadInfo, DeviceInfoType, ControlSource, DeviceOsdCamera, DrcStateEnum } from '/@/types/device'
+import { useMyStore } from '/@/store'
+import { postDrcEnter, postDrcExit } from '/@/api/drc'
+import { useMqtt, DeviceTopicInfo } from './use-mqtt'
+import { DownOutlined, UpOutlined, LeftOutlined, RightOutlined, PauseCircleOutlined, UndoOutlined, RedoOutlined, ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons-vue'
+import { useManualControl, KeyCode } from './use-manual-control'
+import { usePayloadControl } from './use-payload-control'
+import { CameraMode, CameraType, CameraTypeOptions, ZoomCameraTypeOptions, CameraListItem } from '/@/types/live-stream'
+import { useDroneControlWsEvent } from './use-drone-control-ws-event'
+import { useDroneControlMqttEvent } from './use-drone-control-mqtt-event'
+import { postFlightAuth, LostControlActionInCommandFLight, WaylineLostControlActionInCommandFlight } from '/@/api/drone-control/drone'
+import { useDroneControl } from './use-drone-control'
+import { GimbalResetMode, GimbalResetModeOptions, LostControlActionInCommandFLightOptions, WaylineLostControlActionInCommandFlightOptions } from '/@/types/drone-control'
+import DroneControlPopover from './DroneControlPopover.vue'
+import DroneControlInfoPanel from './DroneControlInfoPanel.vue'
+import { noDebugCmdList as baseCmdList, DeviceCmdItem, DeviceCmd } from '/@/types/device-cmd'
+import { useDockControl } from './use-dock-control'
+
+const props = defineProps<{
+  sn: string,
+  deviceInfo: DeviceInfoType,
+  payloads: null | PayloadInfo[]
+}>()
+
+const store = useMyStore()
+const clientId = computed(() => {
+  return store.state.clientId
+})
+
+const initCmdList = baseCmdList.find(item => item.cmdKey === DeviceCmd.ReturnHome)
+const cmdItem = ref(initCmdList)
+
+const {
+  sendDockControlCmd
+} = useDockControl()
+
+async function sendControlCmd (cmdItem: DeviceCmdItem, index: number) {
+  cmdItem.loading = true
+  const result = await sendDockControlCmd({
+    sn: props.sn,
+    cmd: cmdItem.cmdKey,
+    action: cmdItem.action
+  }, false)
+  if (result && flightController.value) {
+    exitFlightCOntrol()
+  }
+  cmdItem.loading = false
+}
+
+const { flyToPoint, stopFlyToPoint, takeoffToPoint } = useDroneControl()
+const MAX_SPEED = 14
+
+const flyToPointPopoverData = reactive({
+  visible: false,
+  loading: false,
+  latitude: null as null | number,
+  longitude: null as null | number,
+  height: null as null | number,
+  maxSpeed: MAX_SPEED,
+})
+
+function onShowFlyToPopover () {
+  flyToPointPopoverData.visible = !flyToPointPopoverData.visible
+  flyToPointPopoverData.loading = false
+  flyToPointPopoverData.latitude = null
+  flyToPointPopoverData.longitude = null
+  flyToPointPopoverData.height = null
+}
+
+async function onFlyToConfirm (confirm: boolean) {
+  if (confirm) {
+    if (!flyToPointPopoverData.height || !flyToPointPopoverData.latitude || !flyToPointPopoverData.longitude) {
+      message.error('Input error')
+      return
+    }
+    try {
+      await flyToPoint(props.sn, {
+        max_speed: flyToPointPopoverData.maxSpeed,
+        points: [
+          {
+            latitude: flyToPointPopoverData.latitude,
+            longitude: flyToPointPopoverData.longitude,
+            height: flyToPointPopoverData.height
+          }
+        ]
+      })
+    } catch (error) {
+    }
+  }
+  flyToPointPopoverData.visible = false
+}
+
+async function onStopFlyToPoint () {
+  await stopFlyToPoint(props.sn)
+}
+
+const takeoffToPointPopoverData = reactive({
+  visible: false,
+  loading: false,
+  latitude: null as null | number,
+  longitude: null as null | number,
+  height: null as null | number,
+  securityTakeoffHeight: null as null | number,
+  maxSpeed: MAX_SPEED,
+  rthAltitude: null as null | number,
+  rcLostAction: LostControlActionInCommandFLight.RETURN_HOME,
+  exitWaylineWhenRcLost: WaylineLostControlActionInCommandFlight.RETURN_HOME
+})
+
+function onShowTakeoffToPointPopover () {
+  takeoffToPointPopoverData.visible = !takeoffToPointPopoverData.visible
+  takeoffToPointPopoverData.loading = false
+  takeoffToPointPopoverData.latitude = null
+  takeoffToPointPopoverData.longitude = null
+  takeoffToPointPopoverData.securityTakeoffHeight = null
+  takeoffToPointPopoverData.rthAltitude = null
+  takeoffToPointPopoverData.rcLostAction = LostControlActionInCommandFLight.RETURN_HOME
+  takeoffToPointPopoverData.exitWaylineWhenRcLost = WaylineLostControlActionInCommandFlight.RETURN_HOME
+}
+
+async function onTakeoffToPointConfirm (confirm: boolean) {
+  if (confirm) {
+    if (!takeoffToPointPopoverData.height ||
+        !takeoffToPointPopoverData.latitude ||
+        !takeoffToPointPopoverData.longitude ||
+        !takeoffToPointPopoverData.securityTakeoffHeight ||
+        !takeoffToPointPopoverData.rthAltitude) {
+      message.error('Input error')
+      return
+    }
+    try {
+      await takeoffToPoint(props.sn, {
+        target_latitude: takeoffToPointPopoverData.latitude,
+        target_longitude: takeoffToPointPopoverData.longitude,
+        target_height: takeoffToPointPopoverData.height,
+        security_takeoff_height: takeoffToPointPopoverData.securityTakeoffHeight,
+        rth_altitude: takeoffToPointPopoverData.rthAltitude,
+        max_speed: takeoffToPointPopoverData.maxSpeed,
+        rc_lost_action: takeoffToPointPopoverData.rcLostAction,
+        exit_wayline_when_rc_lost: takeoffToPointPopoverData.exitWaylineWhenRcLost
+      })
+    } catch (error) {
+    }
+  }
+  takeoffToPointPopoverData.visible = false
+}
+
+const deviceTopicInfo: DeviceTopicInfo = reactive({
+  sn: props.sn,
+  pubTopic: '',
+  subTopic: ''
+})
+
+useMqtt(deviceTopicInfo)
+
+// 飞行控制
+const drcState = computed(() => {
+  return store.state.deviceState?.dockInfo[props.sn]?.link_osd?.drc_state === DrcStateEnum.CONNECTED
+})
+const flightController = ref(drcState.value)
+
+async function onClickFightControl () {
+  if (flightController.value) {
+    exitFlightCOntrol()
+    return
+  }
+  enterFlightControl()
+}
+
+// 进入飞行控制
+async function enterFlightControl () {
+  try {
+    const { code, data } = await postDrcEnter({
+      client_id: clientId.value,
+      dock_sn: props.sn,
+    })
+    if (code === 0) {
+      flightController.value = true
+      if (data.sub && data.sub.length > 0) {
+        deviceTopicInfo.subTopic = data.sub[0]
+      }
+      if (data.pub && data.pub.length > 0) {
+        deviceTopicInfo.pubTopic = data.pub[0]
+      }
+      // 获取飞行控制权
+      if (droneControlSource.value !== ControlSource.A) {
+        await postFlightAuth(props.sn)
+      }
+      message.success('Get flight control successfully')
+    }
+  } catch (error: any) {
+  }
+}
+
+// 退出飞行控制
+async function exitFlightCOntrol () {
+  try {
+    const { code } = await postDrcExit({
+      client_id: clientId.value,
+      dock_sn: props.sn,
+    })
+    if (code === 0) {
+      flightController.value = false
+      deviceTopicInfo.subTopic = ''
+      deviceTopicInfo.pubTopic = ''
+      message.success('Exit flight control')
+    }
+  } catch (error: any) {
+  }
+}
+
+// drc mqtt message
+const { drcInfo } = useDroneControlMqttEvent(props.sn)
+
+const {
+  handleKeyup,
+  handleEmergencyStop,
+  resetControlState,
+} = useManualControl(deviceTopicInfo, flightController)
+
+function onMouseDown (type: KeyCode) {
+  handleKeyup(type)
+}
+
+function onMouseUp () {
+  resetControlState()
+}
+
+// 负载控制
+const payloadSelectInfo = {
+  value: null as any,
+  controlSource: undefined as undefined | ControlSource,
+  options: [] as any,
+  payloadIndex: '' as string,
+  camera: undefined as undefined | DeviceOsdCamera // 当前负载osd信息
+}
+
+const handlePayloadChange = (value: string) => {
+  const payload = props.payloads?.find(item => item.payload_sn === value)
+  if (payload) {
+    payloadSelectInfo.payloadIndex = payload.payload_index || ''
+    payloadSelectInfo.controlSource = payload.control_source
+    payloadSelectInfo.camera = undefined
+  }
+}
+
+// function getCurrentCamera (cameraList: CameraListItem[], cameraIndex?: string):CameraListItem | null {
+//   let camera = null
+//   cameraList.forEach(item => {
+//     if (item.camera_index === cameraIndex) {
+//       camera = item
+//     }
+//   })
+//   return camera
+// }
+
+// const currentCamera = computed(() => {
+//   return getCurrentCamera(props.deviceInfo.dock.basic_osd.live_capacity?.device_list[0]?.camera_list as CameraListItem[], camera_index)
+// })
+// 更新负载信息
+watch(() => props.payloads, (payloads) => {
+  if (payloads && payloads.length > 0) {
+    payloadSelectInfo.value = payloads[0].payload_sn
+    payloadSelectInfo.controlSource = payloads[0].control_source || ControlSource.B
+    payloadSelectInfo.payloadIndex = payloads[0].payload_index || ''
+    payloadSelectInfo.options = payloads.map(item => ({ label: item.payload_name, value: item.payload_sn }))
+    payloadSelectInfo.camera = undefined
+  } else {
+    payloadSelectInfo.value = null
+    payloadSelectInfo.controlSource = undefined
+    payloadSelectInfo.options = []
+    payloadSelectInfo.payloadIndex = ''
+    payloadSelectInfo.camera = undefined
+  }
+}, {
+  immediate: true,
+  deep: true
+})
+watch(() => props.deviceInfo.device, (droneOsd) => {
+  if (droneOsd && droneOsd.cameras) {
+    payloadSelectInfo.camera = droneOsd.cameras.find(item => item.payload_index === payloadSelectInfo.payloadIndex)
+  } else {
+    payloadSelectInfo.camera = undefined
+  }
+}, {
+  immediate: true,
+  deep: true
+})
+
+// ws 消息通知
+const { droneControlSource, payloadControlSource } = useDroneControlWsEvent(props.sn, payloadSelectInfo.value)
+watch(() => payloadControlSource, (controlSource) => {
+  payloadSelectInfo.controlSource = controlSource.value
+}, {
+  immediate: true,
+  deep: true
+})
+const {
+  checkPayloadAuth,
+  authPayload,
+  resetGimbal,
+  switchCameraMode,
+  takeCameraPhoto,
+  startCameraRecording,
+  stopCameraRecording,
+  changeCameraFocalLength,
+  cameraAim,
+} = usePayloadControl()
+
+async function onAuthPayload () {
+  const result = await authPayload(props.sn, payloadSelectInfo.payloadIndex)
+  if (result) {
+    payloadControlSource.value = ControlSource.A
+  }
+}
+
+const gimbalResetPopoverData = reactive({
+  visible: false,
+  loading: false,
+  resetMode: null as null | GimbalResetMode,
+})
+
+function onShowGimbalResetPopover () {
+  gimbalResetPopoverData.visible = !gimbalResetPopoverData.visible
+  gimbalResetPopoverData.loading = false
+  gimbalResetPopoverData.resetMode = null
+}
+
+async function onGimbalResetConfirm (confirm: boolean) {
+  if (confirm) {
+    if (gimbalResetPopoverData.resetMode === null) {
+      message.error('Please select reset mode')
+      return
+    }
+    gimbalResetPopoverData.loading = true
+    try {
+      await resetGimbal(props.sn, {
+        payload_index: payloadSelectInfo.payloadIndex,
+        reset_mode: gimbalResetPopoverData.resetMode
+      })
+    } catch (err) {
+    }
+  }
+  gimbalResetPopoverData.visible = false
+}
+
+async function onSwitchCameraMode () {
+  if (!checkPayloadAuth(payloadSelectInfo.controlSource)) {
+    return
+  }
+  const currentCameraMode = payloadSelectInfo.camera?.camera_mode
+  await switchCameraMode(props.sn, {
+    payload_index: payloadSelectInfo.payloadIndex,
+    camera_mode: currentCameraMode === CameraMode.Photo ? CameraMode.Video : CameraMode.Photo
+  })
+}
+
+async function onTakeCameraPhoto () {
+  if (!checkPayloadAuth(payloadSelectInfo.controlSource)) {
+    return
+  }
+  await takeCameraPhoto(props.sn, payloadSelectInfo.payloadIndex)
+}
+
+async function onStartCameraRecording () {
+  if (!checkPayloadAuth(payloadSelectInfo.controlSource)) {
+    return
+  }
+  await startCameraRecording(props.sn, payloadSelectInfo.payloadIndex)
+}
+
+async function onStopCameraRecording () {
+  if (!checkPayloadAuth(payloadSelectInfo.controlSource)) {
+    return
+  }
+  await stopCameraRecording(props.sn, payloadSelectInfo.payloadIndex)
+}
+
+const zoomFactorPopoverData = reactive({
+  visible: false,
+  loading: false,
+  cameraType: null as null | CameraType,
+  zoomFactor: null as null | number,
+})
+
+function onShowZoomFactorPopover () {
+  zoomFactorPopoverData.visible = !zoomFactorPopoverData.visible
+  zoomFactorPopoverData.loading = false
+  zoomFactorPopoverData.cameraType = null
+  zoomFactorPopoverData.zoomFactor = null
+}
+
+async function onZoomFactorConfirm (confirm: boolean) {
+  if (confirm) {
+    if (!zoomFactorPopoverData.zoomFactor || zoomFactorPopoverData.cameraType === null) {
+      message.error('Please input Zoom Factor')
+      return
+    }
+    zoomFactorPopoverData.loading = true
+    try {
+      await changeCameraFocalLength(props.sn, {
+        payload_index: payloadSelectInfo.payloadIndex,
+        camera_type: zoomFactorPopoverData.cameraType,
+        zoom_factor: zoomFactorPopoverData.zoomFactor
+      })
+    } catch (err) {
+    }
+  }
+  zoomFactorPopoverData.visible = false
+}
+
+const cameraAimPopoverData = reactive({
+  visible: false,
+  loading: false,
+  cameraType: null as null | CameraType,
+  locked: false,
+  x: null as null | number,
+  y: null as null | number,
+})
+
+function onShowCameraAimPopover () {
+  cameraAimPopoverData.visible = !cameraAimPopoverData.visible
+  cameraAimPopoverData.loading = false
+  cameraAimPopoverData.cameraType = null
+  cameraAimPopoverData.locked = false
+  cameraAimPopoverData.x = null
+  cameraAimPopoverData.y = null
+}
+
+async function onCameraAimConfirm (confirm: boolean) {
+  if (confirm) {
+    if (cameraAimPopoverData.cameraType === null || cameraAimPopoverData.x === null || cameraAimPopoverData.y === null) {
+      message.error('Input error')
+      return
+    }
+    try {
+      await cameraAim(props.sn, {
+        payload_index: payloadSelectInfo.payloadIndex,
+        camera_type: cameraAimPopoverData.cameraType,
+        locked: cameraAimPopoverData.locked,
+        x: cameraAimPopoverData.x,
+        y: cameraAimPopoverData.y,
+      })
+    } catch (error) {
+    }
+  }
+  cameraAimPopoverData.visible = false
+}
+</script>
+
+<style lang='scss' scoped>
+.drone-control-wrapper{
+  // border-bottom: 1px solid #515151;
+
+  .drone-control-header{
+    font-size: 14px;
+    font-weight: 600;
+    padding: 10px 10px 0px;
+  }
+
+  .drone-control-box {
+    display: flex;
+    flex-wrap: 1;
+    .box {
+      width: 50%;
+      padding: 5px;
+      border: 0.5px solid rgba(255,255,255,0.3);
+
+      .row {
+        display: flex;
+        flex-wrap: wrap;
+        padding: 2px;
+
+        + .row{
+          margin-bottom: 6px;
+        }
+
+        &::v-deep{
+          .ant-btn{
+            font-size: 12px;
+            padding: 0px 4px;
+            margin-right: 5px;
+          }
+        }
+      }
+
+      .drone-control{
+         &::v-deep{
+
+          .ant-select-single:not(.ant-select-customize-input) .ant-select-selector{
+           padding: 0 2px;
+          }
+        }
+      }
+
+      .drone-control-direction{
+        margin-right: 10px;
+
+        .ant-btn {
+          // padding: 0px 1px;
+          margin-right: 0;
+        }
+
+        .word{
+          width: 12px;
+          margin-left: 2px;
+          font-size: 12px;
+          color: #aaa;
+        }
+      }
+    }
+  }
+}
+</style>
diff --git a/src/components/g-map/DroneControlPopover.vue b/src/components/g-map/DroneControlPopover.vue
new file mode 100644
index 0000000..9948c28
--- /dev/null
+++ b/src/components/g-map/DroneControlPopover.vue
@@ -0,0 +1,115 @@
+<template>
+  <a-popover :visible="state.sVisible"
+             trigger="click"
+             v-bind="$attrs"
+             :overlay-class-name="overlayClassName"
+             placement="bottom"
+             @visibleChange=";"
+             v-on="$attrs">
+    <template #content>
+      <div class="title-content">
+      </div>
+      <slot name="formContent" />
+      <div class="uranus-popconfirm-btns">
+        <a-button size="sm"
+           @click="onCancel">
+           {{ cancelText || 'cancel'}}
+        </a-button>
+        <a-button size="sm"
+          :loading="loading"
+          type="primary"
+          class="confirm-btn"
+          @click="onConfirm">
+          {{ okText || 'ok' }}
+        </a-button>
+      </div>
+    </template>
+    <template v-if="$slots.default">
+      <slot></slot>
+    </template>
+  </a-popover>
+</template>
+
+<script lang="ts" setup>
+import { defineProps, defineEmits, reactive, watch, computed } from 'vue'
+
+const props = defineProps<{
+    visible?: boolean,
+    loading?: Boolean,
+    disabled?: Boolean,
+    title?: String,
+    okText?: String,
+    cancelText?: String,
+    width?: Number,
+}>()
+
+const emit = defineEmits(['cancel', 'confirm'])
+
+const state = reactive({
+  sVisible: false,
+  loading: false,
+})
+
+watch(() => props.visible, (val) => {
+  state.sVisible = val || false
+})
+
+const loading = computed(() => {
+  return props.loading
+})
+const okLabel = computed(() => {
+  return props.loading ? '' : '确定'
+})
+
+const overlayClassName = computed(() => {
+  const classList = ['drone-control-popconfirm']
+  return classList.join(' ')
+})
+
+function onConfirm (e: Event) {
+  if (props.disabled) {
+    return
+  }
+  emit('confirm', e)
+}
+
+function onCancel (e: Event) {
+  state.sVisible = false
+  emit('cancel', e)
+}
+
+</script>
+
+<style lang="scss">
+.drone-control-popconfirm {
+  min-width: 300px;
+
+  .uranus-popconfirm-btns{
+    display: flex;
+    padding: 10px 0px;
+    justify-content: flex-end;
+
+    .confirm-btn{
+      margin-left: 10px;
+    }
+  }
+
+  .form-content{
+    display: flex;
+    flex-direction: column;
+
+    > div {
+      display: flex;
+      margin-bottom: 5px;
+
+      .form-label {
+        flex: 1 0 60px;
+        margin-right: 10px;
+      }
+
+      > div {
+      }
+    }
+  }
+}
+</style>
diff --git a/src/components/g-map/use-connect-mqtt.ts b/src/components/g-map/use-connect-mqtt.ts
new file mode 100644
index 0000000..a032168
--- /dev/null
+++ b/src/components/g-map/use-connect-mqtt.ts
@@ -0,0 +1,71 @@
+
+import {
+  ref,
+  watch,
+  computed,
+  onUnmounted,
+} from 'vue'
+import { useMyStore } from '/@/store'
+import { postDrc } from '/@/api/drc'
+import {
+  UranusMqtt,
+} from '/@/mqtt'
+
+type StatusOptions = {
+  status: 'close';
+  event?: CloseEvent;
+} | {
+  status: 'open';
+  retryCount: number;
+} | {
+  status: 'pending';
+}
+
+export function useConnectMqtt () {
+  const store = useMyStore()
+  const dockOsdVisible = computed(() => {
+    return store.state.osdVisible && store.state.osdVisible.visible && store.state.osdVisible.is_dock
+  })
+  const mqttState = ref<UranusMqtt | null>(null)
+
+  // 监听已打开的设备小窗 窗口数量
+  watch(() => dockOsdVisible.value, async (val) => {
+    // 1.打开小窗
+    // 2.设备拥有飞行控制权
+    // 3.请求建立mqtt连接的认证信息
+    if (val) {
+      if (mqttState.value) return
+      const result = await postDrc({})
+      if (result?.code === 0) {
+        const { address, client_id, username, password, expire_time } = result.data
+        // @TODO: 校验 expire_time
+        mqttState.value = new UranusMqtt(address, {
+          clientId: client_id,
+          username,
+          password,
+        })
+        mqttState.value?.initMqtt()
+        mqttState.value?.on('onStatus', (statusOptions: StatusOptions) => {
+          // @TODO: 异常case
+        })
+
+        store.commit('SET_MQTT_STATE', mqttState.value)
+        store.commit('SET_CLIENT_ID', client_id)
+      }
+      // @TODO: 认证失败case
+      return
+    }
+    // 关闭所有小窗后
+    // 1.销毁mqtt连接重置mqtt状态
+    if (mqttState?.value) {
+      mqttState.value?.destroyed()
+      mqttState.value = null
+      store.commit('SET_MQTT_STATE', null)
+      store.commit('SET_CLIENT_ID', '')
+    }
+  }, { immediate: true })
+
+  onUnmounted(() => {
+    mqttState.value?.destroyed()
+  })
+}
diff --git a/src/components/g-map/useDeviceSetting.ts b/src/components/g-map/use-device-setting.ts
similarity index 100%
rename from src/components/g-map/useDeviceSetting.ts
rename to src/components/g-map/use-device-setting.ts
diff --git a/src/components/g-map/useDockControl.ts b/src/components/g-map/use-dock-control.ts
similarity index 75%
rename from src/components/g-map/useDockControl.ts
rename to src/components/g-map/use-dock-control.ts
index 5e8c38c..3cf9b1a 100644
--- a/src/components/g-map/useDockControl.ts
+++ b/src/components/g-map/use-dock-control.ts
@@ -4,10 +4,10 @@
 import { DeviceCmd, DeviceCmdItemAction } from '/@/types/device-cmd'
 
 export function useDockControl () {
-  const controlPanelVisible = ref(false)
+  const dockControlPanelVisible = ref(false)
 
-  function setControlPanelVisible (visible: boolean) {
-    controlPanelVisible.value = visible
+  function setDockControlPanelVisible (visible: boolean) {
+    dockControlPanelVisible.value = visible
   }
 
   // 远程调试开关
@@ -16,9 +16,6 @@
       sn: sn,
       cmd: on ? DeviceCmd.DebugModeOpen : DeviceCmd.DebugModeClose
     }, false)
-    if (result) {
-      setControlPanelVisible(on)
-    }
     return result
   }
 
@@ -47,10 +44,17 @@
     }
   }
 
+  // 控制面板关闭
+  async function onCloseControlPanel (sn: string) {
+    await dockDebugOnOff(sn, false)
+    setDockControlPanelVisible(false)
+  }
+
   return {
-    controlPanelVisible,
-    setControlPanelVisible,
+    dockControlPanelVisible,
+    setDockControlPanelVisible,
     sendDockControlCmd,
     dockDebugOnOff,
+    onCloseControlPanel,
   }
 }
diff --git a/src/components/g-map/use-drone-control-mqtt-event.ts b/src/components/g-map/use-drone-control-mqtt-event.ts
new file mode 100644
index 0000000..4a3d299
--- /dev/null
+++ b/src/components/g-map/use-drone-control-mqtt-event.ts
@@ -0,0 +1,61 @@
+import { ref, onMounted, onBeforeUnmount } from 'vue'
+import EventBus from '/@/event-bus/'
+import {
+  DRC_METHOD,
+  DRCHsiInfo,
+  DRCOsdInfo,
+  DRCDelayTimeInfo,
+} from '/@/types/drc'
+
+export function useDroneControlMqttEvent (sn: string) {
+  const drcInfo = ref('')
+  const hsiInfo = ref('')
+  const osdInfo = ref('')
+  const delayInfo = ref('')
+
+  function handleHsiInfo (data: DRCHsiInfo) {
+    hsiInfo.value = `method: ${DRC_METHOD.HSI_INFO_PUSH}\r\n ${JSON.stringify(data)}\r\n `
+  }
+
+  function handleOsdInfo (data: DRCOsdInfo) {
+    osdInfo.value = `method: ${DRC_METHOD.OSD_INFO_PUSH}\r\n ${JSON.stringify(data)}\r\n `
+  }
+
+  function handleDelayTimeInfo (data: DRCDelayTimeInfo) {
+    delayInfo.value = `method: ${DRC_METHOD.DELAY_TIME_INFO_PUSH}\r\n ${JSON.stringify(data)}\r\n `
+  }
+
+  function handleDroneControlMqttEvent (payload: any) {
+    if (!payload || !payload.method) {
+      return
+    }
+
+    switch (payload.method) {
+      case DRC_METHOD.HSI_INFO_PUSH: {
+        handleHsiInfo(payload.data)
+        break
+      }
+      case DRC_METHOD.OSD_INFO_PUSH: {
+        handleOsdInfo(payload.data)
+        break
+      }
+      case DRC_METHOD.DELAY_TIME_INFO_PUSH: {
+        handleDelayTimeInfo(payload.data)
+        break
+      }
+    }
+    drcInfo.value = hsiInfo.value + osdInfo.value + delayInfo.value
+  }
+
+  onMounted(() => {
+    EventBus.on('droneControlMqttInfo', handleDroneControlMqttEvent)
+  })
+
+  onBeforeUnmount(() => {
+    EventBus.off('droneControlMqttInfo', handleDroneControlMqttEvent)
+  })
+
+  return {
+    drcInfo: drcInfo
+  }
+}
diff --git a/src/components/g-map/use-drone-control-ws-event.ts b/src/components/g-map/use-drone-control-ws-event.ts
new file mode 100644
index 0000000..34981c2
--- /dev/null
+++ b/src/components/g-map/use-drone-control-ws-event.ts
@@ -0,0 +1,97 @@
+import { message, notification } from 'ant-design-vue'
+import { ref, onMounted, onBeforeUnmount } from 'vue'
+import EventBus from '/@/event-bus/'
+import { EBizCode } from '/@/types'
+import { ControlSource } from '/@/types/device'
+import { ControlSourceChangeType, ControlSourceChangeInfo, FlyToPointMessage, TakeoffToPointMessage, DrcModeExitNotifyMessage, DrcStatusNotifyMessage } from '/@/types/drone-control'
+
+export interface UseDroneControlWsEventParams {
+}
+
+export function useDroneControlWsEvent (sn: string, payloadSn: string, funcs?: UseDroneControlWsEventParams) {
+  const droneControlSource = ref(ControlSource.A)
+  const payloadControlSource = ref(ControlSource.B)
+  function onControlSourceChange (data: ControlSourceChangeInfo) {
+    if (data.type === ControlSourceChangeType.Flight && data.sn === sn) {
+      droneControlSource.value = data.control_source
+      message.info(`Flight control is changed to ${droneControlSource.value}`)
+      return
+    }
+    if (data.type === ControlSourceChangeType.Payload && data.sn === payloadSn) {
+      payloadControlSource.value = data.control_source
+      message.info(`Payload control is changed to ${ payloadControlSource.value }.`)
+      return
+    }
+  }
+
+  function handleProgress(key: string, message: string, error: number) {
+    if (error !== 0) {
+      notification.error({
+        key: key,
+        message: key + 'Error code:' + error,
+        description: message,
+        duration: null
+      })
+    } else {
+      notification.info({
+        key: key,
+        message: key,
+        description: message,
+        duration: 30
+      })
+    }
+  }
+
+  function handleDroneControlWsEvent (payload: any) {
+    if (!payload) {
+      return
+    }
+
+    switch (payload.biz_code) {
+      case EBizCode.ControlSourceChange: {
+        onControlSourceChange(payload.data)
+        break
+      }
+      case EBizCode.FlyToPointProgress: {
+        const { sn: deviceSn, result, message: msg } = payload.data as FlyToPointMessage
+        if (deviceSn !== sn) return
+        handleProgress(EBizCode.FlyToPointProgress, `device(sn: ${deviceSn}) ${msg}`, result)
+        break
+      }
+      case EBizCode.TakeoffToPointProgress: {
+        const { sn: deviceSn, result, message: msg } = payload.data as TakeoffToPointMessage
+        if (deviceSn !== sn) return
+        handleProgress(EBizCode.TakeoffToPointProgress, `device(sn: ${deviceSn}) ${msg}`, result)
+        break
+      }
+      case EBizCode.JoystickInvalidNotify: {
+        const { sn: deviceSn, result, message: msg } = payload.data as DrcModeExitNotifyMessage
+        if (deviceSn !== sn) return
+        handleProgress(EBizCode.JoystickInvalidNotify, `device(sn: ${deviceSn}) ${msg}`, result)
+        break
+      }
+      case EBizCode.DrcStatusNotify: {
+        const { sn: deviceSn, result, message: msg } = payload.data as DrcStatusNotifyMessage
+        if (deviceSn !== sn) return
+        // handleProgress(EBizCode.DrcStatusNotify, `device(sn: ${deviceSn}) ${msg}`, result)
+
+        break
+      }
+    }
+    // eslint-disable-next-line no-unused-expressions
+    // console.log('payload.biz_code', payload.data)
+  }
+
+  onMounted(() => {
+    EventBus.on('droneControlWs', handleDroneControlWsEvent)
+  })
+
+  onBeforeUnmount(() => {
+    EventBus.off('droneControlWs', handleDroneControlWsEvent)
+  })
+
+  return {
+    droneControlSource: droneControlSource,
+    payloadControlSource: payloadControlSource
+  }
+}
diff --git a/src/components/g-map/use-drone-control.ts b/src/components/g-map/use-drone-control.ts
new file mode 100644
index 0000000..dee8366
--- /dev/null
+++ b/src/components/g-map/use-drone-control.ts
@@ -0,0 +1,40 @@
+import { ref } from 'vue'
+import { postFlyToPoint, PostFlyToPointBody, deleteFlyToPoint, postTakeoffToPoint, PostTakeoffToPointBody } from '/@/api/drone-control/drone'
+import { message } from 'ant-design-vue'
+
+export function useDroneControl () {
+  const droneControlPanelVisible = ref(false)
+
+  function setDroneControlPanelVisible (visible: boolean) {
+    droneControlPanelVisible.value = visible
+  }
+
+  async function flyToPoint (sn: string, body: PostFlyToPointBody) {
+    const { code } = await postFlyToPoint(sn, body)
+    if (code === 0) {
+      message.success('Fly to')
+    }
+  }
+
+  async function stopFlyToPoint (sn: string) {
+    const { code } = await deleteFlyToPoint(sn)
+    if (code === 0) {
+      message.success('Stop fly to')
+    }
+  }
+
+  async function takeoffToPoint (sn: string, body: PostTakeoffToPointBody) {
+    const { code } = await postTakeoffToPoint(sn, body)
+    if (code === 0) {
+      message.success('Take off successfully')
+    }
+  }
+
+  return {
+    droneControlPanelVisible,
+    setDroneControlPanelVisible,
+    flyToPoint,
+    stopFlyToPoint,
+    takeoffToPoint
+  }
+}
diff --git a/src/components/g-map/use-manual-control.ts b/src/components/g-map/use-manual-control.ts
new file mode 100644
index 0000000..eb178a8
--- /dev/null
+++ b/src/components/g-map/use-manual-control.ts
@@ -0,0 +1,162 @@
+import {
+  ref,
+  onUnmounted,
+  watch,
+  Ref,
+} from 'vue'
+import { message } from 'ant-design-vue'
+import {
+  DRC_METHOD,
+  DroneControlProtocol,
+} from '/@/types/drc'
+import {
+  useMqtt,
+  DeviceTopicInfo
+} from './use-mqtt'
+
+let myInterval: any
+
+export enum KeyCode {
+  KEY_W = 'KeyW',
+  KEY_A = 'KeyA',
+  KEY_S = 'KeyS',
+  KEY_D = 'KeyD',
+  KEY_Q = 'KeyQ',
+  KEY_E = 'KeyE',
+  ARROW_UP = 'ArrowUp',
+  ARROW_DOWN = 'ArrowDown',
+}
+
+export function useManualControl (deviceTopicInfo: DeviceTopicInfo, isCurrentFlightController: Ref<boolean>) {
+  const activeCodeKey = ref(null) as Ref<KeyCode | null>
+  const mqttHooks = useMqtt(deviceTopicInfo)
+
+  function handlePublish (params: DroneControlProtocol) {
+    const body = {
+      method: DRC_METHOD.DRONE_CONTROL,
+      data: params,
+    }
+    handleClearInterval()
+    myInterval = setInterval(() => {
+      window.console.log('keyCode>>>>', activeCodeKey.value, body)
+      body.data.seq = new Date().getTime()
+      mqttHooks?.publishMqtt(deviceTopicInfo.pubTopic, body, { qos: 0 })
+    }, 50)
+  }
+
+  function handleKeyup (keyCode: KeyCode) {
+    if (!deviceTopicInfo.pubTopic) {
+      message.error('请确保已经建立DRC链路')
+      return
+    }
+    const SPEED = 5 //  check
+    const HEIGHT = 5 //  check
+    const W_SPEED = 20 // 机头角速度
+    switch (keyCode) {
+      case 'KeyA':
+        if (activeCodeKey.value === keyCode) return
+        handlePublish({ y: -SPEED })
+        activeCodeKey.value = keyCode
+        break
+      case 'KeyW':
+        if (activeCodeKey.value === keyCode) return
+        handlePublish({ x: SPEED })
+        activeCodeKey.value = keyCode
+        break
+      case 'KeyS':
+        if (activeCodeKey.value === keyCode) return
+        handlePublish({ x: -SPEED })
+        activeCodeKey.value = keyCode
+        break
+      case 'KeyD':
+        if (activeCodeKey.value === keyCode) return
+        handlePublish({ y: SPEED })
+        activeCodeKey.value = keyCode
+        break
+      case 'ArrowUp':
+        if (activeCodeKey.value === keyCode) return
+        handlePublish({ h: HEIGHT })
+        activeCodeKey.value = keyCode
+        break
+      case 'ArrowDown':
+        if (activeCodeKey.value === keyCode) return
+        handlePublish({ h: -HEIGHT })
+        activeCodeKey.value = keyCode
+        break
+      case 'KeyQ':
+        if (activeCodeKey.value === keyCode) return
+        handlePublish({ w: -W_SPEED })
+        activeCodeKey.value = keyCode
+        break
+      case 'KeyE':
+        if (activeCodeKey.value === keyCode) return
+        handlePublish({ w: W_SPEED })
+        activeCodeKey.value = keyCode
+        break
+      default:
+        break
+    }
+  }
+
+  function handleClearInterval () {
+    clearInterval(myInterval)
+    myInterval = undefined
+  }
+
+  function resetControlState () {
+    activeCodeKey.value = null
+    handleClearInterval()
+  }
+
+  function onKeyup () {
+    resetControlState()
+  }
+
+  function onKeydown (e: KeyboardEvent) {
+    handleKeyup(e.code as KeyCode)
+  }
+
+  function startKeyboardManualControl () {
+    window.addEventListener('keydown', onKeydown)
+    window.addEventListener('keyup', onKeyup)
+  }
+
+  function closeKeyboardManualControl () {
+    resetControlState()
+    window.removeEventListener('keydown', onKeydown)
+    window.removeEventListener('keyup', onKeyup)
+  }
+
+  watch(() => isCurrentFlightController.value, (val) => {
+    if (val && deviceTopicInfo.pubTopic) {
+      startKeyboardManualControl()
+    } else {
+      closeKeyboardManualControl()
+    }
+  }, { immediate: true })
+
+  onUnmounted(() => {
+    closeKeyboardManualControl()
+  })
+
+  function handleEmergencyStop () {
+    if (!deviceTopicInfo.pubTopic) {
+      message.error('请确保已经建立DRC链路')
+      return
+    }
+    const body = {
+      method: DRC_METHOD.DRONE_EMERGENCY_STOP,
+      data: {}
+    }
+    resetControlState()
+    window.console.log('handleEmergencyStop>>>>', deviceTopicInfo.pubTopic, body)
+    mqttHooks?.publishMqtt(deviceTopicInfo.pubTopic, body, { qos: 1 })
+  }
+
+  return {
+    activeCodeKey,
+    handleKeyup,
+    handleEmergencyStop,
+    resetControlState,
+  }
+}
diff --git a/src/components/g-map/use-mqtt.ts b/src/components/g-map/use-mqtt.ts
new file mode 100644
index 0000000..c02f537
--- /dev/null
+++ b/src/components/g-map/use-mqtt.ts
@@ -0,0 +1,132 @@
+import {
+  ref,
+  reactive,
+  computed,
+  watch,
+  onUnmounted,
+} from 'vue'
+import {
+  IClientPublishOptions,
+  IPublishPacket,
+} from '/@/mqtt'
+import { useMyStore } from '/@/store'
+import {
+  DRC_METHOD,
+} from '/@/types/drc'
+import EventBus from '/@/event-bus'
+
+export interface DeviceTopicInfo{
+  sn: string
+  pubTopic: string
+  subTopic: string
+}
+
+type MessageMqtt = (topic: string, payload: Buffer, packet: IPublishPacket) => void | Promise<void>
+
+export function useMqtt (deviceTopicInfo: DeviceTopicInfo) {
+  let cacheSubscribeArr: {
+    topic: string;
+    callback?: MessageMqtt;
+  }[] = []
+
+  const store = useMyStore()
+
+  const mqttState = computed(() => {
+    return store.state.mqttState
+  })
+
+  function publishMqtt (topic: string, body: object, ots?: IClientPublishOptions) {
+    // const buffer = Buffer.from(JSON.stringify(body))
+    mqttState.value?.publishMqtt(topic, JSON.stringify(body), ots)
+  }
+
+  function subscribeMqtt (topic: string, handleMessageMqtt?: MessageMqtt) {
+    mqttState.value?.subscribeMqtt(topic)
+    const handler = handleMessageMqtt || onMessageMqtt
+    mqttState.value?.on('onMessageMqtt', handler)
+    cacheSubscribeArr.push({
+      topic,
+      callback: handler,
+    })
+  }
+
+  function onMessageMqtt (message: any) {
+    if (cacheSubscribeArr.findIndex(item => item.topic === message?.topic) !== -1) {
+      const payloadStr = new TextDecoder('utf-8').decode(message?.payload)
+      const payloadObj = JSON.parse(payloadStr)
+      switch (payloadObj?.method) {
+        case DRC_METHOD.HEART_BEAT:
+          break
+        case DRC_METHOD.DELAY_TIME_INFO_PUSH:
+        case DRC_METHOD.HSI_INFO_PUSH:
+        case DRC_METHOD.OSD_INFO_PUSH:
+          EventBus.emit('droneControlMqttInfo', payloadObj)
+          break
+        default:
+          break
+      }
+    }
+  }
+
+  function unsubscribeDrc () {
+    // 销毁已订阅事件
+    cacheSubscribeArr.forEach(item => {
+      mqttState.value?.off('onMessageMqtt', item.callback)
+      mqttState.value?.unsubscribeMqtt(item.topic)
+    })
+    cacheSubscribeArr = []
+  }
+
+  // 心跳
+  const heartBeatSeq = ref(0)
+  const state = reactive({
+    heartState: new Map<string, {
+      pingInterval: any;
+    }>(),
+  })
+
+  // 监听云控控制权
+  watch(() => deviceTopicInfo, (val, oldVal) => {
+    if (val.subTopic !== '') {
+      // 1.订阅topic
+      subscribeMqtt(deviceTopicInfo.subTopic)
+      // 2.发心跳
+      publishDrcPing(deviceTopicInfo.sn)
+    } else {
+      clearInterval(state.heartState.get(deviceTopicInfo.sn)?.pingInterval)
+      state.heartState.delete(deviceTopicInfo.sn)
+      heartBeatSeq.value = 0
+    }
+  }, { immediate: true, deep: true })
+
+  function publishDrcPing (sn: string) {
+    const body = {
+      method: DRC_METHOD.HEART_BEAT,
+      data: {
+        ts: new Date().getTime(),
+        seq: heartBeatSeq.value,
+      },
+    }
+    const pingInterval = setInterval(() => {
+      if (!mqttState.value) return
+      heartBeatSeq.value += 1
+      body.data.ts = new Date().getTime()
+      body.data.seq = heartBeatSeq.value
+      publishMqtt(deviceTopicInfo.pubTopic, body, { qos: 0 })
+    }, 1000)
+    state.heartState.set(sn, {
+      pingInterval,
+    })
+  }
+
+  onUnmounted(() => {
+    unsubscribeDrc()
+    heartBeatSeq.value = 0
+  })
+
+  return {
+    mqttState,
+    publishMqtt,
+    subscribeMqtt,
+  }
+}
diff --git a/src/components/g-map/use-payload-control.ts b/src/components/g-map/use-payload-control.ts
new file mode 100644
index 0000000..d702c00
--- /dev/null
+++ b/src/components/g-map/use-payload-control.ts
@@ -0,0 +1,120 @@
+import { message } from 'ant-design-vue'
+import {
+  postPayloadAuth,
+  postPayloadCommands,
+  PayloadCommandsEnum,
+  PostCameraModeBody,
+  PostCameraFocalLengthBody,
+  PostGimbalResetBody,
+  PostCameraAimBody,
+} from '/@/api/drone-control/payload'
+import { ControlSource } from '/@/types/device'
+
+export function usePayloadControl () {
+  function checkPayloadAuth (controlSource?: ControlSource) {
+    if (controlSource !== ControlSource.A) {
+      message.error('Get Payload Control first')
+      return false
+    }
+    return true
+  }
+
+  async function authPayload (sn: string, payloadIndx: string) {
+    const { code } = await postPayloadAuth(sn, {
+      payload_index: payloadIndx
+    })
+    if (code === 0) {
+      message.success('Get Payload Control successfully')
+      return true
+    }
+    return false
+  }
+
+  async function resetGimbal (sn: string, data: PostGimbalResetBody) {
+    const { code } = await postPayloadCommands(sn, {
+      cmd: PayloadCommandsEnum.GimbalReset,
+      data: data
+    })
+    if (code === 0) {
+      message.success('Gimbal Reset successfully')
+    }
+  }
+
+  async function switchCameraMode (sn: string, data: PostCameraModeBody) {
+    const { code } = await postPayloadCommands(sn, {
+      cmd: PayloadCommandsEnum.CameraModeSwitch,
+      data: data
+    })
+    if (code === 0) {
+      message.success('Camera Mode Switch successfully')
+    }
+  }
+
+  async function takeCameraPhoto (sn: string, payloadIndx: string) {
+    const { code } = await postPayloadCommands(sn, {
+      cmd: PayloadCommandsEnum.CameraPhotoTake,
+      data: {
+        payload_index: payloadIndx
+      }
+    })
+    if (code === 0) {
+      message.success('Take Photo successfully')
+    }
+  }
+
+  async function startCameraRecording (sn: string, payloadIndx: string) {
+    const { code } = await postPayloadCommands(sn, {
+      cmd: PayloadCommandsEnum.CameraRecordingStart,
+      data: {
+        payload_index: payloadIndx
+      }
+    })
+    if (code === 0) {
+      message.success('Start Recording successfully')
+    }
+  }
+
+  async function stopCameraRecording (sn: string, payloadIndx: string) {
+    const { code } = await postPayloadCommands(sn, {
+      cmd: PayloadCommandsEnum.CameraRecordingStop,
+      data: {
+        payload_index: payloadIndx
+      }
+    })
+    if (code === 0) {
+      message.success('Stop Recording successfully')
+    }
+  }
+
+  async function changeCameraFocalLength (sn: string, data: PostCameraFocalLengthBody) {
+    const { code } = await postPayloadCommands(sn, {
+      cmd: PayloadCommandsEnum.CameraFocalLengthSet,
+      data: data,
+    })
+    if (code === 0) {
+      message.success('Zoom successfully')
+    }
+  }
+
+  async function cameraAim (sn: string, data: PostCameraAimBody) {
+    const { code } = await postPayloadCommands(sn, {
+      cmd: PayloadCommandsEnum.CameraAim,
+      data: data,
+    })
+    if (code === 0) {
+      message.success('Zoom Aim successfully')
+    }
+  }
+
+  return {
+    checkPayloadAuth,
+    authPayload,
+    resetGimbal,
+    switchCameraMode,
+    takeCameraPhoto,
+    startCameraRecording,
+    stopCameraRecording,
+    changeCameraFocalLength,
+    cameraAim,
+  }
+}
diff --git a/src/components/task/CreatePlan.vue b/src/components/task/CreatePlan.vue
index 37f0ae7..becc1be 100644
--- a/src/components/task/CreatePlan.vue
+++ b/src/components/task/CreatePlan.vue
@@ -182,7 +182,8 @@
     disabled.value = true
     const createPlanBody = { ...planBody } as unknown as CreatePlan
     if (planBody.select_execute_time) {
-      createPlanBody.execute_time = moment(planBody.select_execute_time).valueOf()
+      createPlanBody.task_days = [moment(planBody.select_execute_time).unix()]
+      createPlanBody.task_periods = [createPlanBody.task_days]
     }
     createPlanBody.rth_altitude = Number(createPlanBody.rth_altitude)
     if (wayline.value && wayline.value.template_types && wayline.value.template_types.length > 0) {
diff --git a/src/components/task/TaskPanel.vue b/src/components/task/TaskPanel.vue
index e7874e5..fd6a204 100644
--- a/src/components/task/TaskPanel.vue
+++ b/src/components/task/TaskPanel.vue
@@ -5,7 +5,7 @@
       :pagination="paginationProp" :scroll="{ x: '100%', y: 600 }" @change="refreshData">
       <!-- 执行时间 -->
       <template #duration="{ record }">
-        <div class="flex-row">
+        <div class="flex-row" style="white-space: pre-wrap">
           <div>
             <div>{{ formatTaskTime(record.begin_time) }}</div>
             <div>{{ formatTaskTime(record.end_time) }}</div>
@@ -62,7 +62,7 @@
       </template>
       <!-- 操作 -->
       <template #action="{ record }">
-        <span class="action-area">
+        <div class="action-area">
           <a-popconfirm
             v-if="record.status === TaskStatus.Wait"
             title="Are you sure you want to delete flight task?"
@@ -72,7 +72,25 @@
           >
             <a-button type="primary" size="small">Delete</a-button>
           </a-popconfirm>
-        </span>
+          <a-popconfirm
+            v-if="record.status === TaskStatus.Carrying"
+            title="Are you sure you want to suspend?"
+            ok-text="Yes"
+            cancel-text="No"
+            @confirm="onSuspendTask(record.job_id)"
+          >
+            <a-button type="primary" size="small">Suspend</a-button>
+          </a-popconfirm>
+          <a-popconfirm
+            v-if="record.status === TaskStatus.Paused"
+            title="Are you sure you want to resume?"
+            ok-text="Yes"
+            cancel-text="No"
+            @confirm="onResumeTask(record.job_id)"
+          >
+            <a-button type="primary" size="small">Resume</a-button>
+          </a-popconfirm>
+        </div>
       </template>
     </a-table>
   </div>
@@ -84,7 +102,7 @@
 import { TableState } from 'ant-design-vue/lib/table/interface'
 import { onMounted } from 'vue'
 import { IPage } from '/@/api/http/type'
-import { deleteTask, getWaylineJobs, Task, uploadMediaFileNow } from '/@/api/wayline'
+import { deleteTask, updateTaskStatus, UpdateTaskStatus, getWaylineJobs, Task, uploadMediaFileNow } from '/@/api/wayline'
 import { useMyStore } from '/@/store'
 import { ELocalStorageKey } from '/@/types/enums'
 import { useFormatTask } from './use-format-task'
@@ -272,6 +290,30 @@
   }
 }
 
+// 挂起任务
+async function onSuspendTask (jobId: string) {
+  const { code } = await updateTaskStatus(workspaceId, {
+    job_id: jobId,
+    status: UpdateTaskStatus.Suspend
+  })
+  if (code === 0) {
+    message.success('Suspended successfully')
+    getPlans()
+  }
+}
+
+// 解除挂起任务
+async function onResumeTask (jobId: string) {
+  const { code } = await updateTaskStatus(workspaceId, {
+    job_id: jobId,
+    status: UpdateTaskStatus.Resume
+  })
+  if (code === 0) {
+    message.success('Resumed successfully')
+    getPlans()
+  }
+}
+
 // 立即上传媒体
 async function onUploadMediaFileNow (jobId: string) {
   const { code } = await uploadMediaFileNow(workspaceId, jobId)
@@ -291,8 +333,13 @@
     margin-top: 10px;
   }
   .action-area {
-    color: $primary;
-    cursor: pointer;
+
+    &::v-deep {
+      .ant-btn {
+        margin-right: 10px;
+        margin-bottom: 10px;
+      }
+    }
   }
 
   .circle-icon {
diff --git a/src/event-bus/index.ts b/src/event-bus/index.ts
index 72feae5..fbe0933 100644
--- a/src/event-bus/index.ts
+++ b/src/event-bus/index.ts
@@ -4,6 +4,8 @@
   deviceUpgrade: any; // 设备升级
   deviceLogUploadProgress: any // 设备日志上传
   flightTaskWs: any // 机场任务消息
+  droneControlWs: any // 飞行指令信息
+  droneControlMqttInfo: any // drc 链路通知
 };
 
 const emitter: Emitter<Events> = mitt<Events>()
diff --git a/src/hooks/use-g-map-tsa.ts b/src/hooks/use-g-map-tsa.ts
index c36779b..f71217c 100644
--- a/src/hooks/use-g-map-tsa.ts
+++ b/src/hooks/use-g-map-tsa.ts
@@ -1,12 +1,11 @@
 import store from '/@/store'
 import { getRoot } from '/@/root'
-import { ELocalStorageKey } from '/@/types'
+import { ELocalStorageKey, EDeviceTypeName } from '/@/types'
 import { getDeviceBySn } from '/@/api/manage'
 import { message } from 'ant-design-vue'
 import dockIcon from '/@/assets/icons/dock.png'
 import rcIcon from '/@/assets/icons/rc.png'
 import droneIcon from '/@/assets/icons/drone.png'
-import { EDeviceTypeName } from '/@/types'
 
 export function deviceTsaUpdate () {
   const root = getRoot()
diff --git a/src/mqtt/config.ts b/src/mqtt/config.ts
new file mode 100644
index 0000000..1af7df6
--- /dev/null
+++ b/src/mqtt/config.ts
@@ -0,0 +1,12 @@
+
+import {
+  IClientOptions,
+} from 'mqtt'
+
+export const OPTIONS: IClientOptions = {
+  clean: true, // true: 清除会话, false: 保留会话
+  connectTimeout: 10000, // mqtt 超时时间
+  resubscribe: true, // 断开重连后,再次订阅原订阅
+  reconnectPeriod: 10000, // 重连间隔时间: 5s
+  keepalive: 1, // 心跳间隔时间:1s
+}
diff --git a/src/mqtt/index.ts b/src/mqtt/index.ts
new file mode 100644
index 0000000..f6570df
--- /dev/null
+++ b/src/mqtt/index.ts
@@ -0,0 +1,117 @@
+import EventEmitter from 'eventemitter3'
+import {
+  OPTIONS,
+} from './config'
+import {
+  connect,
+  MqttClient,
+  IClientPublishOptions,
+  IPublishPacket,
+  Packet,
+  ISubscriptionGrant,
+  IClientOptions,
+} from 'mqtt/dist/mqtt.min'
+
+export class UranusMqtt extends EventEmitter {
+  _url: string
+  _options?: IClientOptions
+  _client: MqttClient | null
+  _hasInit: boolean
+
+  constructor (url?: string, options?: IClientOptions) {
+    super()
+    this._url = url || ''
+    this._options = options
+    this._client = null
+    this._hasInit = false
+  }
+
+  initMqtt = () => {
+    // 仅初始化一次
+    if (this._hasInit) return
+    // 建立连接
+    this._client = connect(this._url, {
+      ...OPTIONS,
+      ...this._options,
+    })
+    this._hasInit = true
+    if (this._client) {
+      this._client.on('reconnect', this._onReconnect)
+
+      // 消息监听
+      this._client.on('message', this._onMessage)
+
+      // 连接关闭
+      this._client.on('close', this._onClose)
+
+      // 连接异常
+      this._client.on('error', this._onError)
+    }
+  }
+
+  // 发布
+  publishMqtt = (topic: string, body: string | Buffer, opts?: IClientPublishOptions) => {
+    if (!this._client?.connected) {
+      this.initMqtt()
+    }
+    this._client?.publish(topic, body, opts || {}, (error?: Error, packet?: Packet) => {
+      if (error) {
+        window.console.error('mqtt publish error,', error, packet)
+      }
+    })
+  }
+
+  // 订阅
+  subscribeMqtt = (topic: string) => {
+    if (!this._client?.connected) {
+      this.initMqtt()
+    }
+    window.console.log('subscribeMqtt>>>>>', topic)
+    this._client?.subscribe(topic, (error: Error, granted: ISubscriptionGrant[]) => {
+      window.console.log('mqtt subscribe,', error, granted)
+    })
+  }
+
+  // 取消订阅
+  unsubscribeMqtt = (topic: string) => {
+    window.console.log('mqtt unsubscribeMqtt,', topic)
+    this._client?.unsubscribe(topic)
+  }
+
+  // 关闭 mqtt 客户端
+  destroyed = () => {
+    window.console.log('mqtt destroyed')
+    this._client?.end()
+  }
+
+  _onReconnect = () => {
+    if (this._client) { window.console.error('mqtt reconnect,') }
+  }
+
+  _onMessage = (topic: string, payload: Buffer, packet: IPublishPacket) => {
+    this.emit('onMessageMqtt', { topic, payload, packet })
+  }
+
+  _onClose = () => {
+    // 连接异常关闭会自动重连
+    window.console.error('mqtt close,')
+    this.emit('onStatus', {
+      status: 'close',
+    })
+  }
+
+  _onError = (error: Error) => {
+    // 连接错误会自动重连
+    window.console.error('mqtt error,', error)
+    this.emit('onStatus', {
+      status: 'error',
+      data: error,
+    })
+  }
+}
+
+export {
+  IClientOptions,
+  IPublishPacket,
+  IClientPublishOptions,
+}
diff --git a/src/pages/page-web/projects/Firmwares.vue b/src/pages/page-web/projects/Firmwares.vue
index d3e6757..54fd590 100644
--- a/src/pages/page-web/projects/Firmwares.vue
+++ b/src/pages/page-web/projects/Firmwares.vue
@@ -242,7 +242,7 @@
   if (fileList.value.length === 0) {
     message.error('Please select at least one file.')
   }
-  const uploading: string = 'uploading'
+  let uploading: string
   formRef.value.validate().then(async () => {
     const file: FileItem = fileList.value[0]
     const fileData = new FormData()
@@ -257,6 +257,8 @@
         fileData.append(key, val.toString())
       }
     })
+    const timestamp = new Date().getTime()
+    uploading = (file.name ?? 'uploding') + timestamp
     notification.open({
       key: uploading,
       message: `Uploading  ${moment().format()}`,
@@ -267,7 +269,7 @@
       if (res.code === 0) {
         notification.success({
           message: `Uploaded  ${moment().format()}`,
-          description: `[${file.name}] file uploaded successfully.`,
+          description: `[${file.name}] file uploaded successfully. Duration: ${moment.duration(new Date().getTime() - timestamp).asSeconds()}`,
           duration: null
         })
         getAllFirmwares(pageParam)
diff --git a/src/pages/page-web/projects/devices.vue b/src/pages/page-web/projects/devices.vue
index 768e86f..07c722d 100644
--- a/src/pages/page-web/projects/devices.vue
+++ b/src/pages/page-web/projects/devices.vue
@@ -20,7 +20,9 @@
             style="margin: -5px 0"
           />
           <template v-else>
-            {{ text }}
+            <a-tooltip :title="text">
+              {{ text }}
+            </a-tooltip>
           </template>
         </div>
       </template>
@@ -127,7 +129,7 @@
 import { DeviceCmdExecuteInfo, DeviceCmdExecuteStatus } from '/@/types/device-cmd'
 import DeviceLogUploadRecordDrawer from '/@/components/devices/device-log/DeviceLogUploadRecordDrawer.vue'
 import DeviceHmsDrawer from '/@/components/devices/device-hms/DeviceHmsDrawer.vue'
-import { message } from 'ant-design-vue'
+import { message, notification } from 'ant-design-vue'
 
 interface DeviceData {
   device: Device[]
@@ -268,13 +270,18 @@
   for (let i = 0; i < devices.length; i++) {
     if (devices[i].device_sn === payload.sn) {
       if (!payload.output) return
-      const { status, progress } = payload.output
+      const { status, progress, ext } = payload.output
       if (status === DeviceCmdExecuteStatus.Sent || status === DeviceCmdExecuteStatus.InProgress) { // 升级中
+        const rate = ext?.rate ? (ext.rate / 1024).toFixed(2) + 'kb/s' : ''
         devices[i].firmware_status = DeviceFirmwareStatusEnum.DuringUpgrade
-        devices[i].firmware_progress = progress?.percent || 0
+        devices[i].firmware_progress = (progress?.percent || 0) + '% ' + rate
       } else { // 终态:成功,失败,超时
         if (status === DeviceCmdExecuteStatus.Failed || status === DeviceCmdExecuteStatus.Timeout) {
-          message.error(`设备(${payload.sn}) 升级失败`)
+          notification.error({
+            message: `(${payload.sn}) Upgrade failed`,
+            description: `Error Code: ${payload.result}`,
+            duration: null
+          })
         }
         // 拉取列表
         getDevices(current.value[0], true)
diff --git a/src/pages/page-web/projects/dock.vue b/src/pages/page-web/projects/dock.vue
index 6e4febf..e31c88c 100644
--- a/src/pages/page-web/projects/dock.vue
+++ b/src/pages/page-web/projects/dock.vue
@@ -58,7 +58,7 @@
   getDocks()
   const key = setInterval(() => {
     const data = document.getElementById('data')?.lastElementChild as HTMLDivElement
-    if (body.total === 0 || Math.ceil(body.total / body.page_size) <= body.page || scorllHeight.value <= data?.clientHeight + data?.offsetTop) {
+    if (body.total === 0 || Math.ceil(body.total / body.page_size) <= body.page || scorllHeight.value + 50 <= data?.clientHeight + data?.offsetTop) {
       clearInterval(key)
       return
     }
diff --git a/src/pages/page-web/projects/livestream.vue b/src/pages/page-web/projects/livestream.vue
index ad9a9f9..f2c3414 100644
--- a/src/pages/page-web/projects/livestream.vue
+++ b/src/pages/page-web/projects/livestream.vue
@@ -31,8 +31,8 @@
 import { getRoot } from '/@/root'
 import { ERouterName } from '/@/types'
 const root = getRoot()
-const routeName = ref<string>()
-const showLive = ref<boolean>(false)
+const routeName = ref<string>('LiveOthers')
+const showLive = ref<boolean>(root.$route.name === ERouterName.LIVING)
 
 const options = [
   { key: 0, label: 'Agora Live', path: '/' + ERouterName.LIVESTREAM + '/' + ERouterName.LIVING, routeName: 'LiveAgora' },
@@ -40,6 +40,7 @@
 ]
 
 const selectLivestream = (route: string) => {
+  showLive.value = root.$route.name === ERouterName.LIVING
   routeName.value = route
 }
 
diff --git a/src/pages/page-web/projects/tsa.vue b/src/pages/page-web/projects/tsa.vue
index 3668f68..9ab2216 100644
--- a/src/pages/page-web/projects/tsa.vue
+++ b/src/pages/page-web/projects/tsa.vue
@@ -20,15 +20,15 @@
                 <div style="float: left; padding: 0px 5px 8px 8px; width: 88%">
                   <div style="width: 80%; height: 30px; line-height: 30px; font-size: 16px;">
                     <a-tooltip :title="`${dock.gateway.callsign} - ${dock.callsign ?? 'No Drone'}`">
-                      <span class="text-hidden" style="max-width: 200px;">{{ dock.gateway.callsign }} - {{ dock.callsign ?? 'No Drone' }}</span>
+                      <div class="text-hidden" style="max-width: 200px;">{{ dock.gateway.callsign }} - {{ dock.callsign ?? 'No Drone' }}</div>
                     </a-tooltip>
                   </div>
                   <div class="mt5 flex-align-center flex-row flex-justify-between" style="background: #595959;">
                     <div class="flex-align-center flex-row">
                       <span class="ml5 mr5"><RobotOutlined /></span>
-                      <span class="font-bold text-hidden" style="max-width: 80px;" :style="dockInfo[dock.gateway.sn] && dockInfo[dock.gateway.sn].basic_osd?.mode_code !== EDockModeCode.Disconnected ? 'color: #00ee8b' :  'color: red;'">
+                      <div class="font-bold text-hidden" style="max-width: 80px;" :style="dockInfo[dock.gateway.sn] && dockInfo[dock.gateway.sn].basic_osd?.mode_code !== EDockModeCode.Disconnected ? 'color: #00ee8b' :  'color: red;'">
                         {{ dockInfo[dock.gateway.sn] ? EDockModeCode[dockInfo[dock.gateway.sn].basic_osd?.mode_code] : EDockModeCode[EDockModeCode.Disconnected] }}
-                      </span>
+                      </div>
                     </div>
                     <div class="mr5 flex-align-center flex-row" style="width: 85px; margin-right: 0; height: 18px;">
                       <div v-if="hmsInfo[dock.gateway.sn]" class="flex-align-center flex-row">
@@ -75,11 +75,11 @@
                     </div>
                   </div>
                   <div class="mt5 flex-align-center flex-row flex-justify-between" style="background: #595959;">
-                    <div>
+                    <div class="flex-row">
                       <span class="ml5 mr5"><RocketOutlined /></span>
-                      <span class="font-bold" :style="deviceInfo[dock.sn] && deviceInfo[dock.sn].mode_code !== EModeCode.Disconnected ? 'color: #00ee8b' :  'color: red;'">
+                      <div class="font-bold text-hidden" style="max-width: 80px" :style="deviceInfo[dock.sn] && deviceInfo[dock.sn].mode_code !== EModeCode.Disconnected ? 'color: #00ee8b' :  'color: red;'">
                         {{ deviceInfo[dock.sn] ? EModeCode[deviceInfo[dock.sn].mode_code] : EModeCode[EModeCode.Disconnected] }}
-                      </span>
+                      </div>
                     </div>
                     <div class="mr5 flex-align-center flex-row" style="width: 85px; margin-right: 0; height: 18px;">
                       <div v-if="hmsInfo[dock.sn]" class="flex-align-center flex-row">
@@ -173,11 +173,11 @@
                 </div>
               </div>
               <div class="flex-row flex-justify-center flex-align-center" style="height: 40px;">
-                <div style="height: 20px; background: #595959; width: 94%;" >
+                <div class="flex-row" style="height: 20px; background: #595959; width: 94%;" >
                   <span class="mr5"><a-image style="margin-left: 2px; margin-top: -2px; height: 20px; width: 20px;" :src="rc" /></span>
                   <a-tooltip>
                     <template #title>{{ device.gateway.model }} - {{ device.gateway.callsign }} </template>
-                    <span class="text-hidden" style="max-width: 200px;">{{ device.gateway.model }} - {{ device.gateway.callsign }}</span>
+                    <div class="text-hidden" style="max-width: 200px;">{{ device.gateway.model }} - {{ device.gateway.callsign }}</div>
                   </a-tooltip>
                 </div>
               </div>
@@ -194,10 +194,9 @@
 import { EDeviceTypeName, ELocalStorageKey } from '/@/types'
 import noData from '/@/assets/icons/no-data.png'
 import rc from '/@/assets/icons/rc.png'
-import { DeviceStatus, EModeCode, OSDVisible, EDockModeCode, DeviceOsd } from '/@/types/device'
+import { OnlineDevice, EModeCode, OSDVisible, EDockModeCode, DeviceOsd } from '/@/types/device'
 import { useMyStore } from '/@/store'
 import { getDeviceTopo, getUnreadDeviceHms, updateDeviceHms } from '/@/api/manage'
-import { message } from 'ant-design-vue'
 import { RocketOutlined, EyeInvisibleOutlined, EyeOutlined, RobotOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'
 import { EHmsLevel } from '/@/types/enums'
 
@@ -207,22 +206,6 @@
 const osdVisible = ref({} as OSDVisible)
 const hmsVisible = new Map<string, boolean>()
 const scorllHeight = ref()
-
-interface OnlineDevice {
-  model: string,
-  callsign: string,
-  sn: string,
-  mode: number,
-  gateway: {
-    model: string,
-    callsign: string,
-    sn: string,
-    domain: string,
-  },
-  payload: {
-    model: string
-  }[]
-}
 
 const onlineDevices = reactive({
   data: [] as OnlineDevice[]
@@ -286,7 +269,12 @@
       }
       child?.payloads_list.forEach((payload: any) => {
         device.payload.push({
-          model: payload.payload_name
+          index: payload.index,
+          model: payload.model,
+          payload_name: payload.payload_name,
+          payload_sn: payload.payload_sn,
+          control_source: payload.control_source,
+          payload_index: payload.payload_index
         })
       })
       if (EDeviceTypeName.Dock === gateway.domain) {
@@ -316,6 +304,7 @@
     osdVisible.value.gateway_sn = device.gateway.sn
     osdVisible.value.is_dock = isDock
     osdVisible.value.gateway_callsign = device.gateway.callsign
+    osdVisible.value.payloads = device.payload
   }
   store.commit('SET_OSD_VISIBLE_INFO', osdVisible)
 }
diff --git a/src/pages/page-web/projects/workspace.vue b/src/pages/page-web/projects/workspace.vue
index 4d965f0..d302112 100644
--- a/src/pages/page-web/projects/workspace.vue
+++ b/src/pages/page-web/projects/workspace.vue
@@ -100,6 +100,15 @@
       })
       break
     }
+    case EBizCode.ControlSourceChange:
+    case EBizCode.FlyToPointProgress:
+    case EBizCode.TakeoffToPointProgress:
+    case EBizCode.JoystickInvalidNotify:
+    case EBizCode.DrcStatusNotify:
+    {
+      EventBus.emit('droneControlWs', payload)
+      break
+    }
     default:
       break
   }
diff --git a/src/shims-mqtt.d.ts b/src/shims-mqtt.d.ts
new file mode 100644
index 0000000..45ab849
--- /dev/null
+++ b/src/shims-mqtt.d.ts
@@ -0,0 +1,4 @@
+declare module 'mqtt/dist/mqtt.min' {
+  import MQTT from 'mqtt'
+  export = MQTT
+}
diff --git a/src/store/index.ts b/src/store/index.ts
index d7f4673..6dbf7ba 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -67,13 +67,14 @@
     currentSn: '',
     currentType: -1
   },
-  osdVisible: {
+  osdVisible: { // osd 显示设备相关信息
     sn: '',
     callsign: '',
     model: '',
     visible: false,
     gateway_sn: '',
     is_dock: false,
+    payloads: null
   } as OSDVisible,
   waylineInfo: {
 
@@ -86,7 +87,9 @@
   },
   // 机场指令执行状态信息
   devicesCmdExecuteInfo: {
-  } as DevicesCmdExecuteInfo
+  } as DevicesCmdExecuteInfo,
+  mqttState: null as any, // mqtt 实例
+  clientId: '', // mqtt 连接 唯一客户端id
 })
 
 export type RootStateType = ReturnType<typeof initStateFunc>
@@ -117,15 +120,18 @@
     state.deviceState.currentSn = info.sn
     state.deviceState.currentType = EDeviceTypeName.Dock
     const dock = state.deviceState.dockInfo[info.sn]
+    if (info.host.mode_code !== undefined) {
+      dock.basic_osd = info.host
+      return
+    }
     if (info.host.sdr) {
       dock.link_osd = info.host
       return
     }
-    if (info.host.job_number) {
+    if (info.host.job_number !== undefined) {
       dock.work_osd = info.host
       return
     }
-    dock.basic_osd = info.host
   },
   SET_DRAW_VISIBLE_INFO (state, bool) {
     state.drawVisible = bool
@@ -182,7 +188,13 @@
     } else {
       state.devicesCmdExecuteInfo[info.sn] = [info]
     }
-  }
+  },
+  SET_MQTT_STATE (state, mqttState) {
+    state.mqttState = mqttState
+  },
+  SET_CLIENT_ID (state, clientId) {
+    state.clientId = clientId
+  },
 }
 
 const actions: ActionTree<RootStateType, RootStateType> = {
diff --git a/src/types/device-cmd.ts b/src/types/device-cmd.ts
index d8804cd..c1e545d 100644
--- a/src/types/device-cmd.ts
+++ b/src/types/device-cmd.ts
@@ -39,6 +39,16 @@
   loading: boolean // 按钮loading
   disabled?: boolean // 按钮disabled
 }
+export const noDebugCmdList: DeviceCmdItem[] = [
+  {
+    label: 'Return Home',
+    status: '--',
+    operateText: 'Return Home',
+    cmdKey: DeviceCmd.ReturnHome,
+    func: 'returnHome',
+    loading: false,
+  },
+]
 
 // 机场指令
 export const cmdList: DeviceCmdItem[] = [
@@ -88,14 +98,6 @@
     cmdKey: DeviceCmd.ChargeOpen,
     oppositeCmdKey: DeviceCmd.ChargeClose,
     func: 'chargeStatus',
-    loading: false,
-  },
-  {
-    label: '一键返航',
-    status: '--',
-    operateText: '返航',
-    cmdKey: DeviceCmd.ReturnHome,
-    func: 'returnHome',
     loading: false,
   },
   {
@@ -281,6 +283,9 @@
       percent: number,
       step_key: string,
       step_result: number
+    },
+    ext?: {
+      rate?: number
     }
   }
   result: number,
diff --git a/src/types/device.ts b/src/types/device.ts
index f23f27e..fdbeb67 100644
--- a/src/types/device.ts
+++ b/src/types/device.ts
@@ -1,6 +1,8 @@
 import { commonColor } from '/@/utils/color'
 import { NightLightsStateEnum, DistanceLimitStatus, ObstacleAvoidance } from './device-setting'
 import { AlarmModeEnum, BatteryStoreModeEnum, DroneBatteryStateEnum, FourGLinkStateEnum, SdrLinkStateEnum, LinkWorkModeEnum } from './airport-tsa'
+import { CameraMode } from '/@/types/live-stream'
+
 export interface DeviceValue {
   key: string; // 'domain-type-subtype'
   domain: string; // 表示一个领域,作为一个命名空间,暂时分 飞机类-0, 负载类-1,RC类-2,机场类-3 4种
@@ -20,10 +22,6 @@
 export enum DRONE_TYPE {
   M30 = 67,
   M300 = 60,
-  Phantom4 = 11,
-  Phantom4Pro = 18,
-  Phantom4RTK = 59,
-  Phantom4Advanced = 27,
   Mavic3EnterpriseAdvanced= 77,
 }
 
@@ -76,10 +74,6 @@
   M3T: `${DOMAIN.DRONE}-${DRONE_TYPE.Mavic3EnterpriseAdvanced}-${DEVICE_SUB_TYPE.ONE}`,
 
   M300: `${DOMAIN.DRONE}-${DRONE_TYPE.M300}-${DEVICE_SUB_TYPE.ZERO}`,
-  Phantom4: `${DOMAIN.DRONE}-${DRONE_TYPE.Phantom4}-${DEVICE_SUB_TYPE.ZERO}`,
-  Phantom4Pro: `${DOMAIN.DRONE}-${DRONE_TYPE.Phantom4Pro}-${DEVICE_SUB_TYPE.ZERO}`,
-  Phantom4RTK: `${DOMAIN.DRONE}-${DRONE_TYPE.Phantom4RTK}-${DEVICE_SUB_TYPE.ZERO}`,
-  Phantom4Advanced: `${DOMAIN.DRONE}-${DRONE_TYPE.Phantom4Advanced}-${DEVICE_SUB_TYPE.ZERO}`,
 
   FPV: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.FPV}-${DEVICE_SUB_TYPE.ZERO}`,
   H20: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.H20}-${DEVICE_SUB_TYPE.ZERO}`,
@@ -113,10 +107,6 @@
   [DEVICE_MODEL_KEY.M3T]: 'Mavic 3T',
   // [DEVICE_MODEL_KEY.M3M]: 'Mavic 3M',
   [DEVICE_MODEL_KEY.M300]: 'M300 RTK',
-  [DEVICE_MODEL_KEY.Phantom4]: 'Phantom 4',
-  [DEVICE_MODEL_KEY.Phantom4Pro]: 'Phantom 4 Pro',
-  [DEVICE_MODEL_KEY.Phantom4RTK]: 'Phantom 4 RTK',
-  [DEVICE_MODEL_KEY.Phantom4Advanced]: 'Phantom 4 Advanced',
 
   // payload
   [DEVICE_MODEL_KEY.FPV]: 'FPV',
@@ -141,6 +131,36 @@
 
   // dock
   [DEVICE_MODEL_KEY.Dock]: 'Dock',
+}
+
+// 控制权
+export enum ControlSource {
+  A = 'A',
+  B = 'B'
+}
+
+export interface PayloadInfo {
+  index: number,
+  model: string,
+  control_source?: ControlSource,
+  payload_sn?: string,
+  payload_index?: string,
+  payload_name?: string,
+}
+
+// 设备信息
+export interface OnlineDevice {
+  model: string,
+  callsign: string,
+  sn: string,
+  mode: number,
+  gateway: {
+    model: string,
+    callsign: string,
+    sn: string,
+    domain: string,
+  },
+  payload: PayloadInfo[]
 }
 
 // 固件升级类型
@@ -184,7 +204,7 @@
   children?: Device[],
   domain: number,
   type: number,
-  firmware_progress?: number, // 升级进度
+  firmware_progress?: string, // 升级进度
 }
 
 export interface DeviceStatus {
@@ -207,6 +227,7 @@
   is_dock: boolean,
   gateway_sn: string,
   gateway_callsign: string,
+  payloads: null | PayloadInfo [],
 }
 
 export interface GatewayOsd {
@@ -215,6 +236,24 @@
   longitude: number,
   latitude: number,
 }
+
+export interface OsdCameraLiveview {
+  bottom: number,
+  left: number,
+  right: number,
+  top: number,
+}
+export interface DeviceOsdCamera {
+  camera_mode: CameraMode,
+  payload_index: string,
+  photo_state: number,
+  record_time: number,
+  recording_state: number,
+  remain_photo_num: number,
+  remain_record_duration: number,
+  liveview_world_region: OsdCameraLiveview
+}
+
 export interface DeviceOsd {
   longitude: number,
   latitude: number,
@@ -242,6 +281,7 @@
   height_limit?: number;// 限高设置
   distance_limit_status?: DistanceLimitStatus;// 限远开关
   obstacle_avoidance?: ObstacleAvoidance;// 飞行器避障开关设置
+  cameras?: DeviceOsdCamera[]
 }
 
 export enum NetworkStateTypeEnum {
@@ -263,16 +303,16 @@
 }
 
 export enum DroneInDockEnum {
-  INSIDE, OUTSIDE
+  OUTSIDE, INSIDE
 }
 
 export interface DockBasicOsd {
-  network_state: {
+  network_state?: {
     type: NetworkStateTypeEnum,
     quality: number,
     rate: number,
   },
-  drone_charge_state: {
+  drone_charge_state?: {
     state: number,
     capacity_percent: number,
   },
@@ -285,7 +325,7 @@
   latitude: number,
   longitude: number,
   height: number,
-  alternate_land_point: {
+  alternate_land_point?: {
     latitude: number,
     longitude: number,
     height: number,
@@ -293,14 +333,14 @@
     is_configured: number
   }
   first_power_on: number,
-  positionState: {
+  positionState?: {
     gps_number: number,
     is_fixed: number,
     rtk_number: number,
     is_calibration: number,
     quality: number,
   },
-  storage: {
+  storage?: {
     total: number,
     used: number,
   },
@@ -308,25 +348,32 @@
   cover_state: number,
   supplement_light_state: number,
   emergency_stop_state: number,
-  air_conditioner: {
+  air_conditioner?: {
     air_conditioner_state: number,
     switch_time: number,
   }
   battery_store_mode?: BatteryStoreModeEnum; // 电池保养(存储)模式
   alarm_state?: AlarmModeEnum; // 机场声光报警状态
   putter_state: number,
-  sub_device: {
+  sub_device?: {
     device_sn?: string,
     device_model_key?: string,
     device_online_status: number,
     device_paired: number,
   },
+  // live_capacity?: LiveCapacity; // 直播能力
+  // live_status?: Array<LiveStatus>; // 直播状态
 }
-
+export enum DrcStateEnum {
+  DISCONNECT = 0,
+  CONNECTING = 1,
+  CONNECTED = 2
+}
 export interface DockLinkOsd {
+  drc_state: DrcStateEnum,
   flighttask_prepare_capacity: number,
   flighttask_step_code: number,
-  media_file_detail: {
+  media_file_detail?: {
     remain_upload: number
   },
   sdr: {
@@ -334,7 +381,7 @@
     down_quality: string,
     frequency_band: number,
   },
-  wireless_link?:{
+  wireless_link?:{ // 图传链路<会包括4G和sdr信息
     dongle_number: number, // dongle 数量
     ['4g_link_state']: FourGLinkStateEnum, // 4g_link_state
     sdr_link_state: SdrLinkStateEnum, // sdr链路连接状态
@@ -359,13 +406,13 @@
   job_number: number,
   acc_time: number,
   activation_time: number,
-  maintain_status: {
+  maintain_status?: {
     maintain_status_array: MaintainStatus[]
   }
   electric_supply_voltage: number,
   working_voltage: string,
   working_current: string,
-  backup_battery: {
+  backup_battery?: {
     voltage: number,
     temperature: number,
     switch: number,
@@ -379,7 +426,7 @@
 export interface DockOsd {
   basic_osd: DockBasicOsd,
   link_osd: DockLinkOsd,
-  work_osd: DockWorkOsd  
+  work_osd: DockWorkOsd
 }
 
 export enum EModeCode {
@@ -460,7 +507,7 @@
 }
 
 // TODO: 设备拓扑管理优化
-// 设备信息
+// 设备osd信息
 export interface DeviceInfoType {
   gateway: GatewayOsd, // 遥控器
   dock: DockOsd, // 机场
diff --git a/src/types/drc.ts b/src/types/drc.ts
new file mode 100644
index 0000000..e31b55f
--- /dev/null
+++ b/src/types/drc.ts
@@ -0,0 +1,71 @@
+export enum DRC_METHOD {
+  HEART_BEAT = 'heart_beat',
+  DRONE_CONTROL = 'drone_control', // 飞行控制-虚拟摇杆
+  DRONE_EMERGENCY_STOP = 'drone_emergency_stop', // 急停
+  OSD_INFO_PUSH = 'osd_info_push', // 高频osd信息上报
+  HSI_INFO_PUSH = 'hsi_info_push', // 避障信息上报
+  DELAY_TIME_INFO_PUSH = 'delay_info_push', // 图传链路延时信息上报
+}
+
+// 手动控制
+export interface DroneControlProtocol {
+  x?: number; // 水平方向速度,正值为A指令  负值为D指令 单位:m/s
+  y?: number; // 前进后退方向速度,正值为W指令  负值为S指令 单位:m/s
+  h?: number;// 上下高度值,正值为上升指令  负值为下降指令 单位:m
+  w?: number; // 机头角速度,正值为顺时针,负值为逆时针 单位:degree/s   (web端暂无此设计)
+  step_x?: number; // 水平方向步长
+  step_y?: number; // 前后方向步长
+  step_h?: number; // 高度方向步长
+  step_w?: number; // 机头转向步长
+  seq?: number; // 从0计时
+  freq?: number; // 指令发送频率
+  delay_time?: number; // 指令从发送到设备端接收可容忍的时间 发送频率+链路传输时长
+}
+
+// 低延时osd
+export interface DRCOsdInfo {
+  attitude_head: number;// 飞机姿态head角,单位:度
+  latitude: number;// 飞机经纬度
+  longitude: number;
+  altitude: number;
+  speed_x: number;
+  speed_y: number;
+  speed_z: number;
+  gimbal_pitch: number;// 云台pitch角
+  gimbal_roll: number;// 云台roll角
+  gimbal_yaw: number;// 云台yaw角
+}
+
+// 态势感知-HSI
+export interface DRCHsiInfo {
+  up_distance: number;// 上方的障碍物距离,单位:mm
+  down_distance: number;// 下方的障碍物距离,单位:mm
+  around_distances: number[]; // 水平方向观察点,分布在[0,360)区间,表示障碍物与飞机距离,单位为mm。 0对应机头方向正前方,顺时针分布,例如0度为机头正前方,90度为飞机正右方
+  up_enable: boolean; // 上视避障开关状态,true:已开启 false:已关闭
+  up_work: boolean; // 上视避障工作状态,true:正常工作 false:异常或离线
+  down_enable: boolean; // 下视避障开关状态,true:已开启 false:已关闭
+  down_work: boolean; // 下视避障工作状态,true:正常工作 false:异常或离线
+  left_enable: boolean; // 左视避障开关状态,true:已开启 false:已关闭
+  left_work: boolean; // 左视避障工作状态,true:正常工作 false:异常或离线
+  right_enable: boolean; // 右视避障开关状态,true:已开启 false:已关闭
+  right_work: boolean; // 右视避障工作状态,true:正常工作 false:异常或离线
+  front_enable: boolean; // 前视避障开关状态,true:已开启 false:已关闭
+  front_work: boolean; // 前视避障工作状态,true:正常工作 false:异常或离线
+  back_enable: boolean; // 后视避障开关状态,true:已开启 false:已关闭
+  back_work: boolean; // 后视避障工作状态,true:正常工作 false:异常或离线
+  vertical_enable: boolean; // 垂直方向综合开关状态,当本协议中上、下视开关状态均为true时,输出true:已开启,否则输出false:已关闭
+  vertical_work: boolean; // 垂直方向避障工作状态,当本协议中上、下视工作均为true时,输出true:正常工作,否则输出false:异常或离线
+  horizontal_enable: boolean; // 水平方向综合开关状态,当本协议中前、后、左、右、开关状态均为true时,输出true:已开启,否则输出false:已关闭
+  horizontal_work: boolean; // 水平方向避障工作综合状态,当本协议中前、后、左、右视工作均为true时,输出true:正常工作,否则输出false:异常或离线
+}
+
+export interface LiveViewDelayItem {
+  video_id: string;
+  liveview_delay_time: number;
+}
+
+// 链路时延信息
+export interface DRCDelayTimeInfo {
+  sdr_cmd_delay: number; // sdr链路命令延时,单位:ms
+  liveview_delay_list: LiveViewDelayItem[];
+}
diff --git a/src/types/drone-control.ts b/src/types/drone-control.ts
new file mode 100644
index 0000000..4c16619
--- /dev/null
+++ b/src/types/drone-control.ts
@@ -0,0 +1,68 @@
+import { ControlSource } from './device'
+import { LostControlActionInCommandFLight, WaylineLostControlActionInCommandFlight } from '/@/api/drone-control/drone'
+
+export enum ControlSourceChangeType {
+  Flight = 1,
+  Payload = 2,
+}
+
+// 控制权变化消息
+export interface ControlSourceChangeInfo {
+  sn: string,
+  type: ControlSourceChangeType,
+  control_source: ControlSource
+}
+
+// 飞向目标点结果
+export interface FlyToPointMessage {
+  sn: string,
+  result: number,
+  message: string,
+}
+
+// 一键起飞结果
+export interface TakeoffToPointMessage {
+  sn: string,
+  result: number,
+  message: string,
+}
+
+// 设备端退出drc模式
+export interface DrcModeExitNotifyMessage {
+  sn: string,
+  result: number,
+  message: string,
+}
+
+// 飞行控制模式状态
+export interface DrcStatusNotifyMessage {
+  sn: string,
+  result: number,
+  message: string,
+}
+
+export const WaylineLostControlActionInCommandFlightOptions = [
+  { label: 'Continue', value: WaylineLostControlActionInCommandFlight.CONTINUE },
+  { label: 'Execute Lost Action', value: WaylineLostControlActionInCommandFlight.EXEC_LOST_ACTION }
+]
+
+export const LostControlActionInCommandFLightOptions = [
+  { label: 'Return Home', value: LostControlActionInCommandFLight.RETURN_HOME },
+  { label: 'Hover', value: LostControlActionInCommandFLight.HOVER },
+  { label: 'Landing', value: LostControlActionInCommandFLight.Land }
+]
+
+// 云台重置模式
+export enum GimbalResetMode {
+  Recenter = 0,
+  Down = 1,
+  RecenterGimbalPan = 2,
+  PitchDown = 3,
+}
+
+export const GimbalResetModeOptions = [
+  { label: 'Gimbal Recenter', value: GimbalResetMode.Recenter },
+  { label: 'Gimbal down', value: GimbalResetMode.Down },
+  { label: 'Recenter Gimbal Pan', value: GimbalResetMode.RecenterGimbalPan },
+  { label: 'Gimbal Pitch Down', value: GimbalResetMode.PitchDown }
+]
diff --git a/src/types/enums.ts b/src/types/enums.ts
index 9dc6365..43248fa 100644
--- a/src/types/enums.ts
+++ b/src/types/enums.ts
@@ -115,7 +115,14 @@
     DeviceUpgrade = 'ota_progress', // 设备升级
 
     // 设备日志
-    DeviceLogUploadProgress = 'fileupload_progress' // 设备日志上传上传
+    DeviceLogUploadProgress = 'fileupload_progress', // 设备日志上传
+
+    // 飞行指令消息
+    ControlSourceChange = 'control_source_change', // 控制权更新
+    FlyToPointProgress = 'fly_to_point_progress', // 飞向目标点
+    TakeoffToPointProgress = 'takeoff_to_point_progress', // 一键起飞
+    JoystickInvalidNotify = 'joystick_invalid_notify', // 设备端退出drc模式
+    DrcStatusNotify = 'drc_status_notify', // 飞行控制模式状态
 }
 
 export enum EDeviceTypeName {
diff --git a/src/types/live-stream.ts b/src/types/live-stream.ts
index 2103910..4a02799 100644
--- a/src/types/live-stream.ts
+++ b/src/types/live-stream.ts
@@ -53,3 +53,74 @@
     RTSP = 'RTSP',
     GB28181 = 'GB28181'
 }
+
+export enum CameraMode {
+    Photo = 0, // 拍照
+    Video = 1, // 录像
+}
+
+// 镜头类型
+export enum VideoType {
+    NORMAL = 'normal',
+    WIDE = 'wide',
+    ZOOM = 'zoom',
+    IR = 'ir'
+}
+
+// 镜头类型
+export enum CameraType {
+    WIDE = 'wide',
+    ZOOM = 'zoom',
+    IR = 'ir'
+}
+
+export const CameraTypeOptions = [
+  { label: CameraType.WIDE, value: CameraType.WIDE },
+  { label: CameraType.ZOOM, value: CameraType.ZOOM },
+  { label: CameraType.IR, value: CameraType.IR },
+]
+
+export const ZoomCameraTypeOptions = [
+  { label: CameraType.ZOOM, value: CameraType.ZOOM },
+  { label: CameraType.IR, value: CameraType.IR },
+]
+
+export interface VideoListItem {
+    video_index: string;
+    video_type: VideoType;
+    switchable_video_types?: Array<VideoType>;
+}
+
+export interface CameraListItem {
+    available_video_number: number;
+    camera_index: string;
+    camera_name: string;
+    coexist_video_number_max: number;
+    video_list: VideoListItem[];
+    // 自定义
+    switchCamera?: boolean;
+    content?: string;
+    // 该camera由哪个控上报的
+    camera_carrier_sns?: string[];
+}
+
+export interface DeviceListItem {
+    sn: string;
+    available_video_number: number;
+    coexist_video_number_max: number;
+    camera_list: CameraListItem[];
+}
+
+// export interface LiveCapacity {
+//     available_video_number: number;
+//     coexist_video_number_max: number;
+//     device_list: DeviceListItem[];
+// }
+
+// export interface LiveStatus {
+//     live_time: number; // 直播时间 该路码流已推流时间 unit: s
+//     live_trendline: number; // 直播带宽的使用状态 代表直播性能趋势,0-4表示overuse,其中,数值越小,表示overuse程度越大,5表示normal状态,6~10表示underuse,其中,数值越大,表示有更多比例的带宽未能充分利用
+//     video_id: string; // 直播码流标识符 某路在推视频码流的标识符,格式为 #{uav_sn}/#{camera_id}/#{video_index}
+//     video_quality: number; // 直播码流的质量 0: 自动, 1: 流畅, 2: 高清, 3: 超清
+//     error_status?: number; // 设备端当前状态,是错误码,需要匹配到文案上
+//   }
diff --git a/src/types/task.ts b/src/types/task.ts
index cd5d97a..a37fe73 100644
--- a/src/types/task.ts
+++ b/src/types/task.ts
@@ -37,6 +37,7 @@
   Success = 3, // 完成
   CanCel = 4, // 取消
   Fail = 5, // 失败
+  Paused = 6, // 暂停
 }
 
 export const TaskStatusMap = {
@@ -45,6 +46,8 @@
   [TaskStatus.Success]: 'Task completed',
   [TaskStatus.CanCel]: 'Task canceled',
   [TaskStatus.Fail]: 'Task failed',
+  [TaskStatus.Paused]: 'Paused',
+
 }
 
 export const TaskStatusColor = {
@@ -53,6 +56,7 @@
   [TaskStatus.Success]: commonColor.NORMAL,
   [TaskStatus.CanCel]: commonColor.FAIL,
   [TaskStatus.Fail]: commonColor.FAIL,
+  [TaskStatus.Paused]: commonColor.BLUE,
 }
 
 // 任务执行 ws 消息状态
@@ -93,7 +97,7 @@
   [TaskProgressStatus.Failed]: TaskStatus.Fail,
   [TaskProgressStatus.Canceled]: TaskStatus.CanCel,
   [TaskProgressStatus.Timeout]: TaskStatus.Fail,
-  [TaskProgressStatus.Paused]: TaskStatus.Wait,
+  [TaskProgressStatus.Paused]: TaskStatus.Paused,
 }
 
 // 根据媒体文件上传进度信息,前端自己判断出的状态
diff --git a/yarn.lock b/yarn.lock
index 9a9d2c5..5d27955 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -819,6 +819,11 @@
     "mixin-deep" "^1.2.0"
     "pascalcase" "^0.1.1"
 
+"base64-js@^1.3.1":
+  "integrity" "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
+  "resolved" "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz"
+  "version" "1.5.1"
+
 "big-integer@^1.6.17":
   "integrity" "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg=="
   "resolved" "https://registry.npmmirror.com/big-integer/download/big-integer-1.6.51.tgz"
@@ -841,6 +846,15 @@
   dependencies:
     "buffers" "~0.1.1"
     "chainsaw" "~0.1.0"
+
+"bl@^4.0.2":
+  "integrity" "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="
+  "resolved" "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz"
+  "version" "4.1.0"
+  dependencies:
+    "buffer" "^5.5.0"
+    "inherits" "^2.0.4"
+    "readable-stream" "^3.4.0"
 
 "bluebird@^3.5.0":
   "integrity" "sha1-nyKcFb4nJFT/qXOs4NvueaGww28="
@@ -899,10 +913,23 @@
     "node-releases" "^2.0.1"
     "picocolors" "^1.0.0"
 
+"buffer-from@^1.0.0":
+  "integrity" "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
+  "resolved" "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz"
+  "version" "1.1.2"
+
 "buffer-indexof-polyfill@~1.0.0":
   "integrity" "sha1-0nMhNcWZnGSyd/z5savjSYJUcpw="
   "resolved" "https://registry.npm.taobao.org/buffer-indexof-polyfill/download/buffer-indexof-polyfill-1.0.2.tgz"
   "version" "1.0.2"
+
+"buffer@^5.5.0":
+  "integrity" "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="
+  "resolved" "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz"
+  "version" "5.7.1"
+  dependencies:
+    "base64-js" "^1.3.1"
+    "ieee754" "^1.1.13"
 
 "buffers@~0.1.1":
   "integrity" "sha1-skV5w77U1tOWru5tmorn9Ugqt7s="
@@ -1079,6 +1106,14 @@
   "resolved" "https://registry.npmmirror.com/commander/download/commander-7.2.0.tgz"
   "version" "7.2.0"
 
+"commist@^1.0.0":
+  "integrity" "sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg=="
+  "resolved" "https://registry.npmmirror.com/commist/-/commist-1.1.0.tgz"
+  "version" "1.1.0"
+  dependencies:
+    "leven" "^2.1.0"
+    "minimist" "^1.1.0"
+
 "component-emitter@^1.2.1":
   "integrity" "sha1-FuQHD7qK4ptnnyIVhT7hgasuq8A="
   "resolved" "https://registry.npm.taobao.org/component-emitter/download/component-emitter-1.3.0.tgz"
@@ -1093,6 +1128,16 @@
   "integrity" "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
   "resolved" "https://registry.npm.taobao.org/concat-map/download/concat-map-0.0.1.tgz"
   "version" "0.0.1"
+
+"concat-stream@^2.0.0":
+  "integrity" "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A=="
+  "resolved" "https://registry.npmmirror.com/concat-stream/-/concat-stream-2.0.0.tgz"
+  "version" "2.0.0"
+  dependencies:
+    "buffer-from" "^1.0.0"
+    "inherits" "^2.0.3"
+    "readable-stream" "^3.0.2"
+    "typedarray" "^0.0.6"
 
 "constant-case@^3.0.4":
   "integrity" "sha1-O4Sprq9M8x7EXmv13pG9+wWJ+vE="
@@ -1216,7 +1261,7 @@
   dependencies:
     "ms" "^2.1.1"
 
-"debug@^4.0.1", "debug@^4.1.0", "debug@^4.1.1", "debug@^4.3.2", "debug@^4.3.3":
+"debug@^4.0.1", "debug@^4.1.0", "debug@^4.1.1", "debug@^4.3.1", "debug@^4.3.2", "debug@^4.3.3":
   "integrity" "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q=="
   "resolved" "https://registry.npmmirror.com/debug/download/debug-4.3.3.tgz"
   "version" "4.3.3"
@@ -1366,6 +1411,16 @@
   dependencies:
     "readable-stream" "^2.0.2"
 
+"duplexify@^4.1.1":
+  "integrity" "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw=="
+  "resolved" "https://registry.npmmirror.com/duplexify/-/duplexify-4.1.2.tgz"
+  "version" "4.1.2"
+  dependencies:
+    "end-of-stream" "^1.4.1"
+    "inherits" "^2.0.3"
+    "readable-stream" "^3.1.1"
+    "stream-shift" "^1.0.0"
+
 "electron-to-chromium@^1.4.17":
   "integrity" "sha512-PO3kEfcxPrti/4STbXvCkNIF4fgWvCKl2508e6UI7KomCDffpIfeBZLXsh5DK/XGsjUw3kwq6WEsi0MJTlGAdg=="
   "resolved" "https://registry.npmmirror.com/electron-to-chromium/download/electron-to-chromium-1.4.43.tgz"
@@ -1380,6 +1435,13 @@
   "integrity" "sha1-VXBmIEatKeLpFucariYKvf9Pang="
   "resolved" "https://registry.npm.taobao.org/emojis-list/download/emojis-list-3.0.0.tgz"
   "version" "3.0.0"
+
+"end-of-stream@^1.1.0", "end-of-stream@^1.4.1":
+  "integrity" "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="
+  "resolved" "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz"
+  "version" "1.4.4"
+  dependencies:
+    "once" "^1.4.0"
 
 "enquirer@^2.3.5":
   "integrity" "sha1-Kn/l3WNKHkElqXXsmU/1RW3Dc00="
@@ -1728,6 +1790,11 @@
   "resolved" "https://registry.npm.taobao.org/etag/download/etag-1.8.1.tgz"
   "version" "1.8.1"
 
+"eventemitter3@^5.0.0":
+  "integrity" "sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg=="
+  "resolved" "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.0.tgz"
+  "version" "5.0.0"
+
 "expand-brackets@^2.1.4":
   "integrity" "sha1-t3c14xXOMPa27/D4OwQVGiJEliI="
   "resolved" "https://registry.npm.taobao.org/expand-brackets/download/expand-brackets-2.1.4.tgz"
@@ -1950,7 +2017,7 @@
   dependencies:
     "is-glob" "^4.0.1"
 
-"glob@^7.1.3":
+"glob@^7.1.3", "glob@^7.1.6":
   "integrity" "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q=="
   "resolved" "https://registry.npmmirror.com/glob/download/glob-7.2.0.tgz"
   "version" "7.2.0"
@@ -2081,6 +2148,14 @@
     "capital-case" "^1.0.4"
     "tslib" "^2.0.3"
 
+"help-me@^3.0.0":
+  "integrity" "sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ=="
+  "resolved" "https://registry.npmmirror.com/help-me/-/help-me-3.0.0.tgz"
+  "version" "3.0.0"
+  dependencies:
+    "glob" "^7.1.6"
+    "readable-stream" "^3.6.0"
+
 "htmlparser2@^3.8.3":
   "integrity" "sha1-vWedw/WYl7ajS7EHSchVu1OpOS8="
   "resolved" "https://registry.npmmirror.com/htmlparser2/download/htmlparser2-3.10.1.tgz?cache=0&sync_timestamp=1636640933377&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fhtmlparser2%2Fdownload%2Fhtmlparser2-3.10.1.tgz"
@@ -2092,6 +2167,11 @@
     "entities" "^1.1.1"
     "inherits" "^2.0.1"
     "readable-stream" "^3.1.1"
+
+"ieee754@^1.1.13":
+  "integrity" "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
+  "resolved" "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz"
+  "version" "1.2.1"
 
 "ignore@^4.0.6":
   "integrity" "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg=="
@@ -2134,7 +2214,7 @@
     "once" "^1.3.0"
     "wrappy" "1"
 
-"inherits@^2.0.1", "inherits@^2.0.3", "inherits@~2.0.0", "inherits@~2.0.3", "inherits@2":
+"inherits@^2.0.1", "inherits@^2.0.3", "inherits@^2.0.4", "inherits@~2.0.0", "inherits@~2.0.3", "inherits@2":
   "integrity" "sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w="
   "resolved" "https://registry.npm.taobao.org/inherits/download/inherits-2.0.4.tgz"
   "version" "2.0.4"
@@ -2402,6 +2482,11 @@
   "resolved" "https://registry.npmmirror.com/js-base64/download/js-base64-2.6.4.tgz"
   "version" "2.6.4"
 
+"js-sdsl@4.3.0":
+  "integrity" "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ=="
+  "resolved" "https://registry.npmmirror.com/js-sdsl/-/js-sdsl-4.3.0.tgz"
+  "version" "4.3.0"
+
 "js-tokens@^3.0.0 || ^4.0.0", "js-tokens@^4.0.0":
   "integrity" "sha1-GSA/tZmR35jjoocFDUZHzerzJJk="
   "resolved" "https://registry.nlark.com/js-tokens/download/js-tokens-4.0.0.tgz?cache=0&sync_timestamp=1619345098261&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fjs-tokens%2Fdownload%2Fjs-tokens-4.0.0.tgz"
@@ -2493,6 +2578,11 @@
   "integrity" "sha1-B8BQNKbDSfoG4k+jWqdttFgM5N0="
   "resolved" "https://registry.npm.taobao.org/kind-of/download/kind-of-6.0.3.tgz"
   "version" "6.0.3"
+
+"leven@^2.1.0":
+  "integrity" "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA=="
+  "resolved" "https://registry.npmmirror.com/leven/-/leven-2.1.0.tgz"
+  "version" "2.1.0"
 
 "levn@^0.4.1":
   "integrity" "sha1-rkViwAdHO5MqYgDUAyaN0v/8at4="
@@ -2635,7 +2725,7 @@
   dependencies:
     "brace-expansion" "^1.1.7"
 
-"minimist@^1.2.0", "minimist@^1.2.5":
+"minimist@^1.1.0", "minimist@^1.2.0", "minimist@^1.2.5":
   "integrity" "sha1-Z9ZgFLZqaoqqDAg8X9WN9OTpdgI="
   "resolved" "https://registry.npm.taobao.org/minimist/download/minimist-1.2.5.tgz"
   "version" "1.2.5"
@@ -2664,6 +2754,38 @@
   "integrity" "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
   "resolved" "https://registry.npmmirror.com/moment/download/moment-2.29.1.tgz"
   "version" "2.29.1"
+
+"mqtt-packet@^6.8.0":
+  "integrity" "sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA=="
+  "resolved" "https://registry.npmmirror.com/mqtt-packet/-/mqtt-packet-6.10.0.tgz"
+  "version" "6.10.0"
+  dependencies:
+    "bl" "^4.0.2"
+    "debug" "^4.1.1"
+    "process-nextick-args" "^2.0.1"
+
+"mqtt@^4.3.7":
+  "integrity" "sha512-ew3qwG/TJRorTz47eW46vZ5oBw5MEYbQZVaEji44j5lAUSQSqIEoul7Kua/BatBW0H0kKQcC9kwUHa1qzaWHSw=="
+  "resolved" "https://registry.npmmirror.com/mqtt/-/mqtt-4.3.7.tgz"
+  "version" "4.3.7"
+  dependencies:
+    "commist" "^1.0.0"
+    "concat-stream" "^2.0.0"
+    "debug" "^4.1.1"
+    "duplexify" "^4.1.1"
+    "help-me" "^3.0.0"
+    "inherits" "^2.0.3"
+    "lru-cache" "^6.0.0"
+    "minimist" "^1.2.5"
+    "mqtt-packet" "^6.8.0"
+    "number-allocator" "^1.0.9"
+    "pump" "^3.0.0"
+    "readable-stream" "^3.6.0"
+    "reinterval" "^1.1.0"
+    "rfdc" "^1.3.0"
+    "split2" "^3.1.0"
+    "ws" "^7.5.5"
+    "xtend" "^4.0.2"
 
 "ms@^2.1.1", "ms@2.1.2":
   "integrity" "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk="
@@ -2737,6 +2859,14 @@
   dependencies:
     "boolbase" "^1.0.0"
 
+"number-allocator@^1.0.9":
+  "integrity" "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA=="
+  "resolved" "https://registry.npmmirror.com/number-allocator/-/number-allocator-1.0.14.tgz"
+  "version" "1.0.14"
+  dependencies:
+    "debug" "^4.3.1"
+    "js-sdsl" "4.3.0"
+
 "object-assign@^4", "object-assign@^4.1.0":
   "integrity" "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
   "resolved" "https://registry.npm.taobao.org/object-assign/download/object-assign-4.1.1.tgz"
@@ -2799,7 +2929,7 @@
   "resolved" "https://registry.npm.taobao.org/omit.js/download/omit.js-2.0.2.tgz"
   "version" "2.0.2"
 
-"once@^1.3.0":
+"once@^1.3.0", "once@^1.3.1", "once@^1.4.0":
   "integrity" "sha1-WDsap3WWHUsROsF9nFC6753Xa9E="
   "resolved" "https://registry.npm.taobao.org/once/download/once-1.4.0.tgz"
   "version" "1.4.0"
@@ -2980,7 +3110,7 @@
   "resolved" "https://registry.npm.taobao.org/prelude-ls/download/prelude-ls-1.2.1.tgz"
   "version" "1.2.1"
 
-"process-nextick-args@~2.0.0":
+"process-nextick-args@^2.0.1", "process-nextick-args@~2.0.0":
   "integrity" "sha1-eCDZsWEgzFXKmud5JoCufbptf+I="
   "resolved" "https://registry.npm.taobao.org/process-nextick-args/download/process-nextick-args-2.0.1.tgz"
   "version" "2.0.1"
@@ -2989,6 +3119,14 @@
   "integrity" "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="
   "resolved" "https://registry.npmmirror.com/progress/download/progress-2.0.3.tgz"
   "version" "2.0.3"
+
+"pump@^3.0.0":
+  "integrity" "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww=="
+  "resolved" "https://registry.npmmirror.com/pump/-/pump-3.0.0.tgz"
+  "version" "3.0.0"
+  dependencies:
+    "end-of-stream" "^1.1.0"
+    "once" "^1.3.1"
 
 "punycode@^2.1.0":
   "integrity" "sha1-tYsBCsQMIsVldhbI0sLALHv0eew="
@@ -3031,7 +3169,7 @@
     "string_decoder" "~1.1.1"
     "util-deprecate" "~1.0.1"
 
-"readable-stream@^3.1.1":
+"readable-stream@^3.0.0", "readable-stream@^3.0.2", "readable-stream@^3.1.1", "readable-stream@^3.4.0", "readable-stream@^3.6.0":
   "integrity" "sha1-M3u9o63AcGvT4CRCaihtS0sskZg="
   "resolved" "https://registry.npm.taobao.org/readable-stream/download/readable-stream-3.6.0.tgz"
   "version" "3.6.0"
@@ -3083,6 +3221,11 @@
   "resolved" "https://registry.nlark.com/regexpp/download/regexpp-3.2.0.tgz"
   "version" "3.2.0"
 
+"reinterval@^1.1.0":
+  "integrity" "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ=="
+  "resolved" "https://registry.npmmirror.com/reinterval/-/reinterval-1.1.0.tgz"
+  "version" "1.1.0"
+
 "repeat-element@^1.1.2":
   "integrity" "sha1-vmgVIIR6tYx1aKx1+/rSjtQtOek="
   "resolved" "https://registry.nlark.com/repeat-element/download/repeat-element-1.1.4.tgz"
@@ -3131,6 +3274,11 @@
   "integrity" "sha1-kNo4Kx4SbvwCFG6QhFqI2xKSXXY="
   "resolved" "https://registry.npm.taobao.org/reusify/download/reusify-1.0.4.tgz"
   "version" "1.0.4"
+
+"rfdc@^1.3.0":
+  "integrity" "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA=="
+  "resolved" "https://registry.npmmirror.com/rfdc/-/rfdc-1.3.0.tgz"
+  "version" "1.3.0"
 
 "rimraf@^3.0.2":
   "integrity" "sha1-8aVAK6YiCtUswSgrrBrjqkn9Bho="
@@ -3375,6 +3523,13 @@
   dependencies:
     "extend-shallow" "^3.0.0"
 
+"split2@^3.1.0":
+  "integrity" "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg=="
+  "resolved" "https://registry.npmmirror.com/split2/-/split2-3.2.2.tgz"
+  "version" "3.2.2"
+  dependencies:
+    "readable-stream" "^3.0.0"
+
 "sprintf-js@~1.0.2":
   "integrity" "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
   "resolved" "https://registry.npm.taobao.org/sprintf-js/download/sprintf-js-1.0.3.tgz"
@@ -3392,6 +3547,11 @@
   dependencies:
     "define-property" "^0.2.5"
     "object-copy" "^0.1.0"
+
+"stream-shift@^1.0.0":
+  "integrity" "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ=="
+  "resolved" "https://registry.npmmirror.com/stream-shift/-/stream-shift-1.0.1.tgz"
+  "version" "1.0.1"
 
 "strict-uri-encode@^1.0.0":
   "integrity" "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
@@ -3635,6 +3795,11 @@
   "integrity" "sha1-G/IH9LKPkVg2ZstfvTJ4hzAc1fQ="
   "resolved" "https://registry.npmmirror.com/type-fest/download/type-fest-0.20.2.tgz"
   "version" "0.20.2"
+
+"typedarray@^0.0.6":
+  "integrity" "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
+  "resolved" "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz"
+  "version" "0.0.6"
 
 "typescript@^4.5.4", "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta":
   "integrity" "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg=="
@@ -3933,6 +4098,16 @@
   "resolved" "https://registry.nlark.com/wrappy/download/wrappy-1.0.2.tgz?cache=0&sync_timestamp=1619133505879&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fwrappy%2Fdownload%2Fwrappy-1.0.2.tgz"
   "version" "1.0.2"
 
+"ws@^7.5.5":
+  "integrity" "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q=="
+  "resolved" "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz"
+  "version" "7.5.9"
+
+"xtend@^4.0.2":
+  "integrity" "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
+  "resolved" "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz"
+  "version" "4.0.2"
+
 "yallist@^4.0.0":
   "integrity" "sha1-m7knkNnA7/7GO+c1GeEaNQGaOnI="
   "resolved" "https://registry.npm.taobao.org/yallist/download/yallist-4.0.0.tgz"

--
Gitblit v1.9.3