class CommDebugger { constructor() { this.ws = null; this.isConnected = false; this.currentPort = 1; // 为每个端口维护监听状态 this.portMonitoringStatus = {}; this.initializeElements(); this.bindEvents(); // 设置默认端口显示 this.portSelect.value = this.currentPort; this.connect(); } initializeElements() { this.portSelect = document.getElementById('portSelect'); this.portOptions = document.getElementById('portOptions'); this.startBtn = document.getElementById('startBtn'); this.stopBtn = document.getElementById('stopBtn'); this.clearBtn = document.getElementById('clearBtn'); this.readData = document.getElementById('readData'); this.readSendBtn = document.getElementById('readSendBtn'); this.writeData = document.getElementById('writeData'); this.writeSendBtn = document.getElementById('writeSendBtn'); this.logOutput = document.getElementById('logOutput'); this.connectionStatus = document.getElementById('connectionStatus'); // 新增的复选框元素 this.writeToWriteSendCheckbox = document.getElementById('writeToWriteSend'); this.readToReadSendCheckbox = document.getElementById('readToReadSend'); this.readToModifyDataCheckbox = document.getElementById('readToModifyData'); // 数据表格元素 this.dataTable = document.getElementById('dataTable'); } bindEvents() { // 端口选择框事件处理 this.portSelect.addEventListener('focus', () => { this.portOptions.classList.add('show'); }); this.portSelect.addEventListener('click', () => { this.portOptions.classList.toggle('show'); }); this.portSelect.addEventListener('blur', () => { // 添加延迟以确保点击选项时不会立即隐藏 setTimeout(() => { this.portOptions.classList.remove('show'); }, 200); }); this.portSelect.addEventListener('input', (e) => { this.currentPort = this.getPortNumber(e.target.value); }); // 端口选项点击事件 const portOptions = this.portOptions.querySelectorAll('.port-option'); portOptions.forEach(option => { option.addEventListener('mousedown', (e) => { e.preventDefault(); // 防止blur事件触发 this.portSelect.value = e.target.getAttribute('data-value'); this.currentPort = this.getPortNumber(this.portSelect.value); this.portOptions.classList.remove('show'); this.portSelect.focus(); }); }); this.startBtn.addEventListener('click', () => { this.startMonitoring(); }); this.stopBtn.addEventListener('click', () => { this.stopMonitoring(); }); this.clearBtn.addEventListener('click', () => { this.clearLog(); }); this.readSendBtn.addEventListener('click', () => { this.sendReadData(); }); this.writeSendBtn.addEventListener('click', () => { this.sendWriteData(); }); // 复选框事件处理 this.writeToWriteSendCheckbox.addEventListener('change', () => { this.handleWriteToWriteSendChange(); }); this.readToReadSendCheckbox.addEventListener('change', () => { this.handleReadToReadSendChange(); }); this.readToModifyDataCheckbox.addEventListener('change', () => { this.handleReadToModifyDataChange(); }); // 初始化WebSocket连接 // this.connect(); // 移除重复调用,只在构造函数中调用一次 } // 获取端口号(支持COMx格式或纯数字) getPortNumber(portStr) { if (!portStr) return 1; // 如果是COMx格式,提取数字部分 const comMatch = portStr.match(/^COM(\d+)$/i); if (comMatch) { return parseInt(comMatch[1]); } // 如果是纯数字格式 const num = parseInt(portStr); if (!isNaN(num) && num > 0) { return num; } // 默认返回1 return 1; } connect() { try { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws`; this.ws = new WebSocket(wsUrl); this.ws.onopen = () => { this.isConnected = true; this.updateConnectionStatus(true); this.log('WebSocket连接已建立', 'success'); }; this.ws.onmessage = (event) => { this.handleMessage(event.data); }; this.ws.onclose = () => { this.isConnected = false; this.updateConnectionStatus(false); this.log('WebSocket连接已断开', 'error'); // 自动重连 setTimeout(() => { if (!this.isConnected) { this.log('尝试重新连接...', 'info'); this.connect(); } }, 3000); }; this.ws.onerror = (error) => { this.log('WebSocket连接错误: ' + error, 'error'); }; } catch (error) { this.log('连接失败: ' + error.message, 'error'); } } handleMessage(data) { try { const message = JSON.parse(data); switch (message.cmd) { case 'InitMonitor': this.handleInitMonitorResponse(message); break; case 'StopPort': this.handleStopPortResponse(message); break; case 'ReadData': this.handleReadDataResponse(message); break; case 'WriteData': this.handleWriteDataResponse(message); break; case 'OP_READ': this.handleReadEvent(message); break; case 'OP_WRITE': this.handleWriteEvent(message); break; case 'OP_OPEN': this.handleOpenEvent(message); break; case 'OP_CLOSE': this.handleCloseEvent(message); break; default: this.log('未知消息类型: ' + message.cmd, 'warning'); } } catch (error) { this.log('解析消息失败: ' + error.message, 'error'); } } handleInitMonitorResponse(message) { if (message.result) { // 更新特定端口的监听状态 this.portMonitoringStatus[message.port] = true; this.log(`${message.port} 开始监听成功`, 'success'); } else { this.log(`${message.port} 开始监听失败`, 'error'); } } handleStopPortResponse(message) { if (message.result) { // 更新特定端口的监听状态 this.portMonitoringStatus[message.port] = false; this.log(`${message.port} 停止监听成功`, 'success'); } else { this.log(`${message.port} 停止监听失败`, 'error'); } } handleReadDataResponse(message) { if (message.result) { this.log(`${message.port} 读数据发送成功`, 'info'); } else { this.log(`${message.port} 读数据发送失败`, 'error'); } } handleWriteDataResponse(message) { if (message.result) { this.log(`${message.port} 写数据发送成功`, 'info'); } else { this.log(`${message.port} 写数据发送失败`, 'error'); } } handleReadEvent(message) { this.log(`${message.port} 读取数据: ${message.data}`, 'read'); } handleWriteEvent(message) { this.log(`${message.port} 写入数据: ${message.data}`, 'write'); } handleOpenEvent(message) { this.log(`${message.port} 端口已打开`, 'open'); } handleCloseEvent(message) { this.log(`${message.port} 端口已关闭`, 'close'); } startMonitoring() { if (!this.isConnected) { this.log('WebSocket未连接', 'error'); return; } const message = { cmd: 'InitMonitor', port: this.currentPort }; this.sendMessage(message); } stopMonitoring() { if (!this.isConnected) { this.log('WebSocket未连接', 'error'); return; } const message = { cmd: 'StopPort', port: this.currentPort }; this.sendMessage(message); } sendReadData() { if (!this.isConnected) { this.log('WebSocket未连接', 'error'); return; } const data = this.readData.value.trim(); if (!data) { this.log('请输入读数据内容', 'warning'); return; } const message = { cmd: 'ReadData', port: this.currentPort, data: data }; this.sendMessage(message); // 清除数据表格 this.clearDataTable(); } sendWriteData() { if (!this.isConnected) { this.log('WebSocket未连接', 'error'); return; } const data = this.writeData.value.trim(); if (!data) { this.log('请输入写数据内容', 'warning'); return; } const message = { cmd: 'WriteData', port: this.currentPort, data: data }; this.sendMessage(message); } sendMessage(message) { try { this.ws.send(JSON.stringify(message)); } catch (error) { this.log('发送消息失败: ' + error.message, 'error'); } } updateConnectionStatus(connected) { if (connected) { this.connectionStatus.textContent = '已连接'; this.connectionStatus.className = 'status connected'; } else { this.connectionStatus.textContent = '未连接'; this.connectionStatus.className = 'status disconnected'; } } log(message, type = 'info') { const timestamp = new Date().toLocaleTimeString(); const logMessage = document.createElement('div'); logMessage.className = `log-message ${type}`; logMessage.textContent = `[${timestamp}] ${message}`; this.logOutput.appendChild(logMessage); this.logOutput.scrollTop = this.logOutput.scrollHeight; } clearLog() { this.logOutput.innerHTML = ''; this.log('日志已清空', 'info'); } validateHexData(data) { // 验证十六进制数据格式 const hexPattern = /^[0-9A-Fa-f\s]+$/; return hexPattern.test(data.trim()); } // 处理"写入数据→写发送"复选框变化 handleWriteToWriteSendChange() { // 功能已在handleWriteEvent中实现 this.log(`"写入数据→写发送"选项已${this.writeToWriteSendCheckbox.checked ? '启用' : '禁用'}`, 'info'); } // 处理"读取数据→读发送"复选框变化 handleReadToReadSendChange() { // 功能已在handleReadEvent中实现 this.log(`"读取数据→读发送"选项已${this.readToReadSendCheckbox.checked ? '启用' : '禁用'}`, 'info'); } // 处理"读取数据→修改数据"复选框变化 handleReadToModifyDataChange() { this.log(`"读取数据→修改数据"选项已${this.readToModifyDataCheckbox.checked ? '启用' : '禁用'}`, 'info'); } // 重写handleReadEvent方法以添加复选框功能 handleReadEvent(message) { // 调用原始的日志显示方法 this.handleReadEventOriginal(message); // 如果勾选了"读取数据→修改数据",则解析数据 let isParsed = false; if (this.readToModifyDataCheckbox.checked) { // 如果数据解析成功(满足特定条件),则不再执行"读取数据→读发送"的操作 const isParsed = this.parseAndDisplayData(message.data); if (isParsed) { // 数据满足解析条件,不再执行"读取数据→读发送"的操作 return; } } // 如果勾选了"读取数据→读发送",则将数据添加到读数据编辑框并执行读发送 if (this.readToReadSendCheckbox.checked) { this.readData.value = message.data; // 自动执行读发送 this.sendReadData(); } } // 重写handleWriteEvent方法以添加复选框功能 handleWriteEvent(message) { // 调用原始的日志显示方法 this.handleWriteEventOriginal(message); // 如果勾选了"写入数据→写发送",则将数据添加到写数据编辑框并执行写发送 if (this.writeToWriteSendCheckbox.checked) { this.writeData.value = message.data; // 自动执行写发送 this.sendWriteData(); } } // 原始的handleReadEvent方法 handleReadEventOriginal(message) { this.log(`${message.port} 读取数据: ${message.data}`, 'read'); } // 原始的handleWriteEvent方法 handleWriteEventOriginal(message) { this.log(`${message.port} 写入数据: ${message.data}`, 'write'); } // 解析数据并显示在表格中 parseAndDisplayData(hexData) { // 将十六进制字符串转换为字节数组 const bytes = this.hexStringToBytes(hexData); // 检查是否满足特定条件:ACK=06H, CMD=A3H, LB=17H if (bytes.length >= 3 && bytes[0] === 0x06 && bytes[1] === 0xA3 && bytes[2] === 0x17) { // 解析数据字段 const parsedData = this.parseDataFields(bytes); // 更新表格并重新计算校验和 this.updateDataTable(parsedData, bytes); // 解析成功 return true; } else { this.log('数据不满足解析条件(ACK=06H, CMD=A3H, LB=17H)', 'warning'); // 解析失败 return false; } } // 将十六进制字符串转换为字节数组 hexStringToBytes(hexString) { const bytes = []; const hexValues = hexString.trim().split(/\s+/); for (let i = 0; i < hexValues.length; i++) { const byte = parseInt(hexValues[i], 16); if (!isNaN(byte)) { bytes.push(byte); } } return bytes; } // 解析数据字段 parseDataFields(bytes) { // 根据规范解析数据: // 06H-A3H-17H-CO₂高位-C0,低位-C0 高位-CO 低位-HC 高位-HC低位-NO 高位-NO // 低位-0,高位-0,低位-油温高位-油温低位-转速高位-转速低位-气路压力高位-气路压力低位-过量空气系数高位-过量空气系数低位-PEF 值高位-PEF 值低位-校验码 const data = {}; // 确保数据长度足够 if (bytes.length < 27) { // 3(头部) + 2*11(参数) + 1(校验) = 26字节,索引从0开始所以需要27 this.log('数据长度不足,无法解析', 'error'); return data; } // 提取各参数(高位在前,低位在后) try { // CO₂ (索引3-4) data.CO2 = this.combineBytes(bytes[3], bytes[4]); // CO (索引5-6) data.CO = this.combineBytes(bytes[5], bytes[6]); // HC (索引7-8) data.HC = this.combineBytes(bytes[7], bytes[8]); // NO (索引9-10) data.NO = this.combineBytes(bytes[9], bytes[10]); // O2 (索引11-12) data.O2 = this.combineBytes(bytes[11], bytes[12]); // 油温 (索引13-14) data.油温 = this.combineBytes(bytes[13], bytes[14]); // 转速 (索引15-16) data.转速 = this.combineBytes(bytes[15], bytes[16]); // 气路压力 (索引17-18) data.气路压力 = this.combineBytes(bytes[17], bytes[18]); // 过量空气系数 (索引19-20) data.过量空气系数 = this.combineBytes(bytes[19], bytes[20]); // PEF值 (索引21-22) data.PEF值 = this.combineBytes(bytes[21], bytes[22]); return data; } catch (error) { this.log(`解析数据时出错: ${error.message}`, 'error'); return {}; } } // 合并高低字节为一个数值(高位在前) combineBytes(high, low) { return (high << 8) | low; } // 更新数据表格 updateDataTable(parsedData) { const tbody = this.dataTable.querySelector('tbody'); tbody.innerHTML = ''; // 清空现有数据 // Table3.5 数据转换关系表 const conversionTable = { 'HC': { range: [0, 9999], unit: '×10⁻⁶' }, 'CO': { range: [0, 1500], unit: '%' }, 'NO': { range: [0, 5000], unit: '×10⁻⁶' }, 'CO2': { range: [0, 1800], unit: '%' }, 'O2': { range: [0, 2500], unit: '%' }, '油温': { range: [0, 1000], unit: '℃' }, '转速': { range: [0, 9999], unit: 'r/min' }, '气路压力': { range: [0, 1100], unit: 'kPa' }, '过量空气系数': { range: [0, 2000], unit: '' }, 'PEF值': { range: [470, 540], unit: '' } }; // 参数映射(解析后的数据键名到显示名称) const parameterMapping = { 'HC': 'HC', 'CO': 'CO', 'NO': 'NO', 'CO2': 'CO₂', 'O2': 'O₂', '油温': '油温', '转速': '转速', '气路压力': '气路压力', '过量空气系数': '过量空气系数', 'PEF值': 'PEF 值' }; // 遍历解析后的数据并添加到表格 for (const [key, value] of Object.entries(parsedData)) { if (parameterMapping[key]) { const displayName = parameterMapping[key]; const config = conversionTable[displayName]; if (config) { // 原始值 let rawValue = value; // 根据转换规则转换为实际值 let convertedValue = this.convertValue(rawValue, config); // 最大值 const maxValue = config.range[1]; // 创建表格行 const row = document.createElement('tr'); row.dataset.parameter = key; // 保存参数名用于识别 // 添加单元格 row.innerHTML = `