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.

548 lines
15 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
  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-select
  79. v-model="station_core_id"
  80. class="demo-select-base"
  81. clearable
  82. filterable
  83. placeholder="请选择堆芯"
  84. >
  85. <t-option
  86. v-for="item in coreList"
  87. :value="item.id"
  88. :label="item.name"
  89. :key="item.id"
  90. >
  91. {{ item.name }}
  92. </t-option>
  93. </t-select>
  94. <t-button class="save_btn" @click="onSubmit">保存</t-button>
  95. </div>
  96. </div>
  97. </div>
  98. </div>
  99. <div class="right_camera_container">
  100. <p class="time_photo">实时照片</p>
  101. <div class="default_photo" v-if="!isCameraOpen">
  102. <svg
  103. xmlns="http://www.w3.org/2000/svg"
  104. xmlns:xlink="http://www.w3.org/1999/xlink"
  105. fill="none"
  106. version="1.1"
  107. width="85.90400695800781"
  108. height="76.7254638671875"
  109. viewBox="0 0 85.90400695800781 76.7254638671875"
  110. >
  111. <g>
  112. <path
  113. 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"
  114. fill-rule="evenodd"
  115. fill="#5E5C5C"
  116. fill-opacity="1"
  117. />
  118. </g>
  119. </svg>
  120. </div>
  121. <img :src="photoUrl" class="photo" v-else />
  122. </div>
  123. </div>
  124. </template>
  125. <script setup>
  126. import { ref, onMounted, onUnmounted } from 'vue'
  127. import {
  128. startCapture,
  129. stopCapture,
  130. getCameraState,
  131. takeAndSavePhoto,
  132. startCharacterRecognition,
  133. getCharacterRecognitionResult,
  134. stopCharacterRecognition,
  135. getFlashBrightnessAnalog,
  136. getMechanicalArmState,
  137. openFlashLight,
  138. closeFlashLight,
  139. setFlashBrightnessAnalog,
  140. setExposureTimeRaw,
  141. getCameraParametersInteger,
  142. } from '@/command'
  143. import { coreListApi } from '@/api/info'
  144. import { MessagePlugin } from 'tdesign-vue-next'
  145. import { addCameraConfig } from '@/api/camera'
  146. const photoUrl = ref('')
  147. const recognitionResult = ref('')
  148. const station_core_id = ref('')
  149. const coreList = ref([])
  150. onMounted(async () => {
  151. const res = await coreListApi()
  152. if (res?.code == 200) {
  153. coreList.value = res?.data?.list
  154. }
  155. })
  156. const onSubmit = async () => {
  157. if (!station_core_id.value) {
  158. MessagePlugin('error', { content: '请选择堆芯' })
  159. return
  160. }
  161. // 保存当前相机参数到相应的堆芯
  162. const data = {
  163. station_core_id: station_core_id.value,
  164. simulation_brightness: simulation_brightness.value,
  165. exposure: exposure.value,
  166. }
  167. const res = await addCameraConfig(data)
  168. console.log(res)
  169. if (res?.code == 200) {
  170. MessagePlugin('success', { content: '保存成功' })
  171. }
  172. }
  173. setInterval(
  174. () => {
  175. photoUrl.value = `${
  176. import.meta.env.VITE_HOST_URL
  177. }app/core/realtime_photo/now.png?${Math.random()}`
  178. },
  179. 500,
  180. 500,
  181. )
  182. const simulation_brightness = ref(0)
  183. const exposure = ref(0)
  184. const flashStatus = ref(false)
  185. // 相机参数
  186. const isCameraOpen = ref(false)
  187. const handleStartCpature = () => {
  188. if (!isCameraOpen.value) {
  189. websocketsend(startCapture)
  190. }
  191. }
  192. const handleStopCpature = () => {
  193. if (isCameraOpen.value) {
  194. websocketsend(stopCapture)
  195. }
  196. }
  197. const handleGetCameraState = () => {
  198. websocketsend(getCameraState)
  199. }
  200. const handleTakePhoto = () => {
  201. if (isCameraOpen.value) {
  202. // 如果相机打开则发送拍照,如果相机关闭 则提醒用户
  203. websocketsend(takeAndSavePhoto)
  204. } else {
  205. MessagePlugin('error', { content: '请先开启相机' })
  206. }
  207. }
  208. const handleStartCharacterRecognition = () => {
  209. websocketsend(startCharacterRecognition)
  210. }
  211. const handleStopCharacterRecognition = () => {
  212. websocketsend(stopCharacterRecognition)
  213. }
  214. const handleGetCharacterRecognitionResult = () => {
  215. if (isCameraOpen.value) {
  216. // 如果相机打开则发送拍照,如果相机关闭 则提醒用户
  217. websocketsend(getCharacterRecognitionResult)
  218. } else {
  219. MessagePlugin('error', { content: '请先开启相机' })
  220. }
  221. }
  222. const handleGetMechanicalArmState = () => {
  223. websocketsend(getMechanicalArmState)
  224. }
  225. const handleOpenFlashLight = () => {
  226. if (!flashStatus.value) {
  227. websocketsend(openFlashLight)
  228. }
  229. }
  230. const handleCloseFlashLight = flag => {
  231. if (flashStatus.value) {
  232. websocketsend(closeFlashLight)
  233. }
  234. }
  235. const isOpenCamera = () => {
  236. if (!isCameraOpen.value) {
  237. MessagePlugin('error', { content: '请先打开相机' })
  238. return false
  239. }
  240. return true
  241. }
  242. // 是否真正建立连接
  243. const lockReconnect = ref(false)
  244. // 断开 重连倒计时
  245. const timeoutnum = ref(null)
  246. const websock = ref(null)
  247. const reconnect = () => {
  248. //重新连接
  249. if (lockReconnect.value) {
  250. return
  251. }
  252. lockReconnect.value = true
  253. //没连接上会一直重连,设置延迟避免请求过多
  254. timeoutnum.value && clearTimeout(timeoutnum.value)
  255. timeoutnum.value = setTimeout(function () {
  256. //新连接
  257. initWebSocket()
  258. lockReconnect.value = false
  259. }, 5000)
  260. }
  261. // 接收到消息后改变状态
  262. const websocketonmessage = e => {
  263. // 相机启动停止后接收到回执 而后发送获取相机状态指令 实时改变页面状态
  264. const data = JSON.parse(e.data)
  265. const { messageId, success } = data
  266. if (success) {
  267. switch (messageId) {
  268. case 'startCapture':
  269. MessagePlugin('success', { content: '开启相机成功' })
  270. handleGetCameraState()
  271. break
  272. case 'stopCapture':
  273. MessagePlugin('success', { content: '关闭相机成功' })
  274. handleGetCameraState()
  275. break
  276. case 'getCameraState':
  277. const { cameraState } = data
  278. const { isOpen } = cameraState
  279. isCameraOpen.value = isOpen
  280. break
  281. case 'getMechanicalArmState':
  282. const { state } = data
  283. const { x, y } = state || {}
  284. recognitionResult.value = `机械臂坐标x:${x}, y: ${y}`
  285. break
  286. case 'takeAndSavePhoto':
  287. const { photoInfo } = data
  288. const { path } = photoInfo
  289. recognitionResult.value = `图片保存路径为${path}`
  290. break
  291. case 'getCameraParametersInteger':
  292. const { value } = data
  293. exposure.value = value
  294. break
  295. case 'openFlashLight':
  296. flashStatus.value = true
  297. break
  298. case 'CloseFlashLight':
  299. flashStatus.value = false
  300. break
  301. case 'getFlashBrightnessAnalog':
  302. const { brightness } = data
  303. simulation_brightness.value = brightness
  304. break
  305. case 'getCharacterRecognitionResult':
  306. const { result } = data
  307. const { result: str, srcImagePath } = result || {}
  308. recognitionResult.value = `识别结果为${
  309. str ? str : '空结果'
  310. }, 图片保存路径为${srcImagePath}`
  311. break
  312. default:
  313. break
  314. }
  315. } else {
  316. // MessagePlugin('error', { content: 'ws发送指令执行错误' })
  317. }
  318. }
  319. const websocketonopen = () => {
  320. console.log('客户端链接成功!!!')
  321. handleGetCameraState()
  322. setTimeout(() => {
  323. websocketsend(getCameraParametersInteger)
  324. // 无法获取当前闪光灯是开是关。建议上层代码,初始化时候关闭闪光灯,然后在本地保存一个当前状态
  325. handleCloseFlashLight(false)
  326. websocketsend(getFlashBrightnessAnalog)
  327. }, 1000)
  328. }
  329. const websocketonerror = () => {
  330. reconnect()
  331. }
  332. // 发送消息
  333. const websocketsend = data => {
  334. websock.value.send(data)
  335. }
  336. const websocketclose = () => {
  337. reconnect()
  338. }
  339. const initWebSocket = () => {
  340. //初始化weosocket
  341. const wsuri = import.meta.env.VITE_WEBSOCKET_CAMERA_URL
  342. websock.value = new WebSocket(wsuri)
  343. // 客户端接收服务端数据时触发
  344. websock.value.onmessage = websocketonmessage
  345. // 连接建立时触发
  346. websock.value.onopen = websocketonopen
  347. // 通信发生错误时触发
  348. websock.value.onerror = websocketonerror
  349. // 连接关闭时触发
  350. websock.value.onclose = websocketclose
  351. }
  352. initWebSocket()
  353. // 业务处理
  354. const handleExposureTime = value => {
  355. websocketsend(setExposureTimeRaw(value))
  356. }
  357. const handleSimulationBrightness = value => {
  358. websocketsend(setFlashBrightnessAnalog(value))
  359. }
  360. onUnmounted(() => {
  361. websock.value.close()
  362. })
  363. </script>
  364. <style lang="scss" scoped>
  365. .debug_container {
  366. width: 100%;
  367. height: 100%;
  368. background: #fff;
  369. border-radius: 6px;
  370. padding-bottom: 30px;
  371. padding-right: 30px;
  372. box-sizing: border-box;
  373. display: flex;
  374. align-items: center;
  375. .left_oper_container {
  376. flex: 1;
  377. height: 100%;
  378. padding-left: 28px;
  379. box-sizing: border-box;
  380. .bread_wrap {
  381. padding: 20px 0;
  382. box-sizing: border-box;
  383. border-bottom: 1px solid #d8d8d8;
  384. }
  385. .operation_proposed {
  386. padding: 36px 36px 0 0;
  387. box-sizing: border-box;
  388. .top_btns {
  389. display: flex;
  390. align-items: center;
  391. justify-content: space-between;
  392. .two_btns {
  393. display: flex;
  394. align-items: center;
  395. .default_btn {
  396. display: flex;
  397. align-items: center;
  398. justify-content: center;
  399. border-radius: 6px;
  400. margin-right: 10px;
  401. padding: 7px 16px;
  402. font-size: 16px;
  403. font-weight: normal;
  404. letter-spacing: 0.07em;
  405. color: #ffffff;
  406. background: #ebebeb;
  407. }
  408. }
  409. .active_btn {
  410. display: flex;
  411. align-items: center;
  412. justify-content: center;
  413. padding: 7px 16px;
  414. font-size: 16px;
  415. font-weight: normal;
  416. letter-spacing: 0.07em;
  417. color: #ffffff;
  418. background: #0052d9;
  419. border-radius: 6px;
  420. margin-right: 10px;
  421. cursor: pointer;
  422. }
  423. }
  424. .camera_param {
  425. padding: 53px 0;
  426. .slider_wrap {
  427. display: flex;
  428. align-items: center;
  429. .title {
  430. font-size: 20px;
  431. font-weight: 500;
  432. letter-spacing: 0.07em;
  433. color: #191919;
  434. margin-right: 32px;
  435. white-space: nowrap;
  436. }
  437. .number {
  438. margin-left: 20px;
  439. width: 30px;
  440. }
  441. }
  442. .mb50 {
  443. margin-bottom: 50px;
  444. }
  445. }
  446. .camera_image {
  447. padding: 48px 0;
  448. border-top: solid 1px #d8d8d8;
  449. border-bottom: solid 1px #d8d8d8;
  450. display: flex;
  451. align-items: center;
  452. .active_btn {
  453. display: flex;
  454. align-items: center;
  455. justify-content: center;
  456. padding: 7px 23px;
  457. font-size: 16px;
  458. font-weight: normal;
  459. letter-spacing: 0.07em;
  460. color: #ffffff;
  461. background: #0052d9;
  462. border-radius: 6px;
  463. margin-right: 30px;
  464. cursor: pointer;
  465. }
  466. }
  467. .identify_results {
  468. padding-top: 44px;
  469. display: flex;
  470. .result_wrap {
  471. flex: 1;
  472. .title {
  473. font-size: 20px;
  474. font-weight: 500;
  475. letter-spacing: 0.07em;
  476. color: #191919;
  477. }
  478. .result {
  479. padding: 10px 0;
  480. }
  481. }
  482. .save_form {
  483. flex: 1;
  484. .save_btn {
  485. margin-top: 14px;
  486. }
  487. }
  488. }
  489. }
  490. }
  491. .right_camera_container {
  492. flex: 1;
  493. height: 100%;
  494. padding-left: 30px;
  495. box-sizing: border-box;
  496. display: flex;
  497. flex-direction: column;
  498. .time_photo {
  499. padding: 20px 0;
  500. font-size: 20px;
  501. box-sizing: border-box;
  502. font-weight: normal;
  503. letter-spacing: 0.07em;
  504. color: #191919;
  505. }
  506. .default_photo {
  507. flex: 1;
  508. width: 100%;
  509. border-radius: 6px;
  510. display: flex;
  511. align-items: center;
  512. justify-content: center;
  513. background: #ebebeb;
  514. }
  515. .photo {
  516. flex: 1;
  517. width: 100%;
  518. border-radius: 6px;
  519. }
  520. }
  521. }
  522. </style>