DroneControlPanel.vue 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  1. <template>
  2. <div class="drone-control-wrapper">
  3. <div class="drone-control-header">
  4. 无人机飞行控制
  5. </div>
  6. <div class="drone-control-button">
  7. <Button style="margin-right: 10px;" :ghost="!flightController" size="small" @click="onClickFightControl">
  8. {{ flightController ? '退出远程控制' : '进入远程控制' }}
  9. </Button>
  10. <Button type="primary" size="small" danger ghost @click="handleEmergencyStop">
  11. <template #icon>
  12. <PauseCircleOutlined />
  13. </template>
  14. <span>中止</span>
  15. </Button>
  16. </div>
  17. <div class="drone-control-more">
  18. <a-tooltip title="左旋转">
  19. <Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_Q)" @onmouseup="onMouseUp">
  20. <template #icon>
  21. <UndoOutlined />
  22. </template>
  23. <span class="word">Q</span>
  24. </Button>
  25. </a-tooltip>
  26. <a-tooltip title="前进">
  27. <Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_W)" @onmouseup="onMouseUp">
  28. <template #icon>
  29. <UpOutlined />
  30. </template>
  31. <span class="word">W</span>
  32. </Button>
  33. </a-tooltip>
  34. <a-tooltip title="右旋转">
  35. <Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_E)" @onmouseup="onMouseUp">
  36. <template #icon>
  37. <RedoOutlined />
  38. </template>
  39. <span class="word">E</span>
  40. </Button>
  41. </a-tooltip>
  42. <a-tooltip title="上升">
  43. <Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_C)" @onmouseup="onMouseUp">
  44. <template #icon>
  45. <ArrowUpOutlined />
  46. </template>
  47. <span class="word">C</span>
  48. </Button>
  49. </a-tooltip>
  50. </div>
  51. <div class="drone-control-more">
  52. <a-tooltip title="左平移">
  53. <Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_A)" @onmouseup="onMouseUp">
  54. <template #icon>
  55. <LeftOutlined />
  56. </template>
  57. <span class="word">A</span>
  58. </Button>
  59. </a-tooltip>
  60. <a-tooltip title="后退">
  61. <Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_S)" @onmouseup="onMouseUp">
  62. <template #icon>
  63. <DownOutlined />
  64. </template>
  65. <span class="word">S</span>
  66. </Button>
  67. </a-tooltip>
  68. <a-tooltip title="右平移">
  69. <Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_D)" @onmouseup="onMouseUp">
  70. <template #icon>
  71. <RightOutlined />
  72. </template>
  73. <span class="word">D</span>
  74. </Button>
  75. </a-tooltip>
  76. <a-tooltip title="下降">
  77. <Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_Z)" @onmouseup="onMouseUp">
  78. <template #icon>
  79. <ArrowDownOutlined />
  80. </template>
  81. <span class="word">Z</span>
  82. </Button>
  83. </a-tooltip>
  84. </div>
  85. <Button style="margin-right: 10px;" size="small" ghost @click="onShowTakeoffToPointPopover">
  86. <span>一键起飞</span>
  87. </Button>
  88. <DroneControlPopover :visible="flyToPointPopoverData.visible" :loading="flyToPointPopoverData.loading"
  89. @confirm="($event) => onFlyToConfirm(true)" @cancel="($event) => onFlyToConfirm(false)">
  90. <template #formContent>
  91. <div class="form-content">
  92. <div>
  93. <span class="form-label">latitude:</span>
  94. <a-input-number v-model:value="flyToPointPopoverData.latitude" />
  95. </div>
  96. <div>
  97. <span class="form-label">longitude:</span>
  98. <a-input-number v-model:value="flyToPointPopoverData.longitude" />
  99. </div>
  100. <div>
  101. <span class="form-label">height(m):</span>
  102. <a-input-number v-model:value="flyToPointPopoverData.height" />
  103. </div>
  104. </div>
  105. </template>
  106. <Button size="small" ghost @click="onShowFlyToPopover">
  107. <span>飞向目标点</span>
  108. </Button>
  109. </DroneControlPopover>
  110. <Button style="margin-left: 10px;" size="small" ghost @click="onStopFlyToPoint">
  111. <span>中止飞向目标点</span>
  112. </Button>
  113. <div style="margin-top: 10px;">
  114. <DroneControlPopover :visible="takeoffToPointPopoverData.visible" :loading="takeoffToPointPopoverData.loading"
  115. @confirm="($event) => onTakeoffToPointConfirm(true)" @cancel="($event) => onTakeoffToPointConfirm(false)">
  116. <template #formContent>
  117. <div class="form-content">
  118. <div>
  119. <span class="form-label">latitude:</span>
  120. <a-input-number v-model:value="takeoffToPointPopoverData.latitude" />
  121. </div>
  122. <div>
  123. <span class="form-label">longitude:</span>
  124. <a-input-number v-model:value="takeoffToPointPopoverData.longitude" />
  125. </div>
  126. <div>
  127. <span class="form-label">height(m):</span>
  128. <a-input-number v-model:value="takeoffToPointPopoverData.height" />
  129. </div>
  130. <div>
  131. <span class="form-label">Safe Takeoff Altitude(m):</span>
  132. <a-input-number v-model:value="takeoffToPointPopoverData.securityTakeoffHeight" />
  133. </div>
  134. <div>
  135. <span class="form-label">Return-to-Home Altitude(m):</span>
  136. <a-input-number v-model:value="takeoffToPointPopoverData.rthAltitude" />
  137. </div>
  138. <div>
  139. <span class="form-label">Lost Action:</span>
  140. <a-select v-model:value="takeoffToPointPopoverData.rcLostAction" style="width: 120px"
  141. :options="LostControlActionInCommandFLightOptions"></a-select>
  142. </div>
  143. <div>
  144. <span class="form-label">Wayline Lost Action:</span>
  145. <a-select v-model:value="takeoffToPointPopoverData.exitWaylineWhenRcLost" style="width: 120px"
  146. :options="WaylineLostControlActionInCommandFlightOptions"></a-select>
  147. </div>
  148. <div>
  149. <span class="form-label">Return-to-Home Mode:</span>
  150. <a-select v-model:value="takeoffToPointPopoverData.rthMode" style="width: 120px"
  151. :options="RthModeInCommandFlightOptions"></a-select>
  152. </div>
  153. <div>
  154. <span class="form-label">Commander Mode Lost Action:</span>
  155. <a-select v-model:value="takeoffToPointPopoverData.commanderModeLostAction" style="width: 120px"
  156. :options="CommanderModeLostActionInCommandFlightOptions"></a-select>
  157. </div>
  158. <div>
  159. <span class="form-label">Commander Flight Mode:</span>
  160. <a-select v-model:value="takeoffToPointPopoverData.commanderFlightMode" style="width: 120px"
  161. :options="CommanderFlightModeInCommandFlightOptions"></a-select>
  162. </div>
  163. <div>
  164. <span class="form-label">Commander Flight Height(m):</span>
  165. <a-input-number v-model:value="takeoffToPointPopoverData.commanderFlightHeight" />
  166. </div>
  167. </div>
  168. </template>
  169. <span v-for="(cmdItem) in cmdList" :key="cmdItem.cmdKey" class="control-cmd-item">
  170. <Button style="margin-right: 10px;" :loading="cmdItem.loading" size="small" ghost
  171. @click="sendControlCmd(cmdItem)">
  172. {{ cmdItem.operateText }}
  173. </Button>
  174. </span>
  175. </DroneControlPopover>
  176. </div>
  177. <!-- <div>
  178. <Button size="small" ghost @click="openLivestreamAgora">
  179. <span>Agora Live</span>
  180. </Button>
  181. <Button size="small" ghost @click="openLivestreamOthers">
  182. <span>RTMP/GB28181 Live</span>
  183. </Button>
  184. </div> -->
  185. <!-- <div class="drone-control-box">
  186. <div class="box">
  187. <div class="row">
  188. <Select v-model:value="payloadSelectInfo.value" style="width: 110px; marginRight: 5px"
  189. :options="payloadSelectInfo.options" @change="handlePayloadChange" />
  190. <div class="drone-control">
  191. <Button type="primary" size="small" @click="onAuthPayload">Payload Control</Button>
  192. </div>
  193. </div>
  194. <div class="row">
  195. <DroneControlPopover :visible="gimbalResetPopoverData.visible" :loading="gimbalResetPopoverData.loading"
  196. @confirm="($event) => onGimbalResetConfirm(true)" @cancel="($event) => onGimbalResetConfirm(false)">
  197. <template #formContent>
  198. <div class="form-content">
  199. <div>
  200. <span class="form-label">reset mode:</span>
  201. <a-select v-model:value="gimbalResetPopoverData.resetMode" style="width: 180px"
  202. :options="GimbalResetModeOptions"></a-select>
  203. </div>
  204. </div>
  205. </template>
  206. <Button size="small" ghost @click="onShowGimbalResetPopover">
  207. <span>Gimbal Reset</span>
  208. </Button>
  209. </DroneControlPopover>
  210. <Button size="small" ghost @click="onSwitchCameraMode">
  211. <span>Camera Mode Switch</span>
  212. </Button>
  213. </div>
  214. <div class="row">
  215. <Button size="small" ghost @click="onStartCameraRecording">
  216. <span>Start Recording</span>
  217. </Button>
  218. <Button size="small" ghost @click="onStopCameraRecording">
  219. <span>Stop Recording</span>
  220. </Button>
  221. </div>
  222. <div class="row">
  223. <Button size="small" ghost @click="onTakeCameraPhoto">
  224. <span>Take Photo</span>
  225. </Button>
  226. <DroneControlPopover :visible="zoomFactorPopoverData.visible" :loading="zoomFactorPopoverData.loading"
  227. @confirm="($event) => onZoomFactorConfirm(true)" @cancel="($event) => onZoomFactorConfirm(false)">
  228. <template #formContent>
  229. <div class="form-content">
  230. <div>
  231. <span class="form-label">camera type:</span>
  232. <a-select v-model:value="zoomFactorPopoverData.cameraType" style="width: 120px"
  233. :options="ZoomCameraTypeOptions"></a-select>
  234. </div>
  235. <div>
  236. <span class="form-label">zoom factor:</span>
  237. <a-input-number v-model:value="zoomFactorPopoverData.zoomFactor" :min="2" :max="200" />
  238. </div>
  239. </div>
  240. </template>
  241. <Button size="small" ghost @click="($event) => onShowZoomFactorPopover()">
  242. <span class="word" @click=";">Zoom</span>
  243. </Button>
  244. </DroneControlPopover>
  245. <DroneControlPopover :visible="cameraAimPopoverData.visible" :loading="cameraAimPopoverData.loading"
  246. @confirm="($event) => onCameraAimConfirm(true)" @cancel="($event) => onCameraAimConfirm(false)">
  247. <template #formContent>
  248. <div class="form-content">
  249. <div>
  250. <span class="form-label">camera type:</span>
  251. <a-select v-model:value="cameraAimPopoverData.cameraType" style="width: 120px"
  252. :options="CameraTypeOptions"></a-select>
  253. </div>
  254. <div>
  255. <span class="form-label">locked:</span>
  256. <a-switch v-model:checked="cameraAimPopoverData.locked" />
  257. </div>
  258. <div>
  259. <span class="form-label">x:</span>
  260. <a-input-number v-model:value="cameraAimPopoverData.x" :min="0" :max="1" />
  261. </div>
  262. <div>
  263. <span class="form-label">y:</span>
  264. <a-input-number v-model:value="cameraAimPopoverData.y" :min="0" :max="1" />
  265. </div>
  266. </div>
  267. </template>
  268. <Button size="small" ghost @click="($event) => onShowCameraAimPopover()">
  269. <span class="word" @click=";">AIM</span>
  270. </Button>
  271. </DroneControlPopover>
  272. </div>
  273. </div>
  274. </div> -->
  275. <!-- 信息提示 -->
  276. <!-- <DroneControlInfoPanel :message="drcInfo"></DroneControlInfoPanel> -->
  277. </div>
  278. </template>
  279. <script setup lang="ts">
  280. import { reactive, ref, watch, computed, onMounted, watchEffect } from 'vue'
  281. import { Select, message, Button } from 'ant-design-vue'
  282. import { PayloadInfo, DeviceInfoType, ControlSource, DeviceOsdCamera, DrcStateEnum } from '/@/types/device'
  283. import { useMyStore } from '/@/store'
  284. import { postDrcEnter, postDrcExit } from '/@/api/drc'
  285. import { useMqtt, DeviceTopicInfo } from './use-mqtt'
  286. import { DownOutlined, UpOutlined, LeftOutlined, RightOutlined, PauseCircleOutlined, UndoOutlined, RedoOutlined, ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons-vue'
  287. import { useManualControl, KeyCode } from './use-manual-control'
  288. import { usePayloadControl } from './use-payload-control'
  289. import { CameraMode, CameraType, CameraTypeOptions, ZoomCameraTypeOptions, CameraListItem } from '/@/types/live-stream'
  290. import { useDroneControlWsEvent } from './use-drone-control-ws-event'
  291. import { useDroneControlMqttEvent } from './use-drone-control-mqtt-event'
  292. import {
  293. postFlightAuth, LostControlActionInCommandFLight, WaylineLostControlActionInCommandFlight, ERthMode,
  294. ECommanderModeLostAction, ECommanderFlightMode
  295. } from '/@/api/drone-control/drone'
  296. import { useDroneControl } from './use-drone-control'
  297. import {
  298. GimbalResetMode, GimbalResetModeOptions, LostControlActionInCommandFLightOptions, WaylineLostControlActionInCommandFlightOptions,
  299. RthModeInCommandFlightOptions, CommanderModeLostActionInCommandFlightOptions, CommanderFlightModeInCommandFlightOptions
  300. } from '/@/types/drone-control'
  301. import DroneControlPopover from './DroneControlPopover.vue'
  302. import DroneControlInfoPanel from './DroneControlInfoPanel.vue'
  303. import { noDebugCmdList as baseCmdList, DeviceCmdItem, DeviceCmd } from '/@/types/device-cmd'
  304. import { useDockControl } from './use-dock-control'
  305. const props = defineProps<{
  306. sn: string,
  307. deviceInfo: DeviceInfoType,
  308. payloads: null | PayloadInfo[]
  309. }>()
  310. const store = useMyStore()
  311. const clientId = computed(() => {
  312. return store.state.clientId
  313. })
  314. const initCmdList = baseCmdList.map(cmdItem => Object.assign({}, cmdItem))
  315. const cmdList = ref(initCmdList)
  316. const { sendDockControlCmd } = useDockControl()
  317. async function sendControlCmd(cmdItem: DeviceCmdItem) {
  318. cmdItem.loading = true
  319. const result = await sendDockControlCmd({
  320. sn: props.sn,
  321. cmd: cmdItem.cmdKey,
  322. action: cmdItem.action
  323. }, false)
  324. if (result) {
  325. // message.success('操作成功')
  326. if (flightController.value) {
  327. exitFlightCOntrol()
  328. }
  329. } else {
  330. // message.error('操作失败')
  331. }
  332. cmdItem.loading = false
  333. }
  334. const { flyToPoint, stopFlyToPoint, takeoffToPoint } = useDroneControl()
  335. const MAX_SPEED = 14
  336. const flyToPointPopoverData = reactive({
  337. visible: false,
  338. loading: false,
  339. latitude: null as null | number,
  340. longitude: null as null | number,
  341. height: null as null | number,
  342. maxSpeed: MAX_SPEED,
  343. })
  344. function onShowFlyToPopover() {
  345. flyToPointPopoverData.visible = !flyToPointPopoverData.visible
  346. flyToPointPopoverData.loading = false
  347. flyToPointPopoverData.latitude = null
  348. flyToPointPopoverData.longitude = null
  349. flyToPointPopoverData.height = null
  350. const info = props.deviceInfo?.dock?.basic_osd;
  351. if (info) {
  352. flyToPointPopoverData.latitude = info.latitude;
  353. flyToPointPopoverData.longitude = info.longitude;
  354. }
  355. }
  356. async function onFlyToConfirm(confirm: boolean) {
  357. if (confirm) {
  358. if (!flyToPointPopoverData.height || !flyToPointPopoverData.latitude || !flyToPointPopoverData.longitude) {
  359. message.error('Input error')
  360. return
  361. }
  362. try {
  363. await flyToPoint(props.sn, {
  364. max_speed: flyToPointPopoverData.maxSpeed,
  365. points: [
  366. {
  367. latitude: flyToPointPopoverData.latitude,
  368. longitude: flyToPointPopoverData.longitude,
  369. height: flyToPointPopoverData.height
  370. }
  371. ]
  372. })
  373. } catch (error) {
  374. }
  375. }
  376. flyToPointPopoverData.visible = false
  377. }
  378. async function onStopFlyToPoint() {
  379. await stopFlyToPoint(props.sn)
  380. }
  381. const takeoffToPointPopoverData = reactive({
  382. visible: false,
  383. loading: false,
  384. latitude: null as null | number,
  385. longitude: null as null | number,
  386. height: null as null | number,
  387. securityTakeoffHeight: null as null | number,
  388. maxSpeed: MAX_SPEED,
  389. rthAltitude: null as null | number,
  390. rcLostAction: LostControlActionInCommandFLight.RETURN_HOME,
  391. exitWaylineWhenRcLost: WaylineLostControlActionInCommandFlight.EXEC_LOST_ACTION,
  392. rthMode: ERthMode.SETTING,
  393. commanderModeLostAction: ECommanderModeLostAction.CONTINUE,
  394. commanderFlightMode: ECommanderFlightMode.SETTING,
  395. commanderFlightHeight: null as null | number,
  396. })
  397. function onShowTakeoffToPointPopover() {
  398. takeoffToPointPopoverData.visible = !takeoffToPointPopoverData.visible
  399. takeoffToPointPopoverData.loading = false
  400. takeoffToPointPopoverData.latitude = null
  401. takeoffToPointPopoverData.longitude = null
  402. takeoffToPointPopoverData.height = null
  403. takeoffToPointPopoverData.securityTakeoffHeight = null
  404. takeoffToPointPopoverData.rthAltitude = null
  405. takeoffToPointPopoverData.rcLostAction = LostControlActionInCommandFLight.RETURN_HOME
  406. takeoffToPointPopoverData.exitWaylineWhenRcLost = WaylineLostControlActionInCommandFlight.EXEC_LOST_ACTION
  407. takeoffToPointPopoverData.rthMode = ERthMode.SETTING
  408. takeoffToPointPopoverData.commanderModeLostAction = ECommanderModeLostAction.CONTINUE
  409. takeoffToPointPopoverData.commanderFlightMode = ECommanderFlightMode.SETTING
  410. takeoffToPointPopoverData.commanderFlightHeight = null
  411. const info = props.deviceInfo?.dock?.basic_osd;
  412. if (info) {
  413. takeoffToPointPopoverData.latitude = info.latitude;
  414. takeoffToPointPopoverData.longitude = info.longitude;
  415. }
  416. }
  417. async function onTakeoffToPointConfirm(confirm: boolean) {
  418. if (confirm) {
  419. if (!takeoffToPointPopoverData.height ||
  420. !takeoffToPointPopoverData.latitude ||
  421. !takeoffToPointPopoverData.longitude ||
  422. !takeoffToPointPopoverData.securityTakeoffHeight ||
  423. !takeoffToPointPopoverData.rthAltitude ||
  424. !takeoffToPointPopoverData.commanderFlightHeight) {
  425. message.error('Input error')
  426. return
  427. }
  428. try {
  429. await takeoffToPoint(props.sn, {
  430. target_latitude: takeoffToPointPopoverData.latitude,
  431. target_longitude: takeoffToPointPopoverData.longitude,
  432. target_height: takeoffToPointPopoverData.height,
  433. security_takeoff_height: takeoffToPointPopoverData.securityTakeoffHeight,
  434. rth_altitude: takeoffToPointPopoverData.rthAltitude,
  435. max_speed: takeoffToPointPopoverData.maxSpeed,
  436. rc_lost_action: takeoffToPointPopoverData.rcLostAction,
  437. exit_wayline_when_rc_lost: takeoffToPointPopoverData.exitWaylineWhenRcLost,
  438. rth_mode: takeoffToPointPopoverData.rthMode,
  439. commander_mode_lost_action: takeoffToPointPopoverData.commanderModeLostAction,
  440. commander_flight_mode: takeoffToPointPopoverData.commanderFlightMode,
  441. commander_flight_height: takeoffToPointPopoverData.commanderFlightHeight,
  442. })
  443. } catch (error) {
  444. }
  445. }
  446. takeoffToPointPopoverData.visible = false
  447. }
  448. const deviceTopicInfo: DeviceTopicInfo = reactive({
  449. sn: props.sn,
  450. pubTopic: '',
  451. subTopic: ''
  452. })
  453. useMqtt(deviceTopicInfo)
  454. // 飞行控制
  455. // const drcState = computed(() => {
  456. // return store.state.deviceState?.dockInfo[props.sn]?.link_osd?.drc_state === DrcStateEnum.CONNECTED
  457. // })
  458. const flightController = ref(false)
  459. async function onClickFightControl() {
  460. if (flightController.value) {
  461. exitFlightCOntrol()
  462. return
  463. }
  464. enterFlightControl()
  465. }
  466. // 进入飞行控制
  467. async function enterFlightControl() {
  468. try {
  469. const { code, data } = await postDrcEnter({
  470. client_id: clientId.value,
  471. dock_sn: props.sn,
  472. })
  473. if (code === 0) {
  474. flightController.value = true
  475. if (data.sub && data.sub.length > 0) {
  476. deviceTopicInfo.subTopic = data.sub[0]
  477. }
  478. if (data.pub && data.pub.length > 0) {
  479. deviceTopicInfo.pubTopic = data.pub[0]
  480. }
  481. // 获取飞行控制权
  482. if (droneControlSource.value !== ControlSource.A) {
  483. await postFlightAuth(props.sn)
  484. }
  485. message.success('Get flight control successfully')
  486. }
  487. } catch (error: any) {
  488. }
  489. }
  490. // 退出飞行控制
  491. async function exitFlightCOntrol() {
  492. try {
  493. const { code } = await postDrcExit({
  494. client_id: clientId.value,
  495. dock_sn: props.sn,
  496. })
  497. if (code === 0) {
  498. flightController.value = false
  499. deviceTopicInfo.subTopic = ''
  500. deviceTopicInfo.pubTopic = ''
  501. message.success('退出飞行控制')
  502. }
  503. } catch (error: any) {
  504. }
  505. }
  506. // drc mqtt message
  507. const { drcInfo, errorInfo } = useDroneControlMqttEvent(props.sn)
  508. const {
  509. handleKeyup,
  510. handleEmergencyStop,
  511. resetControlState,
  512. } = useManualControl(deviceTopicInfo, flightController)
  513. function onMouseDown(type: KeyCode) {
  514. handleKeyup(type)
  515. }
  516. function onMouseUp() {
  517. resetControlState()
  518. }
  519. // 负载控制
  520. const payloadSelectInfo = {
  521. value: null as any,
  522. controlSource: undefined as undefined | ControlSource,
  523. options: [] as any,
  524. payloadIndex: '' as string,
  525. camera: undefined as undefined | DeviceOsdCamera // 当前负载osd信息
  526. }
  527. const handlePayloadChange = (value: string) => {
  528. const payload = props.payloads?.find(item => item.payload_sn === value)
  529. if (payload) {
  530. payloadSelectInfo.payloadIndex = payload.payload_index || ''
  531. payloadSelectInfo.controlSource = payload.control_source
  532. payloadSelectInfo.camera = undefined
  533. }
  534. }
  535. // function getCurrentCamera (cameraList: CameraListItem[], cameraIndex?: string):CameraListItem | null {
  536. // let camera = null
  537. // cameraList.forEach(item => {
  538. // if (item.camera_index === cameraIndex) {
  539. // camera = item
  540. // }
  541. // })
  542. // return camera
  543. // }
  544. // const currentCamera = computed(() => {
  545. // return getCurrentCamera(props.deviceInfo.dock.basic_osd.live_capacity?.device_list[0]?.camera_list as CameraListItem[], camera_index)
  546. // })
  547. // 更新负载信息
  548. watch(() => props.payloads, (payloads) => {
  549. if (payloads && payloads.length > 0) {
  550. payloadSelectInfo.value = payloads[0].payload_sn
  551. payloadSelectInfo.controlSource = payloads[0].control_source || ControlSource.B
  552. payloadSelectInfo.payloadIndex = payloads[0].payload_index || ''
  553. payloadSelectInfo.options = payloads.map(item => ({ label: item.payload_name, value: item.payload_sn }))
  554. payloadSelectInfo.camera = undefined
  555. } else {
  556. payloadSelectInfo.value = null
  557. payloadSelectInfo.controlSource = undefined
  558. payloadSelectInfo.options = []
  559. payloadSelectInfo.payloadIndex = ''
  560. payloadSelectInfo.camera = undefined
  561. }
  562. }, {
  563. immediate: true,
  564. deep: true
  565. })
  566. watch(() => props.deviceInfo.device, (droneOsd) => {
  567. if (droneOsd && droneOsd.cameras) {
  568. payloadSelectInfo.camera = droneOsd.cameras.find(item => item.payload_index === payloadSelectInfo.payloadIndex)
  569. } else {
  570. payloadSelectInfo.camera = undefined
  571. }
  572. }, {
  573. immediate: true,
  574. deep: true
  575. })
  576. // ws 消息通知
  577. const { droneControlSource, payloadControlSource } = useDroneControlWsEvent(props.sn, payloadSelectInfo.value)
  578. watch(() => payloadControlSource, (controlSource) => {
  579. payloadSelectInfo.controlSource = controlSource.value
  580. }, {
  581. immediate: true,
  582. deep: true
  583. })
  584. const {
  585. checkPayloadAuth,
  586. authPayload,
  587. resetGimbal,
  588. switchCameraMode,
  589. takeCameraPhoto,
  590. startCameraRecording,
  591. stopCameraRecording,
  592. changeCameraFocalLength,
  593. cameraAim,
  594. } = usePayloadControl()
  595. async function onAuthPayload() {
  596. const result = await authPayload(props.sn, payloadSelectInfo.payloadIndex)
  597. if (result) {
  598. payloadControlSource.value = ControlSource.A
  599. }
  600. }
  601. const gimbalResetPopoverData = reactive({
  602. visible: false,
  603. loading: false,
  604. resetMode: null as null | GimbalResetMode,
  605. })
  606. function onShowGimbalResetPopover() {
  607. gimbalResetPopoverData.visible = !gimbalResetPopoverData.visible
  608. gimbalResetPopoverData.loading = false
  609. gimbalResetPopoverData.resetMode = null
  610. }
  611. async function onGimbalResetConfirm(confirm: boolean) {
  612. if (confirm) {
  613. if (gimbalResetPopoverData.resetMode === null) {
  614. message.error('Please select reset mode')
  615. return
  616. }
  617. gimbalResetPopoverData.loading = true
  618. try {
  619. await resetGimbal(props.sn, {
  620. payload_index: payloadSelectInfo.payloadIndex,
  621. reset_mode: gimbalResetPopoverData.resetMode
  622. })
  623. } catch (err) {
  624. }
  625. }
  626. gimbalResetPopoverData.visible = false
  627. }
  628. async function onSwitchCameraMode() {
  629. if (!checkPayloadAuth(payloadSelectInfo.controlSource)) {
  630. return
  631. }
  632. const currentCameraMode = payloadSelectInfo.camera?.camera_mode
  633. await switchCameraMode(props.sn, {
  634. payload_index: payloadSelectInfo.payloadIndex,
  635. camera_mode: currentCameraMode === CameraMode.Photo ? CameraMode.Video : CameraMode.Photo
  636. })
  637. }
  638. async function onTakeCameraPhoto() {
  639. if (!checkPayloadAuth(payloadSelectInfo.controlSource)) {
  640. return
  641. }
  642. await takeCameraPhoto(props.sn, payloadSelectInfo.payloadIndex)
  643. }
  644. async function onStartCameraRecording() {
  645. if (!checkPayloadAuth(payloadSelectInfo.controlSource)) {
  646. return
  647. }
  648. await startCameraRecording(props.sn, payloadSelectInfo.payloadIndex)
  649. }
  650. async function onStopCameraRecording() {
  651. if (!checkPayloadAuth(payloadSelectInfo.controlSource)) {
  652. return
  653. }
  654. await stopCameraRecording(props.sn, payloadSelectInfo.payloadIndex)
  655. }
  656. const zoomFactorPopoverData = reactive({
  657. visible: false,
  658. loading: false,
  659. cameraType: null as null | CameraType,
  660. zoomFactor: null as null | number,
  661. })
  662. function onShowZoomFactorPopover() {
  663. zoomFactorPopoverData.visible = !zoomFactorPopoverData.visible
  664. zoomFactorPopoverData.loading = false
  665. zoomFactorPopoverData.cameraType = null
  666. zoomFactorPopoverData.zoomFactor = null
  667. }
  668. async function onZoomFactorConfirm(confirm: boolean) {
  669. if (confirm) {
  670. if (!zoomFactorPopoverData.zoomFactor || zoomFactorPopoverData.cameraType === null) {
  671. message.error('Please input Zoom Factor')
  672. return
  673. }
  674. zoomFactorPopoverData.loading = true
  675. try {
  676. await changeCameraFocalLength(props.sn, {
  677. payload_index: payloadSelectInfo.payloadIndex,
  678. camera_type: zoomFactorPopoverData.cameraType,
  679. zoom_factor: zoomFactorPopoverData.zoomFactor
  680. })
  681. } catch (err) {
  682. }
  683. }
  684. zoomFactorPopoverData.visible = false
  685. }
  686. const cameraAimPopoverData = reactive({
  687. visible: false,
  688. loading: false,
  689. cameraType: null as null | CameraType,
  690. locked: false,
  691. x: null as null | number,
  692. y: null as null | number,
  693. })
  694. function onShowCameraAimPopover() {
  695. cameraAimPopoverData.visible = !cameraAimPopoverData.visible
  696. cameraAimPopoverData.loading = false
  697. cameraAimPopoverData.cameraType = null
  698. cameraAimPopoverData.locked = false
  699. cameraAimPopoverData.x = null
  700. cameraAimPopoverData.y = null
  701. }
  702. function openLivestreamOthers() {
  703. store.commit('SET_LIVESTREAM_OTHERS_VISIBLE', true)
  704. }
  705. function openLivestreamAgora() {
  706. store.commit('SET_LIVESTREAM_AGORA_VISIBLE', true)
  707. }
  708. async function onCameraAimConfirm(confirm: boolean) {
  709. if (confirm) {
  710. if (cameraAimPopoverData.cameraType === null || cameraAimPopoverData.x === null || cameraAimPopoverData.y === null) {
  711. message.error('Input error')
  712. return
  713. }
  714. try {
  715. await cameraAim(props.sn, {
  716. payload_index: payloadSelectInfo.payloadIndex,
  717. camera_type: cameraAimPopoverData.cameraType,
  718. locked: cameraAimPopoverData.locked,
  719. x: cameraAimPopoverData.x,
  720. y: cameraAimPopoverData.y,
  721. })
  722. } catch (error) {
  723. }
  724. }
  725. cameraAimPopoverData.visible = false
  726. }
  727. watch(() => errorInfo, (errorInfo) => {
  728. if (errorInfo.value) {
  729. message.error(errorInfo.value)
  730. console.error(errorInfo.value)
  731. errorInfo.value = ''
  732. }
  733. }, {
  734. immediate: true,
  735. deep: true
  736. })
  737. </script>
  738. <style lang='scss' scoped>
  739. .drone-control-wrapper {
  740. padding: 10px 10px 20px;
  741. border-top: 1px solid #515151;
  742. .drone-control-header {
  743. font-size: 14px;
  744. font-weight: 600;
  745. margin-bottom: 10px;
  746. }
  747. .drone-control-button {
  748. margin-bottom: 10px;
  749. }
  750. .drone-control-more {
  751. width: 48%;
  752. display: flex;
  753. justify-content: space-between;
  754. margin-bottom: 10px;
  755. .ant-btn {
  756. margin-right: 0;
  757. height: 32px;
  758. }
  759. .word {
  760. width: 14px;
  761. margin-left: 2px;
  762. font-size: 14px;
  763. color: #aaa;
  764. }
  765. }
  766. }
  767. </style>