光伏pc端应用
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.
 

239 lines
7.3 KiB

/**
* USB Serial JS 桥接模块
* 实现 USB 串口设备的发现、探测、连接及数据读写
* 接口规范: docs/webview-usb-serial.md
*/
const { SerialPort } = require('serialport')
const { ipcMain } = require('electron')
class UsbSerialBridge {
constructor(mainWindow) {
this.mainWindow = mainWindow
this.activeConnections = new Map()
this.discoveredDevices = new Set()
this.activeProbes = new Map()
this._setupIPC()
}
_emit(event, data) {
if (this.mainWindow && !this.mainWindow.isDestroyed()) {
this.mainWindow.webContents.send(event, JSON.stringify(data))
}
}
_setupIPC() {
ipcMain.on('usb-start-discovery', () => this._startDiscovery())
ipcMain.on('usb-stop-discovery', () => this._stopDiscovery())
ipcMain.handle('usb-get-discovered', () => this._getDiscoveredDevices())
ipcMain.on('usb-device-probe', (_e, p) => this._deviceProbe(p))
ipcMain.on('usb-device-open', (_e, p) => this._deviceOpen(p))
ipcMain.on('usb-device-close', (_e, p) => this._deviceClose(p))
ipcMain.on('usb-device-write', (_e, p) => this._deviceWrite(p))
}
async _startDiscovery() {
console.log('[usb] 开始发现串口设备...')
try {
const ports = await SerialPort.list()
for (const p of ports) {
if (!this.discoveredDevices.has(p.path)) {
this.discoveredDevices.add(p.path)
console.log('[usb] 发现设备:', p.path)
this._emit('usb-callback-device-attached', {
deviceName: p.path,
deviceId: parseInt(p.vendorId || '0', 16) || 0,
ports: [1]
})
}
}
console.log('[usb] 发现完成, 共', ports.length, '个设备')
} catch (err) {
console.error('[usb] 发现失败:', err.message)
}
}
_stopDiscovery() {
console.log('[usb] 停止发现')
}
_getDiscoveredDevices() {
return JSON.stringify([])
}
async _deviceProbe({ deviceName, portNumber, baudRate, hexCmd, timeoutMs }) {
const probeId = deviceName + ':' + portNumber
console.log('[usb] 探测:', deviceName, 'port=', portNumber, 'baud=', baudRate)
if (this.activeProbes.has(probeId)) {
const old = this.activeProbes.get(probeId)
clearTimeout(old.timeout)
try { old.port.close() } catch {}
this.activeProbes.delete(probeId)
}
try {
const port = new SerialPort({
path: deviceName,
baudRate: baudRate || 9600,
dataBits: 8,
parity: 'none',
stopBits: 1,
autoOpen: false,
dtr: false,
rts: false
})
await new Promise((resolve, reject) => {
port.open(err => err ? reject(new Error(err.message)) : resolve())
})
let responseHex = ''
const timeout = setTimeout(() => {
this.activeProbes.delete(probeId)
try { port.close() } catch {}
const success = responseHex.length > 0
console.log('[usb] 探测' + (success ? '成功' : '超时') + ':', deviceName, '响应=' + (responseHex || '无'))
this._emit('usb-callback-device-probe-result', {
deviceName, port: portNumber, baudRate,
success, responseHex, error: success ? '' : 'No response'
})
}, timeoutMs || 1000)
port.on('data', (data) => {
responseHex += data.toString('hex').toUpperCase()
})
port.on('error', (err) => {
console.error('[usb] 探测错误:', err.message)
})
this.activeProbes.set(probeId, { timeout, port })
if (hexCmd) {
const cmdBuf = Buffer.from(hexCmd, 'hex')
port.write(cmdBuf, (err) => {
if (err) {
clearTimeout(timeout)
this.activeProbes.delete(probeId)
try { port.close() } catch {}
this._emit('usb-callback-device-probe-result', {
deviceName, port: portNumber, baudRate,
success: false, responseHex: '', error: err.message
})
} else {
console.log('[usb] 已发送探测命令:', hexCmd)
}
})
} else {
console.log('[usb] 不发送探测命令, 等待自动上报...')
}
} catch (err) {
console.error('[usb] 探测失败:', err.message)
this._emit('usb-callback-device-probe-result', {
deviceName, port: portNumber, baudRate,
success: false, responseHex: '', error: err.message
})
}
}
async _deviceOpen({ deviceName, portNumber, baudRate }) {
const connKey = deviceName + ':' + portNumber
console.log('[usb] 打开:', connKey, 'baud=', baudRate)
if (this.activeConnections.has(connKey)) {
try { this.activeConnections.get(connKey).port.close() } catch {}
this.activeConnections.delete(connKey)
}
try {
const port = new SerialPort({
path: deviceName,
baudRate: baudRate || 9600,
dataBits: 8,
parity: 'none',
stopBits: 1,
autoOpen: false,
dtr: false,
rts: false
})
await new Promise((resolve, reject) => {
port.open(err => err ? reject(new Error(err.message)) : resolve())
})
const entry = { port, deviceName, portNumber }
this.activeConnections.set(connKey, entry)
console.log('[usb] 打开成功:', connKey)
this._emit('usb-callback-device-opened', {
deviceName, port: portNumber, success: true, error: ''
})
this._emit('usb-callback-device-state', {
deviceName, port: portNumber, state: 'CONNECTED'
})
port.on('data', (data) => {
this._emit('usb-callback-device-data', {
deviceName,
port: portNumber,
hexData: data.toString('hex').toUpperCase()
})
})
port.on('error', (err) => {
console.error('[usb] 连接错误:', err.message)
})
port.on('close', () => {
console.log('[usb] 连接关闭:', connKey)
this.activeConnections.delete(connKey)
this._emit('usb-callback-device-state', {
deviceName, port: portNumber, state: 'DISCONNECTED'
})
})
} catch (err) {
console.error('[usb] 打开失败:', err.message)
this._emit('usb-callback-device-opened', {
deviceName, port: portNumber, success: false, error: err.message
})
}
}
_deviceClose({ deviceName, portNumber }) {
const connKey = deviceName + ':' + portNumber
console.log('[usb] 关闭:', connKey)
if (this.activeConnections.has(connKey)) {
const entry = this.activeConnections.get(connKey)
try { entry.port.close() } catch {}
this.activeConnections.delete(connKey)
}
}
_deviceWrite({ deviceName, portNumber, hexData }) {
const connKey = deviceName + ':' + portNumber
console.log('[usb] 写入:', connKey, 'hex=' + hexData)
if (!this.activeConnections.has(connKey)) {
console.error('[usb] 写入失败:', connKey, '未连接')
return
}
const entry = this.activeConnections.get(connKey)
const buf = Buffer.from(hexData, 'hex')
entry.port.write(buf, (err) => {
if (err) {
console.error('[usb] 写入错误:', err.message)
}
})
}
disconnectAll() {
for (const [key, entry] of this.activeConnections) {
console.log('[usb] 断开:', key)
try { entry.port.close() } catch {}
}
this.activeConnections.clear()
}
}
module.exports = { UsbSerialBridge }