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

<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-tag theme="danger" class="title"
>保存参数到指定堆芯:(在检测时会自动带入当前堆芯的相机参数)</t-tag
>
<!-- <p class="title">保存参数到指定堆芯</p> -->
<t-select
v-model="station_core_id"
class="demo-select-base"
clearable
filterable
@change="handleSelectChange"
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>
<t-button
class="save_btn"
style="margin-left: 16px"
@click="loadConfig"
>载入配置</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>
<t-loading
:loading="loadingVisible"
text="识别中..."
fullscreen
size="large"
/>
</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, getCameraConfig } from '@/api/camera'
const photoUrl = ref('')
const recognitionResult = ref('')
const station_core_id = ref('')
const coreList = ref([])
const loadingVisible = ref(false)
const handleSelectChange = async val => {
const res = await getCameraConfig(val)
if (res?.code == 200) {
// if (res?.data?.exposure) {
// exposure.value = res?.data?.exposure
// // 改变相机的exposure
// handleExposureTime(res?.data?.exposure)
// } else {
// exposure.value = 0
// handleExposureTime(0)
// }
// if (res?.data?.brightness) {
// simulation_brightness.value = res?.data?.brightness
// // 改变相机的simulation_brightness
// handleSimulationBrightness(res?.data?.brightness)
// } else {
// simulation_brightness.value = 0
// handleSimulationBrightness(0)
// }
}
}
const loadConfig = async () => {
const val = station_core_id.value
const res = await getCameraConfig(val)
if (res?.code == 200) {
if (res?.data?.exposure) {
exposure.value = res?.data?.exposure
// 改变相机的exposure
handleExposureTime(res?.data?.exposure)
} else {
exposure.value = 0
handleExposureTime(0)
}
if (res?.data?.brightness) {
simulation_brightness.value = res?.data?.brightness
// 改变相机的simulation_brightness
handleSimulationBrightness(res?.data?.brightness)
} else {
simulation_brightness.value = 0
handleSimulationBrightness(0)
}
}
}
onMounted(async () => {
const res = await coreListApi()
if (res?.code == 200) {
coreList.value = res?.data?.list
}
setTimeout(() => {
websocketsend(openFlashLight)
}, 2000)
})
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) {
// 如果相机打开则发送拍照,如果相机关闭 则提醒用户
loadingVisible.value = true
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)
console.log('收到结果', 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 || {}
loadingVisible.value = false
recognitionResult.value = `识别结果为${
str ? str : '空结果'
}, 图片保存路径为${srcImagePath}`
if (str) {
MessagePlugin.success('识别结果为' + str)
} else {
MessagePlugin.error('识别失败,结果为空')
}
break
default:
break
}
} else {
// MessagePlugin('error', { content: 'ws发送指令执行错误' })
loadingVisible.value = false
}
}
const websocketonopen = () => {
console.log('客户端链接成功!!!')
handleGetCameraState()
setTimeout(() => {
websocketsend(getCameraParametersInteger)
// 无法获取当前闪光灯是开是关。建议上层代码,初始化时候关闭闪光灯,然后在本地保存一个当前状态
handleCloseFlashLight(false)
}, 500)
setTimeout(() => {
websocketsend(openFlashLight)
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 => {
console.log('发送调整亮度', setFlashBrightnessAnalog(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: 20px;
border: solid 1px #ddd;
margin: 20px 0;
background: #eee;
}
}
.save_form {
flex: 1;
.save_btn {
margin-top: 14px;
}
.title {
margin-bottom: 20px;
}
}
}
}
}
.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 {
width: 835px;
border-radius: 6px;
object-fit: cover;
}
}
}
</style>