script.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866
  1. class CommDebugger {
  2. constructor() {
  3. this.ws = null;
  4. this.isConnected = false;
  5. this.currentPort = 1;
  6. // 为每个端口维护监听状态
  7. this.portMonitoringStatus = {};
  8. this.initializeElements();
  9. this.bindEvents();
  10. // 设置默认端口显示
  11. this.portSelect.value = this.currentPort;
  12. this.connect();
  13. }
  14. initializeElements() {
  15. this.portSelect = document.getElementById('portSelect');
  16. this.portOptions = document.getElementById('portOptions');
  17. this.startBtn = document.getElementById('startBtn');
  18. this.stopBtn = document.getElementById('stopBtn');
  19. this.clearBtn = document.getElementById('clearBtn');
  20. this.readData = document.getElementById('readData');
  21. this.readSendBtn = document.getElementById('readSendBtn');
  22. this.writeData = document.getElementById('writeData');
  23. this.writeSendBtn = document.getElementById('writeSendBtn');
  24. this.logOutput = document.getElementById('logOutput');
  25. this.connectionStatus = document.getElementById('connectionStatus');
  26. // 新增的复选框元素
  27. this.writeToWriteSendCheckbox = document.getElementById('writeToWriteSend');
  28. this.readToReadSendCheckbox = document.getElementById('readToReadSend');
  29. this.readToModifyDataCheckbox = document.getElementById('readToModifyData');
  30. // 数据表格元素
  31. this.dataTable = document.getElementById('dataTable');
  32. }
  33. bindEvents() {
  34. // 端口选择框事件处理
  35. this.portSelect.addEventListener('focus', () => {
  36. this.portOptions.classList.add('show');
  37. });
  38. this.portSelect.addEventListener('click', () => {
  39. this.portOptions.classList.toggle('show');
  40. });
  41. this.portSelect.addEventListener('blur', () => {
  42. // 添加延迟以确保点击选项时不会立即隐藏
  43. setTimeout(() => {
  44. this.portOptions.classList.remove('show');
  45. }, 200);
  46. });
  47. this.portSelect.addEventListener('input', (e) => {
  48. this.currentPort = this.getPortNumber(e.target.value);
  49. });
  50. // 端口选项点击事件
  51. const portOptions = this.portOptions.querySelectorAll('.port-option');
  52. portOptions.forEach(option => {
  53. option.addEventListener('mousedown', (e) => {
  54. e.preventDefault(); // 防止blur事件触发
  55. this.portSelect.value = e.target.getAttribute('data-value');
  56. this.currentPort = this.getPortNumber(this.portSelect.value);
  57. this.portOptions.classList.remove('show');
  58. this.portSelect.focus();
  59. });
  60. });
  61. this.startBtn.addEventListener('click', () => {
  62. this.startMonitoring();
  63. });
  64. this.stopBtn.addEventListener('click', () => {
  65. this.stopMonitoring();
  66. });
  67. this.clearBtn.addEventListener('click', () => {
  68. this.clearLog();
  69. });
  70. this.readSendBtn.addEventListener('click', () => {
  71. this.sendReadData();
  72. });
  73. this.writeSendBtn.addEventListener('click', () => {
  74. this.sendWriteData();
  75. });
  76. // 复选框事件处理
  77. this.writeToWriteSendCheckbox.addEventListener('change', () => {
  78. this.handleWriteToWriteSendChange();
  79. });
  80. this.readToReadSendCheckbox.addEventListener('change', () => {
  81. this.handleReadToReadSendChange();
  82. });
  83. this.readToModifyDataCheckbox.addEventListener('change', () => {
  84. this.handleReadToModifyDataChange();
  85. });
  86. // 初始化WebSocket连接
  87. // this.connect(); // 移除重复调用,只在构造函数中调用一次
  88. }
  89. // 获取端口号(支持COMx格式或纯数字)
  90. getPortNumber(portStr) {
  91. if (!portStr) return 1;
  92. // 如果是COMx格式,提取数字部分
  93. const comMatch = portStr.match(/^COM(\d+)$/i);
  94. if (comMatch) {
  95. return parseInt(comMatch[1]);
  96. }
  97. // 如果是纯数字格式
  98. const num = parseInt(portStr);
  99. if (!isNaN(num) && num > 0) {
  100. return num;
  101. }
  102. // 默认返回1
  103. return 1;
  104. }
  105. connect() {
  106. try {
  107. const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
  108. const wsUrl = `${protocol}//${window.location.host}/ws`;
  109. this.ws = new WebSocket(wsUrl);
  110. this.ws.onopen = () => {
  111. this.isConnected = true;
  112. this.updateConnectionStatus(true);
  113. this.log('WebSocket连接已建立', 'success');
  114. };
  115. this.ws.onmessage = (event) => {
  116. this.handleMessage(event.data);
  117. };
  118. this.ws.onclose = () => {
  119. this.isConnected = false;
  120. this.updateConnectionStatus(false);
  121. this.log('WebSocket连接已断开', 'error');
  122. // 自动重连
  123. setTimeout(() => {
  124. if (!this.isConnected) {
  125. this.log('尝试重新连接...', 'info');
  126. this.connect();
  127. }
  128. }, 3000);
  129. };
  130. this.ws.onerror = (error) => {
  131. this.log('WebSocket连接错误: ' + error, 'error');
  132. };
  133. } catch (error) {
  134. this.log('连接失败: ' + error.message, 'error');
  135. }
  136. }
  137. handleMessage(data) {
  138. try {
  139. const message = JSON.parse(data);
  140. switch (message.cmd) {
  141. case 'InitMonitor':
  142. this.handleInitMonitorResponse(message);
  143. break;
  144. case 'StopPort':
  145. this.handleStopPortResponse(message);
  146. break;
  147. case 'ReadData':
  148. this.handleReadDataResponse(message);
  149. break;
  150. case 'WriteData':
  151. this.handleWriteDataResponse(message);
  152. break;
  153. case 'OP_READ':
  154. this.handleReadEvent(message);
  155. break;
  156. case 'OP_WRITE':
  157. this.handleWriteEvent(message);
  158. break;
  159. case 'OP_OPEN':
  160. this.handleOpenEvent(message);
  161. break;
  162. case 'OP_CLOSE':
  163. this.handleCloseEvent(message);
  164. break;
  165. default:
  166. this.log('未知消息类型: ' + message.cmd, 'warning');
  167. }
  168. } catch (error) {
  169. this.log('解析消息失败: ' + error.message, 'error');
  170. }
  171. }
  172. handleInitMonitorResponse(message) {
  173. if (message.result) {
  174. // 更新特定端口的监听状态
  175. this.portMonitoringStatus[message.port] = true;
  176. this.log(`${message.port} 开始监听成功`, 'success');
  177. } else {
  178. this.log(`${message.port} 开始监听失败`, 'error');
  179. }
  180. }
  181. handleStopPortResponse(message) {
  182. if (message.result) {
  183. // 更新特定端口的监听状态
  184. this.portMonitoringStatus[message.port] = false;
  185. this.log(`${message.port} 停止监听成功`, 'success');
  186. } else {
  187. this.log(`${message.port} 停止监听失败`, 'error');
  188. }
  189. }
  190. handleReadDataResponse(message) {
  191. if (message.result) {
  192. this.log(`${message.port} 读数据发送成功`, 'info');
  193. } else {
  194. this.log(`${message.port} 读数据发送失败`, 'error');
  195. }
  196. }
  197. handleWriteDataResponse(message) {
  198. if (message.result) {
  199. this.log(`${message.port} 写数据发送成功`, 'info');
  200. } else {
  201. this.log(`${message.port} 写数据发送失败`, 'error');
  202. }
  203. }
  204. handleReadEvent(message) {
  205. this.log(`${message.port} 读取数据: ${message.data}`, 'read');
  206. }
  207. handleWriteEvent(message) {
  208. this.log(`${message.port} 写入数据: ${message.data}`, 'write');
  209. }
  210. handleOpenEvent(message) {
  211. this.log(`${message.port} 端口已打开`, 'open');
  212. }
  213. handleCloseEvent(message) {
  214. this.log(`${message.port} 端口已关闭`, 'close');
  215. }
  216. startMonitoring() {
  217. if (!this.isConnected) {
  218. this.log('WebSocket未连接', 'error');
  219. return;
  220. }
  221. const message = {
  222. cmd: 'InitMonitor',
  223. port: this.currentPort
  224. };
  225. this.sendMessage(message);
  226. }
  227. stopMonitoring() {
  228. if (!this.isConnected) {
  229. this.log('WebSocket未连接', 'error');
  230. return;
  231. }
  232. const message = {
  233. cmd: 'StopPort',
  234. port: this.currentPort
  235. };
  236. this.sendMessage(message);
  237. }
  238. sendReadData() {
  239. if (!this.isConnected) {
  240. this.log('WebSocket未连接', 'error');
  241. return;
  242. }
  243. const data = this.readData.value.trim();
  244. if (!data) {
  245. this.log('请输入读数据内容', 'warning');
  246. return;
  247. }
  248. const message = {
  249. cmd: 'ReadData',
  250. port: this.currentPort,
  251. data: data
  252. };
  253. this.sendMessage(message);
  254. // 清除数据表格
  255. this.clearDataTable();
  256. }
  257. sendWriteData() {
  258. if (!this.isConnected) {
  259. this.log('WebSocket未连接', 'error');
  260. return;
  261. }
  262. const data = this.writeData.value.trim();
  263. if (!data) {
  264. this.log('请输入写数据内容', 'warning');
  265. return;
  266. }
  267. const message = {
  268. cmd: 'WriteData',
  269. port: this.currentPort,
  270. data: data
  271. };
  272. this.sendMessage(message);
  273. }
  274. sendMessage(message) {
  275. try {
  276. this.ws.send(JSON.stringify(message));
  277. } catch (error) {
  278. this.log('发送消息失败: ' + error.message, 'error');
  279. }
  280. }
  281. updateConnectionStatus(connected) {
  282. if (connected) {
  283. this.connectionStatus.textContent = '已连接';
  284. this.connectionStatus.className = 'status connected';
  285. } else {
  286. this.connectionStatus.textContent = '未连接';
  287. this.connectionStatus.className = 'status disconnected';
  288. }
  289. }
  290. log(message, type = 'info') {
  291. const timestamp = new Date().toLocaleTimeString();
  292. const logMessage = document.createElement('div');
  293. logMessage.className = `log-message ${type}`;
  294. logMessage.textContent = `[${timestamp}] ${message}`;
  295. this.logOutput.appendChild(logMessage);
  296. this.logOutput.scrollTop = this.logOutput.scrollHeight;
  297. }
  298. clearLog() {
  299. this.logOutput.innerHTML = '';
  300. this.log('日志已清空', 'info');
  301. }
  302. validateHexData(data) {
  303. // 验证十六进制数据格式
  304. const hexPattern = /^[0-9A-Fa-f\s]+$/;
  305. return hexPattern.test(data.trim());
  306. }
  307. // 处理"写入数据→写发送"复选框变化
  308. handleWriteToWriteSendChange() {
  309. // 功能已在handleWriteEvent中实现
  310. this.log(`"写入数据→写发送"选项已${this.writeToWriteSendCheckbox.checked ? '启用' : '禁用'}`, 'info');
  311. }
  312. // 处理"读取数据→读发送"复选框变化
  313. handleReadToReadSendChange() {
  314. // 功能已在handleReadEvent中实现
  315. this.log(`"读取数据→读发送"选项已${this.readToReadSendCheckbox.checked ? '启用' : '禁用'}`, 'info');
  316. }
  317. // 处理"读取数据→修改数据"复选框变化
  318. handleReadToModifyDataChange() {
  319. this.log(`"读取数据→修改数据"选项已${this.readToModifyDataCheckbox.checked ? '启用' : '禁用'}`, 'info');
  320. }
  321. // 重写handleReadEvent方法以添加复选框功能
  322. handleReadEvent(message) {
  323. // 调用原始的日志显示方法
  324. this.handleReadEventOriginal(message);
  325. // 如果勾选了"读取数据→修改数据",则解析数据
  326. let isParsed = false;
  327. if (this.readToModifyDataCheckbox.checked) {
  328. // 如果数据解析成功(满足特定条件),则不再执行"读取数据→读发送"的操作
  329. const isParsed = this.parseAndDisplayData(message.data);
  330. if (isParsed) {
  331. // 数据满足解析条件,不再执行"读取数据→读发送"的操作
  332. return;
  333. }
  334. }
  335. // 如果勾选了"读取数据→读发送",则将数据添加到读数据编辑框并执行读发送
  336. if (this.readToReadSendCheckbox.checked) {
  337. this.readData.value = message.data;
  338. // 自动执行读发送
  339. this.sendReadData();
  340. }
  341. }
  342. // 重写handleWriteEvent方法以添加复选框功能
  343. handleWriteEvent(message) {
  344. // 调用原始的日志显示方法
  345. this.handleWriteEventOriginal(message);
  346. // 如果勾选了"写入数据→写发送",则将数据添加到写数据编辑框并执行写发送
  347. if (this.writeToWriteSendCheckbox.checked) {
  348. this.writeData.value = message.data;
  349. // 自动执行写发送
  350. this.sendWriteData();
  351. }
  352. }
  353. // 原始的handleReadEvent方法
  354. handleReadEventOriginal(message) {
  355. this.log(`${message.port} 读取数据: ${message.data}`, 'read');
  356. }
  357. // 原始的handleWriteEvent方法
  358. handleWriteEventOriginal(message) {
  359. this.log(`${message.port} 写入数据: ${message.data}`, 'write');
  360. }
  361. // 解析数据并显示在表格中
  362. parseAndDisplayData(hexData) {
  363. // 将十六进制字符串转换为字节数组
  364. const bytes = this.hexStringToBytes(hexData);
  365. // 检查是否满足特定条件:ACK=06H, CMD=A3H, LB=17H
  366. if (bytes.length >= 3 && bytes[0] === 0x06 && bytes[1] === 0xA3 && bytes[2] === 0x17) {
  367. // 解析数据字段
  368. const parsedData = this.parseDataFields(bytes);
  369. // 更新表格并重新计算校验和
  370. this.updateDataTable(parsedData, bytes);
  371. // 解析成功
  372. return true;
  373. } else {
  374. this.log('数据不满足解析条件(ACK=06H, CMD=A3H, LB=17H)', 'warning');
  375. // 解析失败
  376. return false;
  377. }
  378. }
  379. // 将十六进制字符串转换为字节数组
  380. hexStringToBytes(hexString) {
  381. const bytes = [];
  382. const hexValues = hexString.trim().split(/\s+/);
  383. for (let i = 0; i < hexValues.length; i++) {
  384. const byte = parseInt(hexValues[i], 16);
  385. if (!isNaN(byte)) {
  386. bytes.push(byte);
  387. }
  388. }
  389. return bytes;
  390. }
  391. // 解析数据字段
  392. parseDataFields(bytes) {
  393. // 根据规范解析数据:
  394. // 06H-A3H-17H-CO₂高位-C0,低位-C0 高位-CO 低位-HC 高位-HC低位-NO 高位-NO
  395. // 低位-0,高位-0,低位-油温高位-油温低位-转速高位-转速低位-气路压力高位-气路压力低位-过量空气系数高位-过量空气系数低位-PEF 值高位-PEF 值低位-校验码
  396. const data = {};
  397. // 确保数据长度足够
  398. if (bytes.length < 27) { // 3(头部) + 2*11(参数) + 1(校验) = 26字节,索引从0开始所以需要27
  399. this.log('数据长度不足,无法解析', 'error');
  400. return data;
  401. }
  402. // 提取各参数(高位在前,低位在后)
  403. try {
  404. // CO₂ (索引3-4)
  405. data.CO2 = this.combineBytes(bytes[3], bytes[4]);
  406. // CO (索引5-6)
  407. data.CO = this.combineBytes(bytes[5], bytes[6]);
  408. // HC (索引7-8)
  409. data.HC = this.combineBytes(bytes[7], bytes[8]);
  410. // NO (索引9-10)
  411. data.NO = this.combineBytes(bytes[9], bytes[10]);
  412. // O2 (索引11-12)
  413. data.O2 = this.combineBytes(bytes[11], bytes[12]);
  414. // 油温 (索引13-14)
  415. data.油温 = this.combineBytes(bytes[13], bytes[14]);
  416. // 转速 (索引15-16)
  417. data.转速 = this.combineBytes(bytes[15], bytes[16]);
  418. // 气路压力 (索引17-18)
  419. data.气路压力 = this.combineBytes(bytes[17], bytes[18]);
  420. // 过量空气系数 (索引19-20)
  421. data.过量空气系数 = this.combineBytes(bytes[19], bytes[20]);
  422. // PEF值 (索引21-22)
  423. data.PEF值 = this.combineBytes(bytes[21], bytes[22]);
  424. return data;
  425. } catch (error) {
  426. this.log(`解析数据时出错: ${error.message}`, 'error');
  427. return {};
  428. }
  429. }
  430. // 合并高低字节为一个数值(高位在前)
  431. combineBytes(high, low) {
  432. return (high << 8) | low;
  433. }
  434. // 更新数据表格
  435. updateDataTable(parsedData) {
  436. const tbody = this.dataTable.querySelector('tbody');
  437. tbody.innerHTML = ''; // 清空现有数据
  438. // Table3.5 数据转换关系表
  439. const conversionTable = {
  440. 'HC': { range: [0, 9999], unit: '×10⁻⁶' },
  441. 'CO': { range: [0, 1500], unit: '%' },
  442. 'NO': { range: [0, 5000], unit: '×10⁻⁶' },
  443. 'CO2': { range: [0, 1800], unit: '%' },
  444. 'O2': { range: [0, 2500], unit: '%' },
  445. '油温': { range: [0, 1000], unit: '℃' },
  446. '转速': { range: [0, 9999], unit: 'r/min' },
  447. '气路压力': { range: [0, 1100], unit: 'kPa' },
  448. '过量空气系数': { range: [0, 2000], unit: '' },
  449. 'PEF值': { range: [470, 540], unit: '' }
  450. };
  451. // 参数映射(解析后的数据键名到显示名称)
  452. const parameterMapping = {
  453. 'HC': 'HC',
  454. 'CO': 'CO',
  455. 'NO': 'NO',
  456. 'CO2': 'CO₂',
  457. 'O2': 'O₂',
  458. '油温': '油温',
  459. '转速': '转速',
  460. '气路压力': '气路压力',
  461. '过量空气系数': '过量空气系数',
  462. 'PEF值': 'PEF 值'
  463. };
  464. // 遍历解析后的数据并添加到表格
  465. for (const [key, value] of Object.entries(parsedData)) {
  466. if (parameterMapping[key]) {
  467. const displayName = parameterMapping[key];
  468. const config = conversionTable[displayName];
  469. if (config) {
  470. // 原始值
  471. let rawValue = value;
  472. // 根据转换规则转换为实际值
  473. let convertedValue = this.convertValue(rawValue, config);
  474. // 最大值
  475. const maxValue = config.range[1];
  476. // 创建表格行
  477. const row = document.createElement('tr');
  478. row.dataset.parameter = key; // 保存参数名用于识别
  479. // 添加单元格
  480. row.innerHTML = `
  481. <td>${displayName}</td>
  482. <td contenteditable="true" data-raw-value="${rawValue}" data-parameter="${key}">${rawValue}</td>
  483. <td>${maxValue}${config.unit}</td>
  484. `;
  485. tbody.appendChild(row);
  486. // 为可编辑单元格添加事件监听器
  487. const editableCell = row.querySelector('[contenteditable]');
  488. editableCell.addEventListener('blur', (e) => {
  489. this.handleParameterEdit(e, key, config, bytes, validatedData);
  490. });
  491. editableCell.addEventListener('keydown', (e) => {
  492. if (e.key === 'Enter') {
  493. e.preventDefault();
  494. editableCell.blur();
  495. }
  496. });
  497. }
  498. }
  499. }
  500. }
  501. // 根据转换规则将原始值转换为实际值
  502. convertValue(rawValue, config) {
  503. const [min, max] = config.range;
  504. // 检查是否在有效范围内
  505. if (rawValue < min) {
  506. rawValue = min;
  507. } else if (rawValue > max) {
  508. rawValue = max;
  509. }
  510. // 根据参数类型进行转换
  511. switch (config.unit) {
  512. case '%':
  513. // 转换为百分比格式(保留两位小数)
  514. return (rawValue / 100).toFixed(2);
  515. case '℃':
  516. // 转换为温度格式(保留一位小数)
  517. return (rawValue / 10).toFixed(1);
  518. case 'kPa':
  519. // 转换为压力格式(保留一位小数)
  520. return (rawValue / 10).toFixed(1);
  521. case '':
  522. // 无单位参数的处理
  523. if (config.range[0] === 0 && config.range[1] === 2000) {
  524. // 过量空气系数,转换为三位小数
  525. const value = (rawValue / 1000).toFixed(3);
  526. // 超过2.000显示2.000
  527. return parseFloat(value) > 2.000 ? '2.000' : value;
  528. } else if (config.range[0] === 470 && config.range[1] === 540) {
  529. // PEF值,转换为三位小数
  530. return (rawValue / 1000).toFixed(3);
  531. }
  532. return rawValue;
  533. default:
  534. // 其他情况直接返回原始值
  535. return rawValue;
  536. }
  537. }
  538. // 更新数据表格并重新计算校验和
  539. updateDataTable(parsedData, bytes) {
  540. const tbody = this.dataTable.querySelector('tbody');
  541. tbody.innerHTML = ''; // 清空现有数据
  542. // Table3.5 数据转换关系表
  543. const conversionTable = {
  544. 'HC': { range: [0, 9999], unit: '×10⁻⁶' },
  545. 'CO': { range: [0, 1500], unit: '%' },
  546. 'NO': { range: [0, 5000], unit: '×10⁻⁶' },
  547. 'CO2': { range: [0, 1800], unit: '%' },
  548. 'O2': { range: [0, 2500], unit: '%' },
  549. '油温': { range: [0, 1000], unit: '℃' },
  550. '转速': { range: [0, 9999], unit: 'r/min' },
  551. '气路压力': { range: [0, 1100], unit: 'kPa' },
  552. '过量空气系数': { range: [0, 2000], unit: '' },
  553. 'PEF值': { range: [470, 540], unit: '' }
  554. };
  555. // 参数映射(解析后的数据键名到显示名称)
  556. const parameterMapping = {
  557. 'HC': 'HC',
  558. 'CO': 'CO',
  559. 'NO': 'NO',
  560. 'CO2': 'CO₂',
  561. 'O2': 'O₂',
  562. '油温': '油温',
  563. '转速': '转速',
  564. '气路压力': '气路压力',
  565. '过量空气系数': '过量空气系数',
  566. 'PEF值': 'PEF 值'
  567. };
  568. // 创建校验后的数据副本
  569. const validatedData = { ...parsedData };
  570. // 遍历解析后的数据并添加到表格
  571. for (const [key, value] of Object.entries(parsedData)) {
  572. if (parameterMapping[key]) {
  573. const displayName = parameterMapping[key];
  574. const config = conversionTable[displayName];
  575. if (config) {
  576. // 原始值
  577. let rawValue = value;
  578. // 根据转换规则转换为实际值
  579. let convertedValue = this.convertValue(rawValue, config);
  580. // 更新校验后的数据
  581. validatedData[key] = rawValue;
  582. // 最大值
  583. const maxValue = config.range[1];
  584. // 创建表格行
  585. const row = document.createElement('tr');
  586. row.dataset.parameter = key; // 保存参数名用于识别
  587. // 添加单元格
  588. row.innerHTML = `
  589. <td>${displayName}</td>
  590. <td contenteditable="true" data-raw-value="${rawValue}" data-parameter="${key}">${rawValue}</td>
  591. <td>${maxValue}${config.unit}</td>
  592. `;
  593. tbody.appendChild(row);
  594. // 为可编辑单元格添加事件监听器
  595. const editableCell = row.querySelector('[contenteditable]');
  596. editableCell.addEventListener('blur', (e) => {
  597. this.handleParameterEdit(e, key, config, bytes, validatedData);
  598. });
  599. editableCell.addEventListener('keydown', (e) => {
  600. if (e.key === 'Enter') {
  601. e.preventDefault();
  602. editableCell.blur();
  603. }
  604. });
  605. }
  606. }
  607. }
  608. // 重新计算校验和并将校验过的读数据添加至读数据编辑框
  609. this.recalculateChecksumAndApply(bytes, validatedData);
  610. }
  611. // 处理参数编辑事件
  612. handleParameterEdit(event, parameterKey, config, originalBytes, validatedData) {
  613. const cell = event.target;
  614. const newValue = parseInt(cell.textContent.trim());
  615. // 验证输入值
  616. if (isNaN(newValue)) {
  617. this.log('请输入有效的数字', 'error');
  618. // 恢复原始值
  619. cell.textContent = cell.dataset.rawValue;
  620. return;
  621. }
  622. // 检查值是否在有效范围内
  623. const [min, max] = config.range;
  624. let validatedValue = newValue;
  625. if (validatedValue < min) {
  626. validatedValue = min;
  627. this.log(`${parameterKey}值低于最小值,已自动调整为${min}`, 'warning');
  628. } else if (validatedValue > max) {
  629. validatedValue = max;
  630. this.log(`${parameterKey}值超过最大值,已自动调整为${max}`, 'warning');
  631. }
  632. // 更新显示值
  633. cell.textContent = validatedValue;
  634. cell.dataset.rawValue = validatedValue;
  635. // 更新校验后的数据
  636. validatedData[parameterKey] = validatedValue;
  637. // 重新计算校验和并更新到读数据编辑框
  638. this.recalculateChecksumAndApply(originalBytes, validatedData);
  639. }
  640. // 重新计算校验和并将校验过的数据更新到读数据编辑框
  641. recalculateChecksumAndApply(originalBytes, validatedData) {
  642. try {
  643. // 创建新的字节数组副本
  644. const newBytes = [...originalBytes];
  645. // 更新数据字段(根据validatedData)
  646. // CO₂ (索引3-4)
  647. if (validatedData.CO2 !== undefined) {
  648. newBytes[3] = (validatedData.CO2 >> 8) & 0xFF;
  649. newBytes[4] = validatedData.CO2 & 0xFF;
  650. }
  651. // CO (索引5-6)
  652. if (validatedData.CO !== undefined) {
  653. newBytes[5] = (validatedData.CO >> 8) & 0xFF;
  654. newBytes[6] = validatedData.CO & 0xFF;
  655. }
  656. // HC (索引7-8)
  657. if (validatedData.HC !== undefined) {
  658. newBytes[7] = (validatedData.HC >> 8) & 0xFF;
  659. newBytes[8] = validatedData.HC & 0xFF;
  660. }
  661. // NO (索引9-10)
  662. if (validatedData.NO !== undefined) {
  663. newBytes[9] = (validatedData.NO >> 8) & 0xFF;
  664. newBytes[10] = validatedData.NO & 0xFF;
  665. }
  666. // O₂ (索引11-12)
  667. if (validatedData.O2 !== undefined) {
  668. newBytes[11] = (validatedData.O2 >> 8) & 0xFF;
  669. newBytes[12] = validatedData.O2 & 0xFF;
  670. }
  671. // 油温 (索引13-14)
  672. if (validatedData.油温 !== undefined) {
  673. newBytes[13] = (validatedData.油温 >> 8) & 0xFF;
  674. newBytes[14] = validatedData.油温 & 0xFF;
  675. }
  676. // 转速 (索引15-16)
  677. if (validatedData.转速 !== undefined) {
  678. newBytes[15] = (validatedData.转速 >> 8) & 0xFF;
  679. newBytes[16] = validatedData.转速 & 0xFF;
  680. }
  681. // 气路压力 (索引17-18)
  682. if (validatedData.气路压力 !== undefined) {
  683. newBytes[17] = (validatedData.气路压力 >> 8) & 0xFF;
  684. newBytes[18] = validatedData.气路压力 & 0xFF;
  685. }
  686. // 过量空气系数 (索引19-20)
  687. if (validatedData.过量空气系数 !== undefined) {
  688. newBytes[19] = (validatedData.过量空气系数 >> 8) & 0xFF;
  689. newBytes[20] = validatedData.过量空气系数 & 0xFF;
  690. }
  691. // PEF值 (索引21-22)
  692. if (validatedData.PEF值 !== undefined) {
  693. newBytes[21] = (validatedData.PEF值 >> 8) & 0xFF;
  694. newBytes[22] = validatedData.PEF值 & 0xFF;
  695. }
  696. // 重新计算校验和 (CS=NOT(ACK+CMD+LB+[DF])+1)
  697. let checksum = 0x06 + 0xA3 + 0x17; // ACK + CMD + LB
  698. // 计算数据字段的和
  699. for (let i = 3; i < 23; i++) { // 从索引3到22的数据字段
  700. checksum += newBytes[i];
  701. }
  702. // 计算校验和: CS=NOT(ACK+CMD+LB+[DF])+1
  703. const newChecksum = (~checksum & 0xFF) + 1;
  704. newBytes[23] = newChecksum; // 更新校验和字段
  705. // 将校验过的数据转换为十六进制字符串并更新到读数据编辑框
  706. const validatedHexData = newBytes.map(byte => byte.toString(16).padStart(2, '0').toUpperCase()).join(' ');
  707. this.readData.value = validatedHexData;
  708. this.log('数据校验完成,校验和已重新计算并更新到读数据编辑框', 'success');
  709. } catch (error) {
  710. this.log(`重新计算校验和时出错: ${error.message}`, 'error');
  711. }
  712. }
  713. // 清除数据表格
  714. clearDataTable() {
  715. const tbody = this.dataTable.querySelector('tbody');
  716. tbody.innerHTML = ''; // 清空现有数据
  717. }
  718. }
  719. // 页面加载完成后初始化
  720. document.addEventListener('DOMContentLoaded', () => {
  721. new CommDebugger();
  722. });