sean.zhou
2023-04-25 f3e92fa13107b569e0aa4d3ffff013fd2983660b
initial v1.4.0
25 files modified
2 files renamed
18 files added
3226 ■■■■■ changed files
package-lock.json 453 ●●●●● patch | view | raw | blame | history
package.json 5 ●●●●● patch | view | raw | blame | history
src/api/drc.ts 58 ●●●●● patch | view | raw | blame | history
src/api/drone-control/drone.ts 58 ●●●●● patch | view | raw | blame | history
src/api/drone-control/payload.ts 93 ●●●●● patch | view | raw | blame | history
src/api/wayline.ts 23 ●●●●● patch | view | raw | blame | history
src/components/GMap.vue 54 ●●●●● patch | view | raw | blame | history
src/components/devices/device-hms/DeviceHmsDrawer.vue 16 ●●●●● patch | view | raw | blame | history
src/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue 2 ●●● patch | view | raw | blame | history
src/components/g-map/DeviceSettingBox.vue 4 ●●●● patch | view | raw | blame | history
src/components/g-map/DockControlPanel.vue 10 ●●●● patch | view | raw | blame | history
src/components/g-map/DroneControlInfoPanel.vue 34 ●●●●● patch | view | raw | blame | history
src/components/g-map/DroneControlPanel.vue 756 ●●●●● patch | view | raw | blame | history
src/components/g-map/DroneControlPopover.vue 115 ●●●●● patch | view | raw | blame | history
src/components/g-map/use-connect-mqtt.ts 71 ●●●●● patch | view | raw | blame | history
src/components/g-map/use-device-setting.ts patch | view | raw | blame | history
src/components/g-map/use-dock-control.ts 20 ●●●●● patch | view | raw | blame | history
src/components/g-map/use-drone-control-mqtt-event.ts 61 ●●●●● patch | view | raw | blame | history
src/components/g-map/use-drone-control-ws-event.ts 97 ●●●●● patch | view | raw | blame | history
src/components/g-map/use-drone-control.ts 40 ●●●●● patch | view | raw | blame | history
src/components/g-map/use-manual-control.ts 162 ●●●●● patch | view | raw | blame | history
src/components/g-map/use-mqtt.ts 132 ●●●●● patch | view | raw | blame | history
src/components/g-map/use-payload-control.ts 120 ●●●●● patch | view | raw | blame | history
src/components/task/CreatePlan.vue 3 ●●●● patch | view | raw | blame | history
src/components/task/TaskPanel.vue 59 ●●●● patch | view | raw | blame | history
src/event-bus/index.ts 2 ●●●●● patch | view | raw | blame | history
src/hooks/use-g-map-tsa.ts 3 ●●●● patch | view | raw | blame | history
src/mqtt/config.ts 12 ●●●●● patch | view | raw | blame | history
src/mqtt/index.ts 117 ●●●●● patch | view | raw | blame | history
src/pages/page-web/projects/Firmwares.vue 6 ●●●●● patch | view | raw | blame | history
src/pages/page-web/projects/devices.vue 17 ●●●● patch | view | raw | blame | history
src/pages/page-web/projects/dock.vue 2 ●●● patch | view | raw | blame | history
src/pages/page-web/projects/livestream.vue 5 ●●●●● patch | view | raw | blame | history
src/pages/page-web/projects/tsa.vue 43 ●●●●● patch | view | raw | blame | history
src/pages/page-web/projects/workspace.vue 9 ●●●●● patch | view | raw | blame | history
src/shims-mqtt.d.ts 4 ●●●● patch | view | raw | blame | history
src/store/index.ts 22 ●●●● patch | view | raw | blame | history
src/types/device-cmd.ts 21 ●●●●● patch | view | raw | blame | history
src/types/device.ts 103 ●●●● patch | view | raw | blame | history
src/types/drc.ts 71 ●●●●● patch | view | raw | blame | history
src/types/drone-control.ts 68 ●●●●● patch | view | raw | blame | history
src/types/enums.ts 9 ●●●● patch | view | raw | blame | history
src/types/live-stream.ts 71 ●●●●● patch | view | raw | blame | history
src/types/task.ts 6 ●●●● patch | view | raw | blame | history
yarn.lock 189 ●●●●● patch | view | raw | blame | history
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="
    }
  }
}
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",
src/api/drc.ts
New file
@@ -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
}
src/api/drone-control/drone.ts
New file
@@ -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
}
src/api/drone-control/payload.ts
New file
@@ -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
}
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`
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;
}
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 {
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>
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<{
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 控制指令
src/components/g-map/DroneControlInfoPanel.vue
New file
@@ -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>
src/components/g-map/DroneControlPanel.vue
New file
@@ -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>
src/components/g-map/DroneControlPopover.vue
New file
@@ -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>
src/components/g-map/use-connect-mqtt.ts
New file
@@ -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()
  })
}
src/components/g-map/use-device-setting.ts
src/components/g-map/use-dock-control.ts
File was renamed from src/components/g-map/useDockControl.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,
  }
}
src/components/g-map/use-drone-control-mqtt-event.ts
New file
@@ -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
  }
}
src/components/g-map/use-drone-control-ws-event.ts
New file
@@ -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
  }
}
src/components/g-map/use-drone-control.ts
New file
@@ -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
  }
}
src/components/g-map/use-manual-control.ts
New file
@@ -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,
  }
}
src/components/g-map/use-mqtt.ts
New file
@@ -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,
  }
}
src/components/g-map/use-payload-control.ts
New file
@@ -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,
  }
}
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) {
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 {
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>()
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()
src/mqtt/config.ts
New file
@@ -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
}
src/mqtt/index.ts
New file
@@ -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,
}
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)
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)
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
    }
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
}
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)
}
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
  }
src/shims-mqtt.d.ts
New file
@@ -0,0 +1,4 @@
declare module 'mqtt/dist/mqtt.min' {
  import MQTT from 'mqtt'
  export = MQTT
}
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> = {
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,
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, // 机场
src/types/drc.ts
New file
@@ -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[];
}
src/types/drone-control.ts
New file
@@ -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 }
]
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 {
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; // 设备端当前状态,是错误码,需要匹配到文案上
//   }
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,
}
// 根据媒体文件上传进度信息,前端自己判断出的状态
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"