You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

634 lines
17 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. <template>
  2. <div class="debug_container">
  3. <div class="left_oper_container">
  4. <div class="bread_wrap">
  5. <t-breadcrumb>
  6. <t-breadcrumbItem>首页</t-breadcrumbItem>
  7. <t-breadcrumbItem>相机调试</t-breadcrumbItem>
  8. </t-breadcrumb>
  9. </div>
  10. <div class="operation_proposed">
  11. <div class="top_btns">
  12. <div class="two_btns">
  13. <div
  14. :class="isCameraOpen ? 'default_btn' : 'active_btn'"
  15. @click="handleStartCpature"
  16. >
  17. 打开相机
  18. </div>
  19. <div
  20. :class="isCameraOpen ? 'active_btn' : 'default_btn'"
  21. @click="handleStopCpature"
  22. >
  23. 关闭相机
  24. </div>
  25. </div>
  26. <div class="two_btns">
  27. <div
  28. :class="flashStatus ? 'default_btn' : 'active_btn'"
  29. @click="handleOpenFlashLight"
  30. >
  31. 打开闪光灯
  32. </div>
  33. <div
  34. :class="flashStatus ? 'active_btn' : 'default_btn'"
  35. @click="handleCloseFlashLight"
  36. >
  37. 关闭闪光灯
  38. </div>
  39. </div>
  40. <div class="active_btn" @click="handleGetMechanicalArmState">
  41. 获取机械臂坐标
  42. </div>
  43. </div>
  44. <div class="camera_param">
  45. <div class="slider_wrap mb50">
  46. <p class="title">模拟通道亮度</p>
  47. <t-slider
  48. v-model="simulation_brightness"
  49. :min="0"
  50. :max="65535"
  51. @change="handleSimulationBrightness"
  52. />
  53. <p class="number">{{ simulation_brightness }}</p>
  54. </div>
  55. <div class="slider_wrap">
  56. <p class="title">曝光时间</p>
  57. <t-slider
  58. v-model="exposure"
  59. :min="0"
  60. :max="100000"
  61. @change="handleExposureTime"
  62. />
  63. <p class="number">{{ exposure }}</p>
  64. </div>
  65. </div>
  66. <div class="camera_image">
  67. <div class="active_btn" @click="handleTakePhoto">拍照</div>
  68. <div class="active_btn" @click="handleGetCharacterRecognitionResult">
  69. 识别
  70. </div>
  71. </div>
  72. <div class="identify_results">
  73. <div class="result_wrap">
  74. <p class="title">结果</p>
  75. <div class="result">{{ recognitionResult }}</div>
  76. </div>
  77. <div class="save_form">
  78. <t-tag theme="danger" class="title"
  79. >保存参数到指定堆芯:(在检测时会自动带入当前堆芯的相机参数)</t-tag
  80. >
  81. <!-- <p class="title">保存参数到指定堆芯</p> -->
  82. <t-select
  83. v-model="station_core_id"
  84. class="demo-select-base"
  85. clearable
  86. filterable
  87. @change="handleSelectChange"
  88. placeholder="请选择堆芯"
  89. >
  90. <t-option
  91. v-for="item in coreList"
  92. :value="item.id"
  93. :label="item.name"
  94. :key="item.id"
  95. >
  96. {{ item.name }}
  97. </t-option>
  98. </t-select>
  99. <t-button class="save_btn" @click="onSubmit">保存</t-button>
  100. <t-button
  101. class="save_btn"
  102. style="margin-left: 16px"
  103. @click="loadConfig"
  104. >载入配置</t-button
  105. >
  106. </div>
  107. </div>
  108. </div>
  109. </div>
  110. <div class="right_camera_container">
  111. <p class="time_photo">实时照片</p>
  112. <div class="default_photo" v-if="!isCameraOpen">
  113. <svg
  114. xmlns="http://www.w3.org/2000/svg"
  115. xmlns:xlink="http://www.w3.org/1999/xlink"
  116. fill="none"
  117. version="1.1"
  118. width="85.90400695800781"
  119. height="76.7254638671875"
  120. viewBox="0 0 85.90400695800781 76.7254638671875"
  121. >
  122. <g>
  123. <path
  124. d="M6.91322,0L78.9908,0C82.8088,0,85.904,3.2252,85.904,7.20367L85.904,69.6923C85.8151,73.6041,82.7458,76.7266,78.9908,76.7255L6.91322,76.7255C3.15816,76.7266,0.0888908,73.6041,0,69.6923L0,7.20367C0,3.22519,3.09515,0,6.91322,0ZM63.5689,38.8742C64.784,37.9363,66.4446,37.9363,67.6596,38.8742L78.9908,43.7761L78.9908,7.20367L6.91322,7.20367L6.91322,38.107L22.3351,21.7815C23.6974,20.2285,26.0451,20.2285,27.4074,21.7815L48.6381,49.3601L63.5689,38.8742ZM51.0912,24.6798C51.0912,19.1005,55.4317,14.5776,60.7861,14.5776C66.1404,14.5777,70.4809,19.1005,70.481,24.6798C70.481,30.2591,66.1404,34.7821,60.7861,34.7821C55.4317,34.7821,51.0912,30.2591,51.0912,24.6798ZM58.0045,24.4667C58.0045,26.0675,59.2498,27.3652,60.7861,27.3652L60.7861,27.5357C62.3886,27.5387,63.6621,26.1337,63.5678,24.4667C63.5678,22.8658,62.3224,21.5681,60.7862,21.5681C59.2499,21.5681,58.0045,22.8658,58.0045,24.4667Z"
  125. fill-rule="evenodd"
  126. fill="#5E5C5C"
  127. fill-opacity="1"
  128. />
  129. </g>
  130. </svg>
  131. </div>
  132. <img :src="photoUrl" class="photo" v-else />
  133. </div>
  134. <t-loading
  135. :loading="loadingVisible"
  136. text="识别中..."
  137. fullscreen
  138. size="large"
  139. />
  140. </div>
  141. </template>
  142. <script setup>
  143. import { ref, onMounted, onUnmounted } from 'vue'
  144. import {
  145. startCapture,
  146. stopCapture,
  147. getCameraState,
  148. takeAndSavePhoto,
  149. startCharacterRecognition,
  150. getCharacterRecognitionResult,
  151. stopCharacterRecognition,
  152. getFlashBrightnessAnalog,
  153. getMechanicalArmState,
  154. openFlashLight,
  155. closeFlashLight,
  156. setFlashBrightnessAnalog,
  157. setExposureTimeRaw,
  158. getCameraParametersInteger,
  159. } from '@/command'
  160. import { coreListApi } from '@/api/info'
  161. import { MessagePlugin } from 'tdesign-vue-next'
  162. import { addCameraConfig, getCameraConfig } from '@/api/camera'
  163. const photoUrl = ref('')
  164. const recognitionResult = ref('')
  165. const station_core_id = ref('')
  166. const coreList = ref([])
  167. const loadingVisible = ref(false)
  168. const handleSelectChange = async val => {
  169. const res = await getCameraConfig(val)
  170. if (res?.code == 200) {
  171. // if (res?.data?.exposure) {
  172. // exposure.value = res?.data?.exposure
  173. // // 改变相机的exposure
  174. // handleExposureTime(res?.data?.exposure)
  175. // } else {
  176. // exposure.value = 0
  177. // handleExposureTime(0)
  178. // }
  179. // if (res?.data?.brightness) {
  180. // simulation_brightness.value = res?.data?.brightness
  181. // // 改变相机的simulation_brightness
  182. // handleSimulationBrightness(res?.data?.brightness)
  183. // } else {
  184. // simulation_brightness.value = 0
  185. // handleSimulationBrightness(0)
  186. // }
  187. }
  188. }
  189. const loadConfig = async () => {
  190. const val = station_core_id.value
  191. const res = await getCameraConfig(val)
  192. if (res?.code == 200) {
  193. if (res?.data?.exposure) {
  194. exposure.value = res?.data?.exposure
  195. // 改变相机的exposure
  196. handleExposureTime(res?.data?.exposure)
  197. } else {
  198. exposure.value = 0
  199. handleExposureTime(0)
  200. }
  201. if (res?.data?.brightness) {
  202. simulation_brightness.value = res?.data?.brightness
  203. // 改变相机的simulation_brightness
  204. handleSimulationBrightness(res?.data?.brightness)
  205. } else {
  206. simulation_brightness.value = 0
  207. handleSimulationBrightness(0)
  208. }
  209. }
  210. }
  211. onMounted(async () => {
  212. const res = await coreListApi()
  213. if (res?.code == 200) {
  214. coreList.value = res?.data?.list
  215. }
  216. setTimeout(() => {
  217. websocketsend(openFlashLight)
  218. }, 2000)
  219. })
  220. const onSubmit = async () => {
  221. if (!station_core_id.value) {
  222. MessagePlugin('error', { content: '请选择堆芯' })
  223. return
  224. }
  225. // 保存当前相机参数到相应的堆芯
  226. const data = {
  227. station_core_id: station_core_id.value,
  228. simulation_brightness: simulation_brightness.value,
  229. exposure: exposure.value,
  230. }
  231. const res = await addCameraConfig(data)
  232. console.log(res)
  233. if (res?.code == 200) {
  234. MessagePlugin('success', { content: '保存成功' })
  235. }
  236. }
  237. setInterval(
  238. () => {
  239. photoUrl.value = `${
  240. import.meta.env.VITE_HOST_URL
  241. }app/core/realtime_photo/now.png?${Math.random()}`
  242. },
  243. 500,
  244. 500,
  245. )
  246. const simulation_brightness = ref(0)
  247. const exposure = ref(0)
  248. const flashStatus = ref(false)
  249. // 相机参数
  250. const isCameraOpen = ref(false)
  251. const handleStartCpature = () => {
  252. if (!isCameraOpen.value) {
  253. websocketsend(startCapture)
  254. }
  255. }
  256. const handleStopCpature = () => {
  257. if (isCameraOpen.value) {
  258. websocketsend(stopCapture)
  259. }
  260. }
  261. const handleGetCameraState = () => {
  262. websocketsend(getCameraState)
  263. }
  264. const handleTakePhoto = () => {
  265. if (isCameraOpen.value) {
  266. // 如果相机打开则发送拍照,如果相机关闭 则提醒用户
  267. websocketsend(takeAndSavePhoto)
  268. } else {
  269. MessagePlugin('error', { content: '请先开启相机' })
  270. }
  271. }
  272. const handleStartCharacterRecognition = () => {
  273. websocketsend(startCharacterRecognition)
  274. }
  275. const handleStopCharacterRecognition = () => {
  276. websocketsend(stopCharacterRecognition)
  277. }
  278. const handleGetCharacterRecognitionResult = () => {
  279. if (isCameraOpen.value) {
  280. // 如果相机打开则发送拍照,如果相机关闭 则提醒用户
  281. loadingVisible.value = true
  282. websocketsend(getCharacterRecognitionResult)
  283. } else {
  284. MessagePlugin('error', { content: '请先开启相机' })
  285. }
  286. }
  287. const handleGetMechanicalArmState = () => {
  288. websocketsend(getMechanicalArmState)
  289. }
  290. const handleOpenFlashLight = () => {
  291. if (!flashStatus.value) {
  292. websocketsend(openFlashLight)
  293. }
  294. }
  295. const handleCloseFlashLight = flag => {
  296. if (flashStatus.value) {
  297. websocketsend(closeFlashLight)
  298. }
  299. }
  300. const isOpenCamera = () => {
  301. if (!isCameraOpen.value) {
  302. MessagePlugin('error', { content: '请先打开相机' })
  303. return false
  304. }
  305. return true
  306. }
  307. // 是否真正建立连接
  308. const lockReconnect = ref(false)
  309. // 断开 重连倒计时
  310. const timeoutnum = ref(null)
  311. const websock = ref(null)
  312. const reconnect = () => {
  313. //重新连接
  314. if (lockReconnect.value) {
  315. return
  316. }
  317. lockReconnect.value = true
  318. //没连接上会一直重连,设置延迟避免请求过多
  319. timeoutnum.value && clearTimeout(timeoutnum.value)
  320. timeoutnum.value = setTimeout(function () {
  321. //新连接
  322. initWebSocket()
  323. lockReconnect.value = false
  324. }, 5000)
  325. }
  326. // 接收到消息后改变状态
  327. const websocketonmessage = e => {
  328. // 相机启动停止后接收到回执 而后发送获取相机状态指令 实时改变页面状态
  329. const data = JSON.parse(e.data)
  330. console.log('收到结果', e.data)
  331. const { messageId, success } = data
  332. if (success) {
  333. switch (messageId) {
  334. case 'startCapture':
  335. MessagePlugin('success', { content: '开启相机成功' })
  336. handleGetCameraState()
  337. break
  338. case 'stopCapture':
  339. MessagePlugin('success', { content: '关闭相机成功' })
  340. handleGetCameraState()
  341. break
  342. case 'getCameraState':
  343. const { cameraState } = data
  344. const { isOpen } = cameraState
  345. isCameraOpen.value = isOpen
  346. break
  347. case 'getMechanicalArmState':
  348. const { state } = data
  349. const { x, y } = state || {}
  350. recognitionResult.value = `机械臂坐标x:${x}, y: ${y}`
  351. break
  352. case 'takeAndSavePhoto':
  353. const { photoInfo } = data
  354. const { path } = photoInfo
  355. recognitionResult.value = `图片保存路径为${path}`
  356. break
  357. case 'getCameraParametersInteger':
  358. const { value } = data
  359. exposure.value = value
  360. break
  361. case 'openFlashLight':
  362. flashStatus.value = true
  363. break
  364. case 'CloseFlashLight':
  365. flashStatus.value = false
  366. break
  367. case 'getFlashBrightnessAnalog':
  368. const { brightness } = data
  369. simulation_brightness.value = brightness
  370. break
  371. case 'getCharacterRecognitionResult':
  372. const { result } = data
  373. const { result: str, srcImagePath } = result || {}
  374. loadingVisible.value = false
  375. recognitionResult.value = `识别结果为${
  376. str ? str : '空结果'
  377. }, 图片保存路径为${srcImagePath}`
  378. if (str) {
  379. MessagePlugin.success('识别结果为' + str)
  380. } else {
  381. MessagePlugin.error('识别失败,结果为空')
  382. }
  383. break
  384. default:
  385. break
  386. }
  387. } else {
  388. // MessagePlugin('error', { content: 'ws发送指令执行错误' })
  389. loadingVisible.value = false
  390. }
  391. }
  392. const websocketonopen = () => {
  393. console.log('客户端链接成功!!!')
  394. handleGetCameraState()
  395. setTimeout(() => {
  396. websocketsend(getCameraParametersInteger)
  397. // 无法获取当前闪光灯是开是关。建议上层代码,初始化时候关闭闪光灯,然后在本地保存一个当前状态
  398. handleCloseFlashLight(false)
  399. }, 500)
  400. setTimeout(() => {
  401. websocketsend(openFlashLight)
  402. websocketsend(getFlashBrightnessAnalog)
  403. }, 1000)
  404. }
  405. const websocketonerror = () => {
  406. reconnect()
  407. }
  408. // 发送消息
  409. const websocketsend = data => {
  410. websock.value.send(data)
  411. }
  412. const websocketclose = () => {
  413. reconnect()
  414. }
  415. const initWebSocket = () => {
  416. //初始化weosocket
  417. const wsuri = import.meta.env.VITE_WEBSOCKET_CAMERA_URL
  418. websock.value = new WebSocket(wsuri)
  419. // 客户端接收服务端数据时触发
  420. websock.value.onmessage = websocketonmessage
  421. // 连接建立时触发
  422. websock.value.onopen = websocketonopen
  423. // 通信发生错误时触发
  424. websock.value.onerror = websocketonerror
  425. // 连接关闭时触发
  426. websock.value.onclose = websocketclose
  427. }
  428. initWebSocket()
  429. // 业务处理
  430. const handleExposureTime = value => {
  431. websocketsend(setExposureTimeRaw(value))
  432. }
  433. const handleSimulationBrightness = value => {
  434. console.log('发送调整亮度', setFlashBrightnessAnalog(value))
  435. websocketsend(setFlashBrightnessAnalog(value))
  436. }
  437. onUnmounted(() => {
  438. websock.value.close()
  439. })
  440. </script>
  441. <style lang="scss" scoped>
  442. .debug_container {
  443. width: 100%;
  444. height: 100%;
  445. background: #fff;
  446. border-radius: 6px;
  447. padding-bottom: 30px;
  448. padding-right: 30px;
  449. box-sizing: border-box;
  450. display: flex;
  451. align-items: center;
  452. .left_oper_container {
  453. flex: 1;
  454. height: 100%;
  455. padding-left: 28px;
  456. box-sizing: border-box;
  457. .bread_wrap {
  458. padding: 20px 0;
  459. box-sizing: border-box;
  460. border-bottom: 1px solid #d8d8d8;
  461. }
  462. .operation_proposed {
  463. padding: 36px 36px 0 0;
  464. box-sizing: border-box;
  465. .top_btns {
  466. display: flex;
  467. align-items: center;
  468. justify-content: space-between;
  469. .two_btns {
  470. display: flex;
  471. align-items: center;
  472. .default_btn {
  473. display: flex;
  474. align-items: center;
  475. justify-content: center;
  476. border-radius: 6px;
  477. margin-right: 10px;
  478. padding: 7px 16px;
  479. font-size: 16px;
  480. font-weight: normal;
  481. letter-spacing: 0.07em;
  482. color: #ffffff;
  483. background: #ebebeb;
  484. }
  485. }
  486. .active_btn {
  487. display: flex;
  488. align-items: center;
  489. justify-content: center;
  490. padding: 7px 16px;
  491. font-size: 16px;
  492. font-weight: normal;
  493. letter-spacing: 0.07em;
  494. color: #ffffff;
  495. background: #0052d9;
  496. border-radius: 6px;
  497. margin-right: 10px;
  498. cursor: pointer;
  499. }
  500. }
  501. .camera_param {
  502. padding: 53px 0;
  503. .slider_wrap {
  504. display: flex;
  505. align-items: center;
  506. .title {
  507. font-size: 20px;
  508. font-weight: 500;
  509. letter-spacing: 0.07em;
  510. color: #191919;
  511. margin-right: 32px;
  512. white-space: nowrap;
  513. }
  514. .number {
  515. margin-left: 20px;
  516. width: 30px;
  517. }
  518. }
  519. .mb50 {
  520. margin-bottom: 50px;
  521. }
  522. }
  523. .camera_image {
  524. padding: 48px 0;
  525. border-top: solid 1px #d8d8d8;
  526. border-bottom: solid 1px #d8d8d8;
  527. display: flex;
  528. align-items: center;
  529. .active_btn {
  530. display: flex;
  531. align-items: center;
  532. justify-content: center;
  533. padding: 7px 23px;
  534. font-size: 16px;
  535. font-weight: normal;
  536. letter-spacing: 0.07em;
  537. color: #ffffff;
  538. background: #0052d9;
  539. border-radius: 6px;
  540. margin-right: 30px;
  541. cursor: pointer;
  542. }
  543. }
  544. .identify_results {
  545. padding-top: 44px;
  546. // display: flex;
  547. .result_wrap {
  548. flex: 1;
  549. .title {
  550. font-size: 20px;
  551. font-weight: 500;
  552. letter-spacing: 0.07em;
  553. color: #191919;
  554. }
  555. .result {
  556. padding: 20px;
  557. border: solid 1px #ddd;
  558. margin: 20px 0;
  559. background: #eee;
  560. }
  561. }
  562. .save_form {
  563. flex: 1;
  564. .save_btn {
  565. margin-top: 14px;
  566. }
  567. .title {
  568. margin-bottom: 20px;
  569. }
  570. }
  571. }
  572. }
  573. }
  574. .right_camera_container {
  575. flex: 1;
  576. height: 100%;
  577. padding-left: 30px;
  578. box-sizing: border-box;
  579. display: flex;
  580. flex-direction: column;
  581. .time_photo {
  582. padding: 20px 0;
  583. font-size: 20px;
  584. box-sizing: border-box;
  585. font-weight: normal;
  586. letter-spacing: 0.07em;
  587. color: #191919;
  588. }
  589. .default_photo {
  590. flex: 1;
  591. width: 100%;
  592. border-radius: 6px;
  593. display: flex;
  594. align-items: center;
  595. justify-content: center;
  596. background: #ebebeb;
  597. }
  598. .photo {
  599. width: 835px;
  600. border-radius: 6px;
  601. object-fit: cover;
  602. }
  603. }
  604. }
  605. </style>