光电挠度仪上位机
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.
 
 
 

1554 lines
46 KiB

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 }
}
}