From 3a5389b1fb6f01f446ed6be1a2c640b337f0fe76 Mon Sep 17 00:00:00 2001 From: cles <208023732@qq.com> Date: Thu, 4 Sep 2025 09:11:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=8C=E9=9B=86=E6=88=90?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E8=BF=9E=E6=8E=A5=E7=8A=B6=E6=80=81=E5=92=8C?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=88=97=E8=A1=A8=E7=AE=A1=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E4=B8=BB=E8=BF=9B=E7=A8=8B=E4=B8=8E=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E7=9A=84TCP=E9=80=9A=E4=BF=A1=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/ipcRouter.js | 150 +++++++++++++++--- src/renderer/src/common/ipcEvents.js | 7 +- .../ImageCollection/ImageCollection.jsx | 8 +- .../MeasurementPointSetting.jsx | 84 +++++++++- .../components/SiderHeader/SiderHeader.jsx | 25 ++- src/renderer/src/stores/deviceStore.js | 32 ++++ 6 files changed, 263 insertions(+), 43 deletions(-) create mode 100644 src/renderer/src/stores/deviceStore.js diff --git a/src/main/ipcRouter.js b/src/main/ipcRouter.js index 3c05edd..cc73f9d 100644 --- a/src/main/ipcRouter.js +++ b/src/main/ipcRouter.js @@ -3,12 +3,17 @@ import dgram from 'dgram' import net from 'net' import { IPC_EVENT } from '../renderer/src/common/ipcEvents.js' import fs from 'fs' -// 全局保存所有TCP连接 + +// 全局保存所有TCP连接和相关信息 const tcpClients = new Map() +// 保存待处理的请求,用于关联响应 +const pendingRequests = new Map() + export function registerIpRouter() { - ipcMain.on(IPC_EVENT.DEVICE_SEARCH, searchDevice) // 设备搜索 - ipcMain.on(IPC_EVENT.DEVICE_CONNECT, connectDevice) // 设备连接 - ipcMain.on(IPC_EVENT.DEVICE_DISCONNECT, disconnectDevice) // 设备断开 + ipcMain.on(IPC_EVENT.DEVICE_SEARCH, searchDevice) + ipcMain.on(IPC_EVENT.DEVICE_CONNECT, connectDevice) + ipcMain.on(IPC_EVENT.DEVICE_DISCONNECT, disconnectDevice) + ipcMain.handle(IPC_EVENT.SENSORS_GET, sensorLoad) // 改为handle,支持异步返回 } const searchDevice = (event) => { @@ -34,7 +39,6 @@ const searchDevice = (event) => { udpClient.on('message', (msg, rinfo) => { try { - // 以 IP 为 key,自动去重 resultMap.set(rinfo.address, { from: rinfo.address, data: msg.toString() @@ -42,20 +46,17 @@ const searchDevice = (event) => { } catch (e) { console.error('parse UDP message failed:', e) } - // 每收到一条消息就重置定时器 if (timer) clearTimeout(timer) timer = setTimeout(() => { udpClient.close() console.log('UDP socket closed after timeout') - // 关闭后统一回复所有结果(已去重) event.reply && event.reply(IPC_EVENT.DEVICE_SEARCH_REPLY, Array.from(resultMap.values())) - }, 1000) // 1秒内没有新消息就关闭 + }, 1000) }) udpClient.on('error', (err) => { console.error('UDP error:', err) udpClient.close() - // 出错时也可以回复已收到的内容 event.reply && event.reply(IPC_EVENT.DEVICE_SEARCH_REPLY, Array.from(resultMap.values())) }) } @@ -69,10 +70,20 @@ const connectDevice = (event, { ip, port }) => { event.reply(IPC_EVENT.DEVICE_CONNECT_REPLY, { success: false, error: '已连接' }) return } + const client = new net.Socket() + + // 保存连接信息,包括event sender用于后续通信 + const connectionInfo = { + client, + eventSender: event.sender, + ip, + port + } + client.connect(Number(port), ip, () => { event.reply(IPC_EVENT.DEVICE_CONNECT_REPLY, { success: true, ip }) - tcpClients.set(ip, client) + tcpClients.set(ip, connectionInfo) }) let buffer = '' @@ -84,50 +95,137 @@ const connectDevice = (event, { ip, port }) => { const line = buffer.slice(0, index) buffer = buffer.slice(index + 1) if (!line.trim()) continue + let msg try { msg = JSON.parse(line) - console.log(msg.command) + console.log('Received command:', `${msg.command}:${msg.type}`) } catch (e) { console.error('TCP data parse error:', e) fs.appendFileSync('error_log.txt', line + '\n') continue } + if (!msg || !msg.command) { console.log('invalid msg format:', msg) continue } - switch (msg.command) { - case 'result': - event.sender.send(IPC_EVENT.SENSOR_DATA, { ip, ...msg }) - break - case 'image': - event.sender.send(IPC_EVENT.IMAGE_DATA, { ip, ...msg }) - break - case 'heartbeat': - break - default: - console.warn('unknow command type:', msg.command) - } + + // 处理TCP响应 + handleTcpResponse(ip, msg) } }) + client.on('error', (err) => { event.reply(IPC_EVENT.DEVICE_CONNECT_REPLY, { success: false, error: err.message }) client.destroy() tcpClients.delete(ip) + // 清理该IP的所有待处理请求 + clearPendingRequestsByIp(ip) }) + client.on('close', () => { tcpClients.delete(ip) + clearPendingRequestsByIp(ip) }) } const disconnectDevice = (event, { ip }) => { - const client = tcpClients.get(ip) - if (client) { - client.destroy() + const connectionInfo = tcpClients.get(ip) + if (connectionInfo) { + connectionInfo.client.destroy() tcpClients.delete(ip) + clearPendingRequestsByIp(ip) event.reply(IPC_EVENT.DEVICE_DISCONNECT_REPLY, { success: true }) } else { event.reply(IPC_EVENT.DEVICE_DISCONNECT_REPLY, { success: false, error: '未连接' }) } } + +// 处理TCP响应 +const handleTcpResponse = (ip, msg) => { + const connectionInfo = tcpClients.get(ip) + if (!connectionInfo) return + + switch (`${msg.command}:${msg.type}`) { + case IPC_EVENT.RESULT_REPLY: + connectionInfo.eventSender.send(IPC_EVENT.RESULT_REPLY, { ip, ...msg }) + break + case IPC_EVENT.IMAGE_REPLY: + connectionInfo.eventSender.send(IPC_EVENT.IMAGE_REPLY, { ip, ...msg }) + break + case IPC_EVENT.SENSORS_GET: + { + const sensorRequest = pendingRequests.get(`${ip}${IPC_EVENT.SENSORS_GET}`) + if (sensorRequest) { + // 响应传感器加载请求 + sensorRequest.resolve({ success: true, data: msg }) + pendingRequests.delete(`${ip}${IPC_EVENT.SENSORS_GET}`) + } + } + break + case IPC_EVENT.HEARTBEAT_REPLY: + // 心跳处理 + break + + default: + console.warn('unknown command:', `${msg.command}:${msg.type}`) + } +} + +// 传感器加载 - 改为异步处理 +const sensorLoad = async (event, { ip }) => { + return new Promise((resolve, reject) => { + const connectionInfo = tcpClients.get(ip) + if (!connectionInfo) { + resolve({ success: false, error: '设备未连接' }) + return + } + + // 生成请求ID并保存待处理请求 + const requestKey = `${ip}${IPC_EVENT.SENSORS_GET}` + + // 设置超时 + const timeout = setTimeout(() => { + pendingRequests.delete(requestKey) + resolve({ success: false, error: '请求超时' }) + }, 10000) // 10秒超时 + + // 保存请求信息 + pendingRequests.set(requestKey, { + resolve: (result) => { + clearTimeout(timeout) + resolve(result) + }, + reject: (error) => { + clearTimeout(timeout) + reject(error) + }, + timestamp: Date.now() + }) + + // 发送传感器加载命令 + const command = { command: 'sensors', type: 'get', values: '' } + const message = JSON.stringify(command) + '\n\n' + + connectionInfo.client.write(message, (err) => { + if (err) { + pendingRequests.delete(requestKey) + clearTimeout(timeout) + resolve({ success: false, error: err.message }) + } + }) + }) +} + +// 清理指定IP的所有待处理请求 +const clearPendingRequestsByIp = (ip) => { + const keysToDelete = [] + for (const [key, request] of pendingRequests) { + if (key.startsWith(`${ip}_`)) { + request.resolve({ success: false, error: '连接已断开' }) + keysToDelete.push(key) + } + } + keysToDelete.forEach((key) => pendingRequests.delete(key)) +} diff --git a/src/renderer/src/common/ipcEvents.js b/src/renderer/src/common/ipcEvents.js index c782d32..2e51b1a 100644 --- a/src/renderer/src/common/ipcEvents.js +++ b/src/renderer/src/common/ipcEvents.js @@ -7,7 +7,8 @@ export const IPC_EVENT = { DEVICE_DISCONNECT: 'device:disconnect', DEVICE_DISCONNECT_REPLY: 'device:disconnect:reply', - SENSOR_DATA: 'sensor:data', // 传感器数据 - IMAGE_DATA: 'image:data', // 图像数据 - HEARTBEAT: 'heartbeat' // 心跳包 + RESULT_REPLY: 'result:reply', // 传感器数据 + IMAGE_REPLY: 'image:reply', // 图像数据 + HEARTBEAT_REPLY: 'heartbeat:reply', // 心跳包 + SENSORS_GET: 'sensors:get' // 加载传感器配置 } diff --git a/src/renderer/src/components/ImageCollection/ImageCollection.jsx b/src/renderer/src/components/ImageCollection/ImageCollection.jsx index 57137a3..564ff44 100644 --- a/src/renderer/src/components/ImageCollection/ImageCollection.jsx +++ b/src/renderer/src/components/ImageCollection/ImageCollection.jsx @@ -31,10 +31,6 @@ function ImagePreview() { height: rectangleData.height * imageScale } setRectangle(displayRect) - console.log('从store更新矩形显示:', { - 原始数据: rectangleData, - 显示坐标: displayRect - }) } else if (!rectangleData) { setRectangle(null) } @@ -250,9 +246,9 @@ function ImagePreview() { const src = `data:image/png;base64,${base64}` setImgSrc(src) } - window.electron.ipcRenderer.on(IPC_EVENT.IMAGE_DATA, handler) + window.electron.ipcRenderer.on(IPC_EVENT.IMAGE_REPLY, handler) return () => { - window.electron.ipcRenderer.removeListener(IPC_EVENT.IMAGE_DATA, handler) + window.electron.ipcRenderer.removeListener(IPC_EVENT.IMAGE_REPLY, handler) } }, []) diff --git a/src/renderer/src/components/MeasurementPointSetting/MeasurementPointSetting.jsx b/src/renderer/src/components/MeasurementPointSetting/MeasurementPointSetting.jsx index 0453559..ec8dfc3 100644 --- a/src/renderer/src/components/MeasurementPointSetting/MeasurementPointSetting.jsx +++ b/src/renderer/src/components/MeasurementPointSetting/MeasurementPointSetting.jsx @@ -1,5 +1,5 @@ import styles from './MeasurementPointSetting.module.css' -import { Flex, Input, Select, InputNumber, Button, Table, Tooltip, Modal } from 'antd' +import { Flex, Input, Select, InputNumber, Button, Table, Tooltip, Modal, message } from 'antd' import { PlusOutlined, MinusOutlined, @@ -9,13 +9,18 @@ import { BoxPlotFilled } from '@ant-design/icons' import useRectangleStore from '../../stores/rectangleStore' +import useDeviceStore from '../../stores/deviceStore' import { useState } from 'react' +import { IPC_EVENT } from '../../common/ipcEvents' function MeasurementPointSetting() { // 从Zustand store获取矩形数据和设置方法 const rectangleData = useRectangleStore((state) => state.rectangleData) const setRectangleData = useRectangleStore((state) => state.setRectangleData) + // 从设备store获取当前连接的设备 + const connectedDevice = useDeviceStore((state) => state.connectedDevice) + // 表单数据状态 const [formData, setFormData] = useState({ location: 1, // 测点位置 @@ -64,8 +69,6 @@ function MeasurementPointSetting() { width: Number(wData.value) || 0, height: Number(hData.value) || 0 } - - console.log('从传感器数据更新矩形:', rectangleInfo) setRectangleData(rectangleInfo) } else { setRectangleData(null) @@ -240,9 +243,80 @@ function MeasurementPointSetting() { }) } - const handleLoad = () => {} + const handleLoad = async () => { + try { + // 从store中获取当前连接的设备IP + if (!connectedDevice || !connectedDevice.ip) { + message.warning('请先连接设备后再加载传感器数据!') + return + } + + const deviceIp = connectedDevice.ip + + // 调用主进程的传感器加载函数 + const result = await window.electron.ipcRenderer.invoke(IPC_EVENT.SENSORS_GET, { + ip: deviceIp + }) + + if (result.success) { + console.log('传感器数据加载成功:', result.data) + message.success('传感器数据已成功加载!') + } else { + console.error('传感器数据加载失败:', result.error) + message.error(`传感器数据加载失败:${result.error}`) + } + } catch (error) { + console.error('调用传感器加载失败:', error) + message.error(`调用传感器加载失败:${error.message}`) + } + } + + const handleSet = () => { + // 检查是否有传感器数据 + if (!sensorList || sensorList.length === 0) { + message.warning('没有传感器数据,请先添加传感器!') + return + } + + // 检查是否连接设备 + if (!connectedDevice || !connectedDevice.ip) { + message.warning('请先连接设备后再设置传感器!') + return + } + + // 收集传感器数据并转换为指定格式 + const collectedData = sensorList.map((sensor) => { + // 从传感器的children中提取数据 + const children = sensor.children || [] + + // 查找各个字段 + const positionItem = children.find((item) => item.name === '测点位置') + const descriptionItem = children.find((item) => item.name === '测点描述') + const coefficientItem = children.find((item) => item.name === '计算系数') + const targetItem = children.find((item) => item.name === '基准标靶') + + // 查找坐标信息(在基准标靶的children中) + const targetChildren = targetItem?.children || [] + const xItem = targetChildren.find((item) => item.name === 'x') + const yItem = targetChildren.find((item) => item.name === 'y') + const wItem = targetChildren.find((item) => item.name === 'w') + const hItem = targetChildren.find((item) => item.name === 'h') + + return { + arg: String(coefficientItem?.value), // 计算系数 + des: String(descriptionItem?.value), // 测点描述 + pos: String(positionItem?.value), // 测点位置 + tar: String(targetItem?.value), // 基准标靶 + w: String(wItem?.value), // 宽 + h: String(hItem?.value), // 高 + x: String(xItem?.value), // x坐标 + y: String(yItem?.value) // y坐标 + } + }) - const handleSet = () => {} + console.log('收集到的传感器数据:', collectedData) + console.log('格式化后的JSON:', JSON.stringify(collectedData, null, 2)) + } return ( diff --git a/src/renderer/src/components/SiderHeader/SiderHeader.jsx b/src/renderer/src/components/SiderHeader/SiderHeader.jsx index 2352f84..589473a 100644 --- a/src/renderer/src/components/SiderHeader/SiderHeader.jsx +++ b/src/renderer/src/components/SiderHeader/SiderHeader.jsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react' import styles from './SiderHeader.module.css' import { Flex, Select, Button, Input, Spin, message } from 'antd' import { IPC_EVENT } from '../../common/ipcEvents.js' +import useDeviceStore from '../../stores/deviceStore' import { VideoCameraFilled, GoldFilled, @@ -20,16 +21,25 @@ function SiderHeader({ showSystemSettings = true }) { const [devicePort, setDevicePort] = useState('2230') const [connected, setConnected] = useState(null) // 保存已连接设备IP + // 使用设备store + const setConnectedDevice = useDeviceStore((state) => state.setConnectedDevice) + const clearConnectedDevice = useDeviceStore((state) => state.clearConnectedDevice) + const setDeviceList_store = useDeviceStore((state) => state.setDeviceList) + // 监听设备搜索结果 useEffect(() => { const handler = (event, results) => { setSearching(false) if (Array.isArray(results) && results.length > 0) { const ips = results.map((item) => item.from) - setDeviceList(ips.map((ip) => ({ value: ip, label: ip }))) + const devices = ips.map((ip) => ({ value: ip, label: ip })) + setDeviceList(devices) + // 同时更新到store中 + setDeviceList_store(ips.map((ip) => ({ ip, name: ip }))) message.success(`发现${ips.length}台设备`) } else { setDeviceList([]) + setDeviceList_store([]) message.warning('未发现设备') } } @@ -37,21 +47,30 @@ function SiderHeader({ showSystemSettings = true }) { return () => { window.electron.ipcRenderer.removeListener(IPC_EVENT.DEVICE_SEARCH_REPLY, handler) } - }, []) + }, [setDeviceList_store]) // 监听设备连接/断开结果 useEffect(() => { const connectHandler = (event, result) => { if (result.success) { setConnected(result.ip) + // 更新到store中 + setConnectedDevice({ + ip: result.ip, + port: devicePort, + connectedAt: new Date().toISOString() + }) message.success('设备连接成功') } else { setConnected(null) + clearConnectedDevice() message.error('设备连接失败: ' + (result.error || '未知错误')) } } const disconnectHandler = (event, result) => { setConnected(null) + // 清除store中的连接信息 + clearConnectedDevice() if (result.success) { message.success('设备已断开') } else { @@ -67,7 +86,7 @@ function SiderHeader({ showSystemSettings = true }) { disconnectHandler ) } - }, []) + }, [devicePort, setConnectedDevice, clearConnectedDevice]) // 搜索设备 const handleSearchDevice = () => { diff --git a/src/renderer/src/stores/deviceStore.js b/src/renderer/src/stores/deviceStore.js new file mode 100644 index 0000000..441a48d --- /dev/null +++ b/src/renderer/src/stores/deviceStore.js @@ -0,0 +1,32 @@ +import { create } from 'zustand' + +const useDeviceStore = create((set) => ({ + // 当前连接的设备信息 + connectedDevice: null, + + // 设备列表 + deviceList: [], + + // 连接状态 + isConnecting: false, + + // 设置连接的设备 + setConnectedDevice: (device) => set({ connectedDevice: device }), + + // 清除连接的设备 + clearConnectedDevice: () => set({ connectedDevice: null }), + + // 设置设备列表 + setDeviceList: (devices) => set({ deviceList: devices }), + + // 设置连接状态 + setConnecting: (status) => set({ isConnecting: status }), + + // 获取当前连接的IP + getConnectedIp: () => { + const state = useDeviceStore.getState() + return state.connectedDevice?.ip || null + } +})) + +export default useDeviceStore