|
|
<template> <div class="debug_container"> <div class="left_oper_container"> <div class="bread_wrap"> <t-breadcrumb> <t-breadcrumbItem>首页</t-breadcrumbItem> <t-breadcrumbItem>相机调试</t-breadcrumbItem> </t-breadcrumb> </div> <div class="operation_proposed"> <div class="top_btns"> <div class="two_btns"> <div :class="isCameraOpen ? 'default_btn' : 'active_btn'" @click="handleStartCpature" > 打开相机 </div> <div :class="isCameraOpen ? 'active_btn' : 'default_btn'" @click="handleStopCpature" > 关闭相机 </div> </div> <div class="two_btns"> <div :class="flashStatus ? 'default_btn' : 'active_btn'" @click="handleOpenFlashLight" > 打开闪光灯 </div> <div :class="flashStatus ? 'active_btn' : 'default_btn'" @click="handleCloseFlashLight" > 关闭闪光灯 </div> </div> <div class="active_btn" @click="handleGetMechanicalArmState"> 获取机械臂坐标 </div> </div> <div class="camera_param"> <div class="slider_wrap mb50"> <p class="title">模拟通道亮度</p> <t-slider v-model="simulation_brightness" :min="0" :max="65535" @change="handleSimulationBrightness" /> <p class="number">{{ simulation_brightness }}</p> </div> <div class="slider_wrap"> <p class="title">曝光时间</p> <t-slider v-model="exposure" :min="0" :max="100000" @change="handleExposureTime" /> <p class="number">{{ exposure }}</p> </div> </div> <div class="camera_image"> <div class="active_btn" @click="handleTakePhoto">拍照</div> <div class="active_btn" @click="handleGetCharacterRecognitionResult"> 识别 </div> </div> <div class="identify_results"> <div class="result_wrap"> <p class="title">结果</p> <div class="result">{{ recognitionResult }}</div> </div> <div class="save_form"> <t-select v-model="station_core_id" class="demo-select-base" clearable filterable placeholder="请选择堆芯" > <t-option v-for="item in coreList" :value="item.id" :label="item.name" :key="item.id" > {{ item.name }} </t-option> </t-select> <t-button class="save_btn" @click="onSubmit">保存</t-button> </div> </div> </div> </div> <div class="right_camera_container"> <p class="time_photo">实时照片</p> <div class="default_photo" v-if="!isCameraOpen"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="85.90400695800781" height="76.7254638671875" viewBox="0 0 85.90400695800781 76.7254638671875" > <g> <path 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" fill-rule="evenodd" fill="#5E5C5C" fill-opacity="1" /> </g> </svg> </div> <img :src="photoUrl" class="photo" v-else /> </div> </div> </template>
<script setup> import { ref, onMounted, onUnmounted } from 'vue' import { startCapture, stopCapture, getCameraState, takeAndSavePhoto, startCharacterRecognition, getCharacterRecognitionResult, stopCharacterRecognition, getFlashBrightnessAnalog, getMechanicalArmState, openFlashLight, closeFlashLight, setFlashBrightnessAnalog, setExposureTimeRaw, getCameraParametersInteger, } from '@/command' import { coreListApi } from '@/api/info' import { MessagePlugin } from 'tdesign-vue-next' import { addCameraConfig } from '@/api/camera'
const photoUrl = ref('') const recognitionResult = ref('') const station_core_id = ref('')
const coreList = ref([])
onMounted(async () => { const res = await coreListApi() if (res?.code == 200) { coreList.value = res?.data?.list } })
const onSubmit = async () => { if (!station_core_id.value) { MessagePlugin('error', { content: '请选择堆芯' }) return } // 保存当前相机参数到相应的堆芯
const data = { station_core_id: station_core_id.value, simulation_brightness: simulation_brightness.value, exposure: exposure.value, } const res = await addCameraConfig(data) console.log(res) if (res?.code == 200) { MessagePlugin('success', { content: '保存成功' }) } }
setInterval( () => { photoUrl.value = `${ import.meta.env.VITE_HOST_URL }app/core/realtime_photo/now.png?${Math.random()}`
}, 500, 500, )
const simulation_brightness = ref(0) const exposure = ref(0) const flashStatus = ref(false)
// 相机参数
const isCameraOpen = ref(false)
const handleStartCpature = () => { if (!isCameraOpen.value) { websocketsend(startCapture) } } const handleStopCpature = () => { if (isCameraOpen.value) { websocketsend(stopCapture) } } const handleGetCameraState = () => { websocketsend(getCameraState) } const handleTakePhoto = () => { if (isCameraOpen.value) { // 如果相机打开则发送拍照,如果相机关闭 则提醒用户
websocketsend(takeAndSavePhoto) } else { MessagePlugin('error', { content: '请先开启相机' }) } }
const handleStartCharacterRecognition = () => { websocketsend(startCharacterRecognition) } const handleStopCharacterRecognition = () => { websocketsend(stopCharacterRecognition) } const handleGetCharacterRecognitionResult = () => { if (isCameraOpen.value) { // 如果相机打开则发送拍照,如果相机关闭 则提醒用户
websocketsend(getCharacterRecognitionResult) } else { MessagePlugin('error', { content: '请先开启相机' }) } } const handleGetMechanicalArmState = () => { websocketsend(getMechanicalArmState) }
const handleOpenFlashLight = () => { if (!flashStatus.value) { websocketsend(openFlashLight) } } const handleCloseFlashLight = flag => { if (flashStatus.value) { websocketsend(closeFlashLight) } }
const isOpenCamera = () => { if (!isCameraOpen.value) { MessagePlugin('error', { content: '请先打开相机' }) return false } return true }
// 是否真正建立连接
const lockReconnect = ref(false) // 断开 重连倒计时
const timeoutnum = ref(null)
const websock = ref(null)
const reconnect = () => { //重新连接
if (lockReconnect.value) { return } lockReconnect.value = true //没连接上会一直重连,设置延迟避免请求过多
timeoutnum.value && clearTimeout(timeoutnum.value) timeoutnum.value = setTimeout(function () { //新连接
initWebSocket() lockReconnect.value = false }, 5000) }
// 接收到消息后改变状态
const websocketonmessage = e => { // 相机启动停止后接收到回执 而后发送获取相机状态指令 实时改变页面状态
const data = JSON.parse(e.data) const { messageId, success } = data if (success) { switch (messageId) { case 'startCapture': MessagePlugin('success', { content: '开启相机成功' }) handleGetCameraState() break case 'stopCapture': MessagePlugin('success', { content: '关闭相机成功' }) handleGetCameraState() break case 'getCameraState': const { cameraState } = data const { isOpen } = cameraState isCameraOpen.value = isOpen break case 'getMechanicalArmState': const { state } = data const { x, y } = state || {} recognitionResult.value = `机械臂坐标x:${x}, y: ${y}` break case 'takeAndSavePhoto': const { photoInfo } = data const { path } = photoInfo recognitionResult.value = `图片保存路径为${path}` break case 'getCameraParametersInteger': const { value } = data exposure.value = value break case 'openFlashLight': flashStatus.value = true break case 'CloseFlashLight': flashStatus.value = false break case 'getFlashBrightnessAnalog': const { brightness } = data simulation_brightness.value = brightness break case 'getCharacterRecognitionResult': const { result } = data const { result: str, srcImagePath } = result || {} recognitionResult.value = `识别结果为${ str ? str : '空结果' }, 图片保存路径为${srcImagePath}`
break default: break } } else { // MessagePlugin('error', { content: 'ws发送指令执行错误' })
} }
const websocketonopen = () => { console.log('客户端链接成功!!!') handleGetCameraState() setTimeout(() => { websocketsend(getCameraParametersInteger) // 无法获取当前闪光灯是开是关。建议上层代码,初始化时候关闭闪光灯,然后在本地保存一个当前状态
handleCloseFlashLight(false) websocketsend(getFlashBrightnessAnalog) }, 1000) }
const websocketonerror = () => { reconnect() }
// 发送消息
const websocketsend = data => { websock.value.send(data) }
const websocketclose = () => { reconnect() }
const initWebSocket = () => { //初始化weosocket
const wsuri = import.meta.env.VITE_WEBSOCKET_CAMERA_URL websock.value = new WebSocket(wsuri) // 客户端接收服务端数据时触发
websock.value.onmessage = websocketonmessage // 连接建立时触发
websock.value.onopen = websocketonopen // 通信发生错误时触发
websock.value.onerror = websocketonerror // 连接关闭时触发
websock.value.onclose = websocketclose }
initWebSocket()
// 业务处理
const handleExposureTime = value => { websocketsend(setExposureTimeRaw(value)) }
const handleSimulationBrightness = value => { websocketsend(setFlashBrightnessAnalog(value)) }
onUnmounted(() => { websock.value.close() }) </script>
<style lang="scss" scoped> .debug_container { width: 100%; height: 100%; background: #fff; border-radius: 6px; padding-bottom: 30px; padding-right: 30px; box-sizing: border-box; display: flex; align-items: center; .left_oper_container { flex: 1; height: 100%; padding-left: 28px; box-sizing: border-box; .bread_wrap { padding: 20px 0; box-sizing: border-box; border-bottom: 1px solid #d8d8d8; } .operation_proposed { padding: 36px 36px 0 0; box-sizing: border-box; .top_btns { display: flex; align-items: center; justify-content: space-between; .two_btns { display: flex; align-items: center; .default_btn { display: flex; align-items: center; justify-content: center; border-radius: 6px; margin-right: 10px; padding: 7px 16px; font-size: 16px; font-weight: normal; letter-spacing: 0.07em; color: #ffffff; background: #ebebeb; } } .active_btn { display: flex; align-items: center; justify-content: center; padding: 7px 16px; font-size: 16px; font-weight: normal; letter-spacing: 0.07em; color: #ffffff; background: #0052d9; border-radius: 6px; margin-right: 10px; cursor: pointer; } } .camera_param { padding: 53px 0; .slider_wrap { display: flex; align-items: center; .title { font-size: 20px; font-weight: 500; letter-spacing: 0.07em; color: #191919; margin-right: 32px; white-space: nowrap; } .number { margin-left: 20px; width: 30px; } } .mb50 { margin-bottom: 50px; } } .camera_image { padding: 48px 0; border-top: solid 1px #d8d8d8; border-bottom: solid 1px #d8d8d8; display: flex; align-items: center; .active_btn { display: flex; align-items: center; justify-content: center; padding: 7px 23px; font-size: 16px; font-weight: normal; letter-spacing: 0.07em; color: #ffffff; background: #0052d9; border-radius: 6px; margin-right: 30px; cursor: pointer; } } .identify_results { padding-top: 44px; display: flex; .result_wrap { flex: 1; .title { font-size: 20px; font-weight: 500; letter-spacing: 0.07em; color: #191919; } .result { padding: 10px 0; } } .save_form { flex: 1; .save_btn { margin-top: 14px; } } } } } .right_camera_container { flex: 1; height: 100%; padding-left: 30px; box-sizing: border-box; display: flex; flex-direction: column; .time_photo { padding: 20px 0; font-size: 20px; box-sizing: border-box; font-weight: normal; letter-spacing: 0.07em; color: #191919; } .default_photo { flex: 1; width: 100%; border-radius: 6px; display: flex; align-items: center; justify-content: center; background: #ebebeb; } .photo { flex: 1; width: 100%; border-radius: 6px; } } } </style>
|