DroneControlPanel.vue 28 KB

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