import { ipcMain } from 'electron' import { dialog, shell } from 'electron' import dgram from 'dgram' import net from 'net' import { appendFileSync, mkdirSync, existsSync } from 'fs' import { dirname } from 'path' import { IPC_EVENT } from '../renderer/src/common/ipcEvents.js' import log from 'electron-log' import ReconnectManager from './reconnectManager.js' const TIMEOUT = 10000 // 10秒超时 const END_SEQUENCE = '\n\n' // 消息结束标志 // 全局保存所有TCP连接和相关信息 const tcpClients = new Map() // 保存待处理的请求,用于关联响应 const pendingRequests = new Map() // 创建重连管理器实例 const reconnectManager = new ReconnectManager() // 处理重连尝试的回调函数 const handleAttemptReconnect = (ip, callback) => { // 获取重连信息 const connectionData = reconnectManager.getReconnectStatus(ip) if (!connectionData.config.enabled) { callback(false, 'Reconnect disabled') return } log.info(`Executing reconnect attempt for ${ip}`) const client = new net.Socket() // 设置连接超时为重连间隔的80%,确保在下次重连前完成 const reconnectInterval = reconnectManager.getConfig().interval * 1000 const connectionTimeout = Math.max(3000, Math.floor(reconnectInterval * 0.8)) client.setTimeout(connectionTimeout) log.debug( `Connection timeout set to ${connectionTimeout}ms (reconnect interval: ${reconnectInterval}ms)` ) // 需要从某处获取端口和eventSender const storedConnectionData = tcpClients.get(ip) || tcpConnectionData.get(ip) if (!storedConnectionData) { callback(false, 'Connection data not found') return } const connectionInfo = { client, eventSender: storedConnectionData.eventSender, ip: ip, port: storedConnectionData.port } client.connect(Number(storedConnectionData.port), ip, () => { log.info(`Reconnected successfully to ${ip}:${storedConnectionData.port}`) // 连接成功,保存连接信息 tcpClients.set(ip, connectionInfo) // 启动心跳检测 startHeartbeatCheck(ip) // 通知渲染进程重连成功 storedConnectionData.eventSender.send(IPC_EVENT.DEVICE_CONNECT_REPLY, { success: true, ip: ip, reconnected: true }) callback(true) }) // 设置数据处理 let buffer = '' client.on('data', (data) => { buffer += data.toString() let index while ((index = buffer.indexOf('\n')) !== -1) { const line = buffer.slice(0, index) buffer = buffer.slice(index + 1) if (!line.trim()) continue let msg try { msg = JSON.parse(line) } catch (e) { log.error('TCP reconnect data parse error:', e) continue } if (!msg || !msg.command) continue handleTcpResponse(ip, msg) } }) client.on('error', (err) => { log.warn(`Reconnect attempt failed for ${ip}:`, err.message) client.destroy() callback(false, err) }) client.on('timeout', () => { log.warn(`Reconnect attempt timeout for ${ip}`) client.destroy() callback(false, new Error('Connection timeout')) }) client.on('close', () => { log.debug(`Reconnect attempt connection closed for ${ip}`) // 连接关闭由心跳检测处理 }) } // 处理重连状态更新的回调函数 const handleReconnectStatus = (status) => { // 通知所有渲染进程重连状态变化 const storedConnectionData = tcpConnectionData.get(status.ip) if (storedConnectionData && storedConnectionData.eventSender) { log.info( `Sending reconnect status update for ${status.ip}: ${status.isReconnecting ? 'reconnecting' : 'idle'}` ) storedConnectionData.eventSender.send(IPC_EVENT.RECONNECT_STATUS, { ip: status.ip, status: status.isReconnecting ? 'reconnecting' : 'idle', attempts: status.attempts, maxAttempts: status.maxAttempts, lastError: status.lastError }) } else { log.warn( `No stored connection data found for ${status.ip}, cannot send reconnect status update` ) // 如果找不到存储的连接数据,尝试从活动连接中获取 const activeConnection = tcpClients.get(status.ip) if (activeConnection && activeConnection.eventSender) { log.info(`Using active connection data to send reconnect status for ${status.ip}`) activeConnection.eventSender.send(IPC_EVENT.RECONNECT_STATUS, { ip: status.ip, status: status.isReconnecting ? 'reconnecting' : 'idle', attempts: status.attempts, maxAttempts: status.maxAttempts, lastError: status.lastError }) } } } // 配置重连管理器事件监听 reconnectManager.on('attempt-reconnect', handleAttemptReconnect) reconnectManager.on('reconnect-status', handleReconnectStatus) // 存储连接数据以供重连使用 const tcpConnectionData = new Map() // 存储连接参数 // 心跳检测相关配置 const HEARTBEAT_INTERVAL = 5000 // 心跳检测间隔 5秒(设备每2秒发送,我们5秒检测一次) const HEARTBEAT_TIMEOUT = 10000 // 心跳超时时间 10秒(设备每2秒发送,超过10秒没收到就认为断开) const heartbeatTimers = new Map() // 保存每个IP的心跳检测定时器 const lastHeartbeatTime = new Map() // 保存每个IP的最后心跳时间 // 记录IPC命令的通用函数(事件类型自动拼接) const logIPCCommand = (ip, command) => { const eventType = `${command.command?.toUpperCase()}_${command.type?.toUpperCase()}` log.info(`IPC Command from renderer - ${eventType} to ${ip}:`, command) } 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.RECONNECT_CONFIG, handleReconnectConfig) ipcMain.handle(IPC_EVENT.SENSORS_GET, sensorLoad) // 获取传感器 ipcMain.handle(IPC_EVENT.SENSORS_SET, sensorSet) // 传感器设置处理 ipcMain.handle(IPC_EVENT.IMAGE_SEND_TIME_GET, imageSendTimeGet) ipcMain.handle(IPC_EVENT.IMAGE_SEND_TIME_SET, imageSendTimeSet) // 其他基本参数GET处理 ipcMain.handle(IPC_EVENT.ZERO_COUNT_GET, zeroCountGet) ipcMain.handle(IPC_EVENT.RESULT_COUNT_GET, resultCountGet) ipcMain.handle(IPC_EVENT.DATA_FPS_GET, dataFpsGet) ipcMain.handle(IPC_EVENT.VIDEO_FPS_GET, videoFpsGet) ipcMain.handle(IPC_EVENT.THRESHOLD_GET, thresholdGet) ipcMain.handle(IPC_EVENT.INVALID_DATA_COUNT_GET, invalidDataCountGet) // 其他基本参数SET处理 ipcMain.handle(IPC_EVENT.ZERO_COUNT_SET, zeroCountSet) ipcMain.handle(IPC_EVENT.RESULT_COUNT_SET, resultCountSet) ipcMain.handle(IPC_EVENT.DATA_FPS_SET, dataFpsSet) ipcMain.handle(IPC_EVENT.VIDEO_FPS_SET, videoFpsSet) ipcMain.handle(IPC_EVENT.THRESHOLD_SET, thresholdSet) ipcMain.handle(IPC_EVENT.INVALID_DATA_COUNT_SET, invalidDataCountSet) ipcMain.handle(IPC_EVENT.IMAGE_SEND_ENABLED, imageSendEnabledSet) ipcMain.handle(IPC_EVENT.IS_CLEAR_ZERO, isClearZeroSet) // 存储目录相关处理 ipcMain.handle(IPC_EVENT.OPEN_DIRECTORY, openDirectory) ipcMain.handle(IPC_EVENT.SELECT_DIRECTORY, selectDirectory) // 文件操作相关处理 ipcMain.handle(IPC_EVENT.ENSURE_DIRECTORY, ensureDirectory) ipcMain.handle(IPC_EVENT.APPEND_TO_FILE, appendToFile) ipcMain.handle(IPC_EVENT.CHECK_FILE_EXISTS, checkFileExists) ipcMain.handle(IPC_EVENT.WRITE_CSV_HEADER, writeCSVHeader) } // 搜索设备 const searchDevice = (event) => { const message = Buffer.from(JSON.stringify({ command: 'name', type: 'get' })) const PORT = 2230 const BROADCAST_ADDR = '255.255.255.255' const udpClient = dgram.createSocket('udp4') const UDP_SEARCH_TIMEOUT = 5000 // 5秒总超时时间 const UDP_RESPONSE_DELAY = 1000 // 收到响应后等1秒收集更多设备 let timer = null let globalTimer = null const resultMap = new Map() // 清理资源的函数 const cleanup = () => { if (timer) clearTimeout(timer) if (globalTimer) clearTimeout(globalTimer) if (!udpClient.destroyed) { udpClient.close() } } // 发送结果并清理 const sendResultsAndCleanup = () => { const results = Array.from(resultMap.values()) log.info(`UDP search completed, found ${results.length} devices`) cleanup() event.reply && event.reply(IPC_EVENT.DEVICE_SEARCH_REPLY, results) } // 设置全局超时 - 无论如何5秒后结束搜索 globalTimer = setTimeout(() => { log.info('UDP search timeout - no devices found within timeout period') sendResultsAndCleanup() }, UDP_SEARCH_TIMEOUT) udpClient.bind(() => { udpClient.setBroadcast(true) udpClient.send(message, 0, message.length, PORT, BROADCAST_ADDR, (err) => { if (err) { log.error('UDP send failed', err) sendResultsAndCleanup() } else { log.info('UDP broadcast sent, waiting for responses...') } }) }) udpClient.on('message', (msg, rinfo) => { try { resultMap.set(rinfo.address, { from: rinfo.address, data: msg.toString() }) log.info(`UDP response from ${rinfo.address}:`, msg.toString()) } catch (e) { log.error('UDP message parse error', e) } // 每次收到消息后,重置响应延迟定时器 if (timer) clearTimeout(timer) timer = setTimeout(() => { log.info('UDP response collection timeout reached') sendResultsAndCleanup() }, UDP_RESPONSE_DELAY) }) udpClient.on('error', (err) => { log.error('UDP error:', err) sendResultsAndCleanup() }) } // 连接设备 const connectDevice = (event, { ip, port }) => { if (!ip || !port) { event.reply(IPC_EVENT.DEVICE_CONNECT_REPLY, { success: false, error: '参数缺失' }) return } if (tcpClients.has(ip)) { 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, connectionInfo) // 启动心跳检测 startHeartbeatCheck(ip) }) let buffer = '' client.on('data', (data) => { buffer += data.toString() let index while ((index = buffer.indexOf('\n')) !== -1) { const line = buffer.slice(0, index) buffer = buffer.slice(index + 1) if (!line.trim()) continue let msg try { msg = JSON.parse(line) } catch (e) { log.error('TCP data parse error:', e) continue } if (!msg || !msg.command) { log.warn('invalid msg format:', msg) continue } // 处理TCP响应 handleTcpResponse(ip, msg) } }) client.on('error', (err) => { log.error('TCP connection 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', () => { log.info(`TCP connection to ${ip} closed`) const connectionInfo = tcpClients.get(ip) if (connectionInfo) { tcpClients.delete(ip) clearPendingRequestsByIp(ip) // 只有在非主动断开的情况下才启动重连 // 主动断开会先调用 disconnectDevice 函数 const reconnectConfig = reconnectManager.getConfig() if (reconnectConfig.enabled) { log.info(`Connection lost to ${ip}, starting reconnect...`) // 保存连接数据供重连使用 tcpConnectionData.set(ip, { port: port, eventSender: connectionInfo.eventSender }) reconnectManager.startReconnect(ip, 'Connection lost') } } }) } // 断开连接 const disconnectDevice = (event, { ip }) => { const connectionInfo = tcpClients.get(ip) if (connectionInfo) { // 停止重连和心跳检测 reconnectManager.stopReconnect(ip) stopHeartbeatCheck(ip) connectionInfo.client.destroy() tcpClients.delete(ip) tcpConnectionData.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 = async (ip, msg) => { const connectionInfo = tcpClients.get(ip) if (!connectionInfo) return // 记录TCP响应数据 const commandType = `${msg.command}:${msg.type}` if ( commandType !== IPC_EVENT.RESULT_REPLY && commandType !== IPC_EVENT.IMAGE_REPLY && commandType !== IPC_EVENT.HEARTBEAT_REPLY ) { log.info(`TCP Response from ${ip}:`, commandType, msg.values || msg) } switch (commandType) { 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) { // 响应传感器加载请求 await delay() sensorRequest.resolve({ success: true, data: msg }) pendingRequests.delete(`${ip}${IPC_EVENT.SENSORS_GET}`) } } break case IPC_EVENT.SENSORS_SET: { const sensorSetRequest = pendingRequests.get(`${ip}${IPC_EVENT.SENSORS_SET}`) if (sensorSetRequest) { // 响应传感器设置请求 await delay() sensorSetRequest.resolve({ success: true, data: msg }) pendingRequests.delete(`${ip}${IPC_EVENT.SENSORS_SET}`) } } break case IPC_EVENT.IMAGE_SEND_TIME_GET: { const imageTimeRequest = pendingRequests.get(`${ip}${IPC_EVENT.IMAGE_SEND_TIME_GET}`) if (imageTimeRequest) { // 响应图像发送间隔获取请求 await delay() imageTimeRequest.resolve({ success: true, data: msg }) pendingRequests.delete(`${ip}${IPC_EVENT.IMAGE_SEND_TIME_GET}`) } } break case IPC_EVENT.IMAGE_SEND_TIME_SET: { const imageTimeRequest = pendingRequests.get(`${ip}${IPC_EVENT.IMAGE_SEND_TIME_SET}`) if (imageTimeRequest) { // 响应图像发送间隔设置请求 await delay() imageTimeRequest.resolve({ success: true, data: msg }) pendingRequests.delete(`${ip}${IPC_EVENT.IMAGE_SEND_TIME_SET}`) } } break // 其他基本参数GET响应处理 case IPC_EVENT.ZERO_COUNT_GET: { const paramRequest = pendingRequests.get(`${ip}${IPC_EVENT.ZERO_COUNT_GET}`) if (paramRequest) { await delay() paramRequest.resolve({ success: true, data: msg }) pendingRequests.delete(`${ip}${IPC_EVENT.ZERO_COUNT_GET}`) } } break case IPC_EVENT.RESULT_COUNT_GET: { const paramRequest = pendingRequests.get(`${ip}${IPC_EVENT.RESULT_COUNT_GET}`) if (paramRequest) { await delay() paramRequest.resolve({ success: true, data: msg }) pendingRequests.delete(`${ip}${IPC_EVENT.RESULT_COUNT_GET}`) } } break case IPC_EVENT.DATA_FPS_GET: { const paramRequest = pendingRequests.get(`${ip}${IPC_EVENT.DATA_FPS_GET}`) if (paramRequest) { await delay() paramRequest.resolve({ success: true, data: msg }) pendingRequests.delete(`${ip}${IPC_EVENT.DATA_FPS_GET}`) } } break case IPC_EVENT.VIDEO_FPS_GET: { const paramRequest = pendingRequests.get(`${ip}${IPC_EVENT.VIDEO_FPS_GET}`) if (paramRequest) { await delay() paramRequest.resolve({ success: true, data: msg }) pendingRequests.delete(`${ip}${IPC_EVENT.VIDEO_FPS_GET}`) } } break case IPC_EVENT.THRESHOLD_GET: { const paramRequest = pendingRequests.get(`${ip}${IPC_EVENT.THRESHOLD_GET}`) if (paramRequest) { await delay() paramRequest.resolve({ success: true, data: msg }) pendingRequests.delete(`${ip}${IPC_EVENT.THRESHOLD_GET}`) } } break case IPC_EVENT.INVALID_DATA_COUNT_GET: { const paramRequest = pendingRequests.get(`${ip}${IPC_EVENT.INVALID_DATA_COUNT_GET}`) if (paramRequest) { await delay() paramRequest.resolve({ success: true, data: msg }) pendingRequests.delete(`${ip}${IPC_EVENT.INVALID_DATA_COUNT_GET}`) } } break // 其他基本参数SET响应处理 case IPC_EVENT.ZERO_COUNT_SET: { const paramRequest = pendingRequests.get(`${ip}${IPC_EVENT.ZERO_COUNT_SET}`) if (paramRequest) { await delay() paramRequest.resolve({ success: true, data: msg }) pendingRequests.delete(`${ip}${IPC_EVENT.ZERO_COUNT_SET}`) } } break case IPC_EVENT.RESULT_COUNT_SET: { const paramRequest = pendingRequests.get(`${ip}${IPC_EVENT.RESULT_COUNT_SET}`) if (paramRequest) { await delay() paramRequest.resolve({ success: true, data: msg }) pendingRequests.delete(`${ip}${IPC_EVENT.RESULT_COUNT_SET}`) } } break case IPC_EVENT.DATA_FPS_SET: { const paramRequest = pendingRequests.get(`${ip}${IPC_EVENT.DATA_FPS_SET}`) if (paramRequest) { await delay() paramRequest.resolve({ success: true, data: msg }) pendingRequests.delete(`${ip}${IPC_EVENT.DATA_FPS_SET}`) } } break case IPC_EVENT.VIDEO_FPS_SET: { const paramRequest = pendingRequests.get(`${ip}${IPC_EVENT.VIDEO_FPS_SET}`) if (paramRequest) { await delay() paramRequest.resolve({ success: true, data: msg }) pendingRequests.delete(`${ip}${IPC_EVENT.VIDEO_FPS_SET}`) } } break case IPC_EVENT.THRESHOLD_SET: { const paramRequest = pendingRequests.get(`${ip}${IPC_EVENT.THRESHOLD_SET}`) if (paramRequest) { await delay() paramRequest.resolve({ success: true, data: msg }) pendingRequests.delete(`${ip}${IPC_EVENT.THRESHOLD_SET}`) } } break case IPC_EVENT.INVALID_DATA_COUNT_SET: { const paramRequest = pendingRequests.get(`${ip}${IPC_EVENT.INVALID_DATA_COUNT_SET}`) if (paramRequest) { await delay() paramRequest.resolve({ success: true, data: msg }) pendingRequests.delete(`${ip}${IPC_EVENT.INVALID_DATA_COUNT_SET}`) } } break case IPC_EVENT.IMAGE_SEND_ENABLED: { const imageSendRequest = pendingRequests.get(`${ip}${IPC_EVENT.IMAGE_SEND_ENABLED}`) if (imageSendRequest) { // 响应图像发送设置请求 await delay() imageSendRequest.resolve({ success: true, data: msg }) pendingRequests.delete(`${ip}${IPC_EVENT.IMAGE_SEND_ENABLED}`) } } break case IPC_EVENT.IS_CLEAR_ZERO: { const clearZeroRequest = pendingRequests.get(`${ip}${IPC_EVENT.IS_CLEAR_ZERO}`) if (clearZeroRequest) { // 响应清零使能设置请求 await delay() clearZeroRequest.resolve({ success: true, data: msg }) pendingRequests.delete(`${ip}${IPC_EVENT.IS_CLEAR_ZERO}`) } } break case IPC_EVENT.HEARTBEAT_REPLY: // 更新心跳时间戳(设备每2秒发送心跳包) lastHeartbeatTime.set(ip, Date.now()) // 心跳包不记录到日志,避免日志过多 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: '请求超时' }) }, TIMEOUT) // 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) + END_SEQUENCE logIPCCommand(ip, command) connectionInfo.client.write(message, (err) => { if (err) { pendingRequests.delete(requestKey) clearTimeout(timeout) resolve({ success: false, error: err.message }) } }) }) } // 传感器设置 const sensorSet = async (event, { ip, sensors }) => { return new Promise((resolve, reject) => { const connectionInfo = tcpClients.get(ip) if (!connectionInfo) { resolve({ success: false, error: '设备未连接' }) return } // 生成请求ID并保存待处理请求 const requestKey = `${ip}${IPC_EVENT.SENSORS_SET}` // 设置超时 const timeout = setTimeout(() => { pendingRequests.delete(requestKey) resolve({ success: false, error: '请求超时' }) }, TIMEOUT) // 10秒超时 // 保存请求信息 pendingRequests.set(requestKey, { resolve: (result) => { clearTimeout(timeout) resolve(result) }, reject: (error) => { clearTimeout(timeout) reject(error) }, timestamp: Date.now() }) // 发送传感器设置命令 const command = { command: 'sensors', type: 'set', values: sensors } const message = JSON.stringify(command) + END_SEQUENCE logIPCCommand(ip, command) connectionInfo.client.write(message, (err) => { if (err) { pendingRequests.delete(requestKey) clearTimeout(timeout) resolve({ success: false, error: err.message }) } }) }) } // 图像发送间隔获取 const imageSendTimeGet = 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.IMAGE_SEND_TIME_GET}` // 设置超时 const timeout = setTimeout(() => { pendingRequests.delete(requestKey) resolve({ success: false, error: '请求超时' }) }, TIMEOUT) // 10秒超时 // 保存请求信息 pendingRequests.set(requestKey, { resolve: (result) => { clearTimeout(timeout) resolve(result) }, reject: (error) => { clearTimeout(timeout) reject(error) }, timestamp: Date.now() }) // 发送图像发送间隔获取命令 const command = { command: 'imageSendTime', type: 'get', values: '' } const message = JSON.stringify(command) + END_SEQUENCE logIPCCommand(ip, command) connectionInfo.client.write(message, (err) => { if (err) { pendingRequests.delete(requestKey) clearTimeout(timeout) resolve({ success: false, error: err.message }) } }) }) } // 图像发送间隔设置 const imageSendTimeSet = async (event, { ip, value }) => { return new Promise((resolve, reject) => { const connectionInfo = tcpClients.get(ip) if (!connectionInfo) { resolve({ success: false, error: '设备未连接' }) return } // 生成请求ID并保存待处理请求 const requestKey = `${ip}${IPC_EVENT.IMAGE_SEND_TIME_SET}` // 设置超时 const timeout = setTimeout(() => { pendingRequests.delete(requestKey) resolve({ success: false, error: '请求超时' }) }, TIMEOUT) // 10秒超时 // 保存请求信息 pendingRequests.set(requestKey, { resolve: (result) => { clearTimeout(timeout) resolve(result) }, reject: (error) => { clearTimeout(timeout) reject(error) }, timestamp: Date.now() }) // 发送图像发送间隔设置命令 const command = { command: 'imageSendTime', type: 'set', values: value } const message = JSON.stringify(command) + END_SEQUENCE logIPCCommand(ip, command) connectionInfo.client.write(message, (err) => { if (err) { pendingRequests.delete(requestKey) clearTimeout(timeout) resolve({ success: false, error: err.message }) } }) }) } // zeroCount获取 const zeroCountGet = async (event, { ip }) => { return new Promise((resolve, reject) => { const connectionInfo = tcpClients.get(ip) if (!connectionInfo) { resolve({ success: false, error: '设备未连接' }) return } const requestKey = `${ip}${IPC_EVENT.ZERO_COUNT_GET}` const timeout = setTimeout(() => { pendingRequests.delete(requestKey) resolve({ success: false, error: '请求超时' }) }, TIMEOUT) pendingRequests.set(requestKey, { resolve: (result) => { clearTimeout(timeout) resolve(result) }, reject: (error) => { clearTimeout(timeout) reject(error) }, timestamp: Date.now() }) const command = { command: 'zeroCount', type: 'get', values: '' } const message = JSON.stringify(command) + END_SEQUENCE logIPCCommand(ip, command) connectionInfo.client.write(message, (err) => { if (err) { pendingRequests.delete(requestKey) clearTimeout(timeout) resolve({ success: false, error: err.message }) } }) }) } // zeroCount设置 const zeroCountSet = async (event, { ip, value }) => { return new Promise((resolve, reject) => { const connectionInfo = tcpClients.get(ip) if (!connectionInfo) { resolve({ success: false, error: '设备未连接' }) return } const requestKey = `${ip}${IPC_EVENT.ZERO_COUNT_SET}` const timeout = setTimeout(() => { pendingRequests.delete(requestKey) resolve({ success: false, error: '请求超时' }) }, TIMEOUT) pendingRequests.set(requestKey, { resolve: (result) => { clearTimeout(timeout) resolve(result) }, reject: (error) => { clearTimeout(timeout) reject(error) }, timestamp: Date.now() }) const command = { command: 'zeroCount', type: 'set', values: value } const message = JSON.stringify(command) + END_SEQUENCE logIPCCommand(ip, command) connectionInfo.client.write(message, (err) => { if (err) { pendingRequests.delete(requestKey) clearTimeout(timeout) resolve({ success: false, error: err.message }) } }) }) } // resultCount获取 const resultCountGet = async (event, { ip }) => { return new Promise((resolve, reject) => { const connectionInfo = tcpClients.get(ip) if (!connectionInfo) { resolve({ success: false, error: '设备未连接' }) return } const requestKey = `${ip}${IPC_EVENT.RESULT_COUNT_GET}` const timeout = setTimeout(() => { pendingRequests.delete(requestKey) resolve({ success: false, error: '请求超时' }) }, TIMEOUT) pendingRequests.set(requestKey, { resolve: (result) => { clearTimeout(timeout) resolve(result) }, reject: (error) => { clearTimeout(timeout) reject(error) }, timestamp: Date.now() }) const command = { command: 'resultCount', type: 'get', values: '' } const message = JSON.stringify(command) + END_SEQUENCE logIPCCommand('RESULT_COUNT_GET', ip, command) connectionInfo.client.write(message, (err) => { if (err) { pendingRequests.delete(requestKey) clearTimeout(timeout) resolve({ success: false, error: err.message }) } }) }) } // resultCount设置 const resultCountSet = async (event, { ip, value }) => { return new Promise((resolve, reject) => { const connectionInfo = tcpClients.get(ip) if (!connectionInfo) { resolve({ success: false, error: '设备未连接' }) return } const requestKey = `${ip}${IPC_EVENT.RESULT_COUNT_SET}` const timeout = setTimeout(() => { pendingRequests.delete(requestKey) resolve({ success: false, error: '请求超时' }) }, TIMEOUT) pendingRequests.set(requestKey, { resolve: (result) => { clearTimeout(timeout) resolve(result) }, reject: (error) => { clearTimeout(timeout) reject(error) }, timestamp: Date.now() }) const command = { command: 'resultCount', type: 'set', values: value } const message = JSON.stringify(command) + END_SEQUENCE logIPCCommand(ip, command) connectionInfo.client.write(message, (err) => { if (err) { pendingRequests.delete(requestKey) clearTimeout(timeout) resolve({ success: false, error: err.message }) } }) }) } // dataFps获取 const dataFpsGet = async (event, { ip }) => { return new Promise((resolve, reject) => { const connectionInfo = tcpClients.get(ip) if (!connectionInfo) { resolve({ success: false, error: '设备未连接' }) return } const requestKey = `${ip}${IPC_EVENT.DATA_FPS_GET}` const timeout = setTimeout(() => { pendingRequests.delete(requestKey) resolve({ success: false, error: '请求超时' }) }, TIMEOUT) pendingRequests.set(requestKey, { resolve: (result) => { clearTimeout(timeout) resolve(result) }, reject: (error) => { clearTimeout(timeout) reject(error) }, timestamp: Date.now() }) const command = { command: 'dataFps', type: 'get', values: '' } const message = JSON.stringify(command) + END_SEQUENCE logIPCCommand(ip, command) connectionInfo.client.write(message, (err) => { if (err) { pendingRequests.delete(requestKey) clearTimeout(timeout) resolve({ success: false, error: err.message }) } }) }) } // dataFps设置 const dataFpsSet = async (event, { ip, value }) => { return new Promise((resolve, reject) => { const connectionInfo = tcpClients.get(ip) if (!connectionInfo) { resolve({ success: false, error: '设备未连接' }) return } const requestKey = `${ip}${IPC_EVENT.DATA_FPS_SET}` const timeout = setTimeout(() => { pendingRequests.delete(requestKey) resolve({ success: false, error: '请求超时' }) }, TIMEOUT) pendingRequests.set(requestKey, { resolve: (result) => { clearTimeout(timeout) resolve(result) }, reject: (error) => { clearTimeout(timeout) reject(error) }, timestamp: Date.now() }) const command = { command: 'dataFps', type: 'set', values: value } const message = JSON.stringify(command) + END_SEQUENCE logIPCCommand(ip, command) connectionInfo.client.write(message, (err) => { if (err) { pendingRequests.delete(requestKey) clearTimeout(timeout) resolve({ success: false, error: err.message }) } }) }) } // videoFps获取 const videoFpsGet = async (event, { ip }) => { return new Promise((resolve, reject) => { const connectionInfo = tcpClients.get(ip) if (!connectionInfo) { resolve({ success: false, error: '设备未连接' }) return } const requestKey = `${ip}${IPC_EVENT.VIDEO_FPS_GET}` const timeout = setTimeout(() => { pendingRequests.delete(requestKey) resolve({ success: false, error: '请求超时' }) }, TIMEOUT) pendingRequests.set(requestKey, { resolve: (result) => { clearTimeout(timeout) resolve(result) }, reject: (error) => { clearTimeout(timeout) reject(error) }, timestamp: Date.now() }) const command = { command: 'videoFps', type: 'get', values: '' } const message = JSON.stringify(command) + END_SEQUENCE logIPCCommand(ip, command) connectionInfo.client.write(message, (err) => { if (err) { pendingRequests.delete(requestKey) clearTimeout(timeout) resolve({ success: false, error: err.message }) } }) }) } // videoFps设置 const videoFpsSet = async (event, { ip, value }) => { return new Promise((resolve, reject) => { const connectionInfo = tcpClients.get(ip) if (!connectionInfo) { resolve({ success: false, error: '设备未连接' }) return } const requestKey = `${ip}${IPC_EVENT.VIDEO_FPS_SET}` const timeout = setTimeout(() => { pendingRequests.delete(requestKey) resolve({ success: false, error: '请求超时' }) }, TIMEOUT) pendingRequests.set(requestKey, { resolve: (result) => { clearTimeout(timeout) resolve(result) }, reject: (error) => { clearTimeout(timeout) reject(error) }, timestamp: Date.now() }) const command = { command: 'videoFps', type: 'set', values: value } const message = JSON.stringify(command) + END_SEQUENCE logIPCCommand(ip, command) connectionInfo.client.write(message, (err) => { if (err) { pendingRequests.delete(requestKey) clearTimeout(timeout) resolve({ success: false, error: err.message }) } }) }) } // threshold获取 const thresholdGet = async (event, { ip }) => { return new Promise((resolve, reject) => { const connectionInfo = tcpClients.get(ip) if (!connectionInfo) { resolve({ success: false, error: '设备未连接' }) return } const requestKey = `${ip}${IPC_EVENT.THRESHOLD_GET}` const timeout = setTimeout(() => { pendingRequests.delete(requestKey) resolve({ success: false, error: '请求超时' }) }, TIMEOUT) pendingRequests.set(requestKey, { resolve: (result) => { clearTimeout(timeout) resolve(result) }, reject: (error) => { clearTimeout(timeout) reject(error) }, timestamp: Date.now() }) const command = { command: 'threshold', type: 'get', values: '' } const message = JSON.stringify(command) + END_SEQUENCE logIPCCommand(ip, command) connectionInfo.client.write(message, (err) => { if (err) { pendingRequests.delete(requestKey) clearTimeout(timeout) resolve({ success: false, error: err.message }) } }) }) } // threshold设置 const thresholdSet = async (event, { ip, value }) => { return new Promise((resolve, reject) => { const connectionInfo = tcpClients.get(ip) if (!connectionInfo) { resolve({ success: false, error: '设备未连接' }) return } const requestKey = `${ip}${IPC_EVENT.THRESHOLD_SET}` const timeout = setTimeout(() => { pendingRequests.delete(requestKey) resolve({ success: false, error: '请求超时' }) }, TIMEOUT) pendingRequests.set(requestKey, { resolve: (result) => { clearTimeout(timeout) resolve(result) }, reject: (error) => { clearTimeout(timeout) reject(error) }, timestamp: Date.now() }) const command = { command: 'threshold', type: 'set', values: value } const message = JSON.stringify(command) + END_SEQUENCE logIPCCommand(ip, command) connectionInfo.client.write(message, (err) => { if (err) { pendingRequests.delete(requestKey) clearTimeout(timeout) resolve({ success: false, error: err.message }) } }) }) } // invalidDataCount获取 const invalidDataCountGet = async (event, { ip }) => { return new Promise((resolve, reject) => { const connectionInfo = tcpClients.get(ip) if (!connectionInfo) { resolve({ success: false, error: '设备未连接' }) return } const requestKey = `${ip}${IPC_EVENT.INVALID_DATA_COUNT_GET}` const timeout = setTimeout(() => { pendingRequests.delete(requestKey) resolve({ success: false, error: '请求超时' }) }, TIMEOUT) pendingRequests.set(requestKey, { resolve: (result) => { clearTimeout(timeout) resolve(result) }, reject: (error) => { clearTimeout(timeout) reject(error) }, timestamp: Date.now() }) const command = { command: 'invalidDataCount', type: 'get', values: '' } const message = JSON.stringify(command) + END_SEQUENCE logIPCCommand(ip, command) connectionInfo.client.write(message, (err) => { if (err) { pendingRequests.delete(requestKey) clearTimeout(timeout) resolve({ success: false, error: err.message }) } }) }) } // invalidDataCount设置 const invalidDataCountSet = async (event, { ip, value }) => { return new Promise((resolve, reject) => { const connectionInfo = tcpClients.get(ip) if (!connectionInfo) { resolve({ success: false, error: '设备未连接' }) return } const requestKey = `${ip}${IPC_EVENT.INVALID_DATA_COUNT_SET}` const timeout = setTimeout(() => { pendingRequests.delete(requestKey) resolve({ success: false, error: '请求超时' }) }, TIMEOUT) pendingRequests.set(requestKey, { resolve: (result) => { clearTimeout(timeout) resolve(result) }, reject: (error) => { clearTimeout(timeout) reject(error) }, timestamp: Date.now() }) const command = { command: 'invalidDataCount', type: 'set', values: value } const message = JSON.stringify(command) + END_SEQUENCE logIPCCommand(ip, command) connectionInfo.client.write(message, (err) => { if (err) { pendingRequests.delete(requestKey) clearTimeout(timeout) resolve({ success: false, error: err.message }) } }) }) } // 图像发送使能设置 const imageSendEnabledSet = async (event, { ip, value }) => { return new Promise((resolve, reject) => { const connectionInfo = tcpClients.get(ip) if (!connectionInfo) { resolve({ success: false, error: '设备未连接' }) return } const requestKey = `${ip}${IPC_EVENT.IMAGE_SEND_ENABLED}` const timeout = setTimeout(() => { pendingRequests.delete(requestKey) resolve({ success: false, error: '请求超时' }) }, TIMEOUT) pendingRequests.set(requestKey, { resolve: (result) => { clearTimeout(timeout) resolve(result) }, reject: (error) => { clearTimeout(timeout) reject(error) }, timestamp: Date.now() }) const command = { command: 'imageSendEnabled', type: 'set', values: value } const message = JSON.stringify(command) + END_SEQUENCE logIPCCommand(ip, command) connectionInfo.client.write(message, (err) => { if (err) { pendingRequests.delete(requestKey) clearTimeout(timeout) resolve({ success: false, error: err.message }) } }) }) } const isClearZeroSet = async (event, { ip, value }) => { return new Promise((resolve, reject) => { const connectionInfo = tcpClients.get(ip) if (!connectionInfo) { resolve({ success: false, error: '设备未连接' }) return } const requestKey = `${ip}${IPC_EVENT.IS_CLEAR_ZERO}` const timeout = setTimeout(() => { pendingRequests.delete(requestKey) resolve({ success: false, error: '请求超时' }) }, TIMEOUT) pendingRequests.set(requestKey, { resolve: (result) => { clearTimeout(timeout) resolve(result) }, reject: (error) => { clearTimeout(timeout) reject(error) }, timestamp: Date.now() }) const command = { command: 'isClearZero', type: 'set', values: value } const message = JSON.stringify(command) + END_SEQUENCE logIPCCommand(ip, command) 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)) } //模拟延迟函数 const delay = () => { return new Promise((resolve) => setTimeout(resolve, 1000)) } // 处理重连配置 const handleReconnectConfig = (event, { enabled, interval }) => { const validInterval = Math.max(4, interval) // 最小4秒 // 更新重连管理器配置 reconnectManager.updateConfig({ enabled, interval: validInterval }) log.info('Reconnect config updated:', { enabled, interval: validInterval }) } // 启动心跳检测 const startHeartbeatCheck = (ip) => { // 清除之前的心跳检测定时器 stopHeartbeatCheck(ip) // 初始化心跳时间 lastHeartbeatTime.set(ip, Date.now()) // 设置心跳检测定时器 const timer = setInterval(() => { checkHeartbeat(ip) }, HEARTBEAT_INTERVAL) heartbeatTimers.set(ip, timer) log.info(`Started heartbeat check for ${ip}`) } // 检查心跳超时 const checkHeartbeat = (ip) => { const lastTime = lastHeartbeatTime.get(ip) const currentTime = Date.now() if (!lastTime) { log.warn(`No heartbeat recorded for ${ip}`) return } const timeSinceLastHeartbeat = currentTime - lastTime if (timeSinceLastHeartbeat > HEARTBEAT_TIMEOUT) { log.warn( `Heartbeat timeout for ${ip}: ${timeSinceLastHeartbeat}ms since last heartbeat (timeout: ${HEARTBEAT_TIMEOUT}ms)` ) // 心跳超时,主动断开连接并启动重连 const connectionInfo = tcpClients.get(ip) if (connectionInfo) { log.info(`Force disconnecting ${ip} due to heartbeat timeout`) // 停止心跳检测 stopHeartbeatCheck(ip) // 销毁连接 connectionInfo.client.destroy() tcpClients.delete(ip) clearPendingRequestsByIp(ip) // 通知渲染进程连接断开 connectionInfo.eventSender.send(IPC_EVENT.DEVICE_DISCONNECT_REPLY, { success: true, reason: 'heartbeat_timeout' }) // 启动重连(如果启用) const reconnectConfig = reconnectManager.getConfig() if (reconnectConfig.enabled) { log.info(`Connection lost to ${ip} due to heartbeat timeout, starting reconnect...`) // 保存连接数据供重连使用 tcpConnectionData.set(ip, { port: connectionInfo.port, eventSender: connectionInfo.eventSender }) reconnectManager.startReconnect(ip, 'Heartbeat timeout') } } } else { // 心跳正常,记录调试信息 // log.debug(`Heartbeat OK for ${ip}: ${timeSinceLastHeartbeat}ms since last heartbeat`) } } // 停止心跳检测 const stopHeartbeatCheck = (ip) => { const timer = heartbeatTimers.get(ip) if (timer) { clearInterval(timer) heartbeatTimers.delete(ip) } lastHeartbeatTime.delete(ip) log.info(`Stopped heartbeat check for ${ip}`) } // 存储目录相关处理函数 const openDirectory = async (event, path) => { try { await shell.openPath(path) log.info(`Opened directory: ${path}`) return { success: true } } catch (error) { log.error('Failed to open directory:', error) return { success: false, error: error.message } } } const selectDirectory = async (event, options) => { try { const result = await dialog.showOpenDialog({ title: options.title || '选择目录', properties: ['openDirectory', 'createDirectory'], defaultPath: options.defaultPath }) if (result.canceled) { return { success: false, canceled: true } } const selectedPath = result.filePaths[0] log.info(`Directory selected: ${selectedPath}`) return { success: true, path: selectedPath } } catch (error) { log.error('Failed to select directory:', error) return { success: false, error: error.message } } } // 确保目录存在 const ensureDirectory = async (event, dirPath) => { try { if (!existsSync(dirPath)) { mkdirSync(dirPath, { recursive: true }) log.info(`Created directory: ${dirPath}`) } return { success: true } } catch (error) { log.error('Failed to create directory:', error) return { success: false, error: error.message } } } // 追加内容到文件 const appendToFile = async (event, filePath, content) => { try { // 确保父目录存在 const dir = dirname(filePath) if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }) } appendFileSync(filePath, content, 'utf8') return { success: true } } catch (error) { log.error('Failed to append to file:', error) return { success: false, error: error.message } } } // 写入CSV文件头(包含BOM) const writeCSVHeader = async (event, filePath, content) => { try { // 确保父目录存在 const dir = dirname(filePath) if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }) } // 添加UTF-8 BOM以确保Excel正确显示中文 const bom = '\uFEFF' appendFileSync(filePath, bom + content, 'utf8') log.info(`Created CSV file with header: ${filePath}`) return { success: true } } catch (error) { log.error('Failed to write CSV header:', error) return { success: false, error: error.message } } } // 检查文件是否存在 const checkFileExists = async (event, filePath) => { try { const exists = existsSync(filePath) return { success: true, exists } } catch (error) { log.error('Failed to check file existence:', error) return { success: false, error: error.message } } }