python绘制lost(损失)曲线 加 方差范围

1. 导入必要的包

我使用了seaborn,通过sns.set_style可以让绘制出来的图更漂亮,而且可以切换不同的类型

import re
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import shutil
import os
sns.set_style('whitegrid')

2. 数据的获取(可跳过此步)

       我用的数据是通过深度强化得到的回报曲线。数据结构如下所示,我所需要的是从train开始的部分,分别对应总的回报,平均回报和回报的方差。我采用了re.findall的正则表达式去提取我所需要的数据,具体的操作方式可以查看源码。

10-15 22:23:15 DATA/traffic DEBUG     train 0 totalreward : -99477.0 ReturnAvg : -102.55360824742269 ReturnStd : 34.34301970480272
10-15 22:23:29 DATA/traffic DEBUG     train 1 totalreward : -83131.0 ReturnAvg : -85.70206185567011 ReturnStd : 53.442993000985545

file_path = 'log.txt'
content = []
with open(file_path, 'r') as f:
    for line in f.readlines():
        line = line.strip('\n')
        content.append(l
import binascii import serial import time import struct import queue import threading from datetime import datetime CanOBDItemList = [] CanPGNItemList = [[0,0,0,0]] filteredCanOBDItemList = [] Frame_start = b'\xFF' Frame_end = b'\x55' Frame_data_style_len = 6 Frame_Data_Len = 0 frame_buffer = bytearray() class CanInfShow_Item: def __int__(self,CanID,CanFramType,Len,CanDataInf): self.SystemCycle = datetime.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3], self.CanID = CanID, self.CanFrame = CanFramType, self.CanDataLen = Len, self.CanData = CanDataInf class CanPGNShow_Item: def __int__(self, PGNID, CanID, CanData, Signal): self.PGNID = PGNID, self.CanID = CanID, self.CanData = CanData, self.Signal = Signal class SerialPort: def __init__(self, port, baudrate): # 初始化串口参数 self.port = port self.baudrate = baudrate self.ser = serial.Serial( port=self.port, baudrate=self.baudrate, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE ) # 等待串口连接稳定 self.last_data_time = time.time() # 新增:最后接收数据的时间戳 self.cycle_dict = {} # 存储{帧ID: [上次时间戳, 当前周期]} self.last_frame_time = {} # 存储每个ID的最后出现时间 self.data_updated_ids = set() # 存储数据变化的CAN ID self.new_added_ids = set() # 存储新增的CAN ID self.last_data_dict = {} # 存储每个CAN ID的上一次数据 self.changed_bytes_dict = {} # 存储每个CAN ID的变化字节索引 self.last_frame_num = {} self.last_pgn_data_dict = {} # 存储每个PGN ID的上一次数据 self.changed_pgn_bytes_dict = {} # 存储每个PGN ID的变化字节索引 self.state = 0 self.current_frame = bytearray() self.expected_length = 0 self.raw_data_queue = queue.Queue(maxsize=10000) self.data_lock = threading.Lock() self.worker_threads = [] self.allowed_pgn_ids = {0xFEF1, 0xF004, 0xFEC1, 0xFEE5, 0xFEEE, 0xFE56, 0xFEF2, 0xF005} self.filter_cycles = [] # 添这一行用于存储过滤周期 #优化二维查找方式的映射字典 self.can_obd_index_map = {} self.filtered_index_map = {} self.can_pgn_index_map = {} time.sleep(0.2) if not self.ser.isOpen(): print("串口打开失败!") def close(self): # 关闭串口连接 if self.ser.isOpen(): self.ser.close() def send(self, data): # 发送数据 if self.ser.isOpen(): try: self.ser.write(data.encode('utf-8')) except serial.SerialException as e: print(f"发送数据失败: {e}") def recv(self, chunk_size=1024): if self.ser.isOpen(): # 每次最多读取1024字节 data = self.ser.read(min(self.ser.inWaiting(), chunk_size)) if data: return data return None def __del__(self): self.close() def SerialIsOpen(self): if self.ser.isOpen(): return 1 else: return 0 def start_reading(self): self.recv_thread = threading.Thread(target=self._recv_worker, daemon=True) self.parse_thread = threading.Thread(target=self._parse_worker, daemon=True) self.recv_thread.start() self.parse_thread.start() self.worker_threads = [self.recv_thread, self.parse_thread] def _recv_worker(self): while self.ser.isOpen(): data = self.recv(chunk_size=4096) # 每次最多读4KB if data: self.raw_data_queue.put(data) # else: # time.sleep(0.001) def _parse_worker(self): while True: try: data = self.raw_data_queue.get(timeout=0.1) for byte in data: self.process_byte(byte) except queue.Empty: continue def process_byte(self, byte): """ 使用状态机逐字节解析帧结构。 """ if self.state == 0: # 等待帧头 FF if byte == 0xFF: self.current_frame.append(byte) self.state = 1 else: # 如果不是帧头,忽略该字节 pass elif self.state == 1: # 等待帧头 55 if byte == 0x55: self.current_frame.append(byte) self.state = 2 else: # 如果第二字节不是55,重置 self.reset_state() elif self.state == 2: # 接收总长度低位 (第2字节) self.current_frame.append(byte) self.state = 3 elif self.state == 3: # 接收总长度高位 (第3字节) self.current_frame.append(byte) self.state = 4 elif self.state == 4: # 接收类型字段 (第4字节) self.current_frame.append(byte) self.state = 5 elif self.state == 5: # 接收保留字段 (第5字节) self.current_frame.append(byte) self.state = 6 elif self.state == 6: # 接收 CAN 通道类型 (第6字节) self.current_frame.append(byte) self.state = 7 elif self.state == 7: # 接收 CAN 报文个数 N (第7字节) self.current_frame.append(byte) self.num_messages = byte self.state = 8 self.messages_received = 0 elif self.state == 8: #接收can报文 self.current_frame.append(byte) self.messages_received += 1 if self.messages_received == self.num_messages * 12: self.state = 9 elif self.state == 9: # 接收校验位 self.current_frame.append(byte) if self.verify_checksum(): self.Frame_analoy_process(bytes(self.current_frame)) else: print("校验失败,丢弃当前帧") self.reset_state() def verify_checksum(self): """ 验证校验和:从第2字节到倒数第二个字节之和 & 0xFF """ data_to_check = self.current_frame[2:-1] # 从第2字节到最后一个校验位之前 checksum = sum(data_to_check) & 0xFF return checksum == self.current_frame[-1] def reset_state(self): """ 重置状态机 """ self.state = 0 self.current_frame = bytearray() self.expected_length = 0 self.messages_received = 0 def set_filter_cycles(self, cycles): self.filter_cycles = cycles.copy() if cycles else [] #报文解析 def Frame_analoy_process(self, Framedata): # 检查帧类型 (0x0C 0x98) if len(Framedata) < 8 or Framedata[4] != 0x0C or Framedata[5] != 0x98: return try: FrameNum = int(Framedata[7]) except IndexError: return # 检查是否有足够数据 if len(Framedata) < 12 * FrameNum + 8: return current_time = time.time() current_frame_ids = set() # 记录当前帧中已处理的ID for index in range(0,FrameNum): # 时间戳 Cantime = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] try: id_bytes = [ Framedata[12 * index + 11], # LSB Framedata[12 * index + 10], Framedata[12 * index + 9], Framedata[12 * index + 8] # MSB ] except IndexError: continue # 格式化为8位大写十六进制 CanID = ''.join(format(b, '02X') for b in id_bytes) # 提取ID字节 CanFramType = "Cycle" Len = 8 CanDataSpace = '' PGNdata = '' PGNID = int(Framedata[12 * index + 9] ) + int(Framedata[12 * index + 10]* 0x100) PGNSignl = self.Frame_analoy_PGN_Signal(PGNID,Framedata[12 * index + 12:]) # 提取数据部分 PGNdata = ''.join( format(Framedata[12 * index + 12 + posindex], '02X') for posindex in range(8) ).upper() try: CanDataSpace = ' '.join( format(Framedata[12 * index + 12 + posindex], '02X') for posindex in range(8) ) except IndexError: continue current_data = CanDataSpace.split() if CanID in self.last_data_dict: last_data = self.last_data_dict[CanID].split() changed_indices = [] for i in range(min(len(last_data), len(current_data))): if last_data[i] != current_data[i]: changed_indices.append(i) self.changed_bytes_dict[CanID] = changed_indices else: self.changed_bytes_dict[CanID] = [] # 新ID无变化 self.last_data_dict[CanID] = CanDataSpace CanItemData = [Cantime, CanID, CanFramType, Len, CanDataSpace] # if CanID == "18F00F52": # print(CanDataSpace) # ✅ 只有在白名单内的PGNID才处理PGN信号 # 获取当前PGN数据 current_pgn_data = PGNdata # 使用上面生成的两位格式数据 # 检查数据变化 if PGNID in self.last_pgn_data_dict: last_data = self.last_pgn_data_dict[PGNID] changed_indices = [] for i in range(min(len(last_data), len(current_pgn_data) // 2)): start_idx = i * 2 end_idx = start_idx + 2 if last_data[start_idx:end_idx] != current_pgn_data[start_idx:end_idx]: changed_indices.append(i) self.changed_pgn_bytes_dict[PGNID] = changed_indices else: self.changed_pgn_bytes_dict[PGNID] = [] # 新PGN ID无变化 self.last_pgn_data_dict[PGNID] = current_pgn_data if PGNID in self.allowed_pgn_ids: PGNSignl = self.Frame_analoy_PGN_Signal(PGNID, Framedata[12 * index + 12:]) SignalItemData = [hex(PGNID)[2:].upper(), CanID, PGNdata, PGNSignl] if all(not sublist for sublist in CanPGNItemList) or CanPGNItemList[0][0] == 0: if CanPGNItemList: removed_id = CanPGNItemList[0][1] self.remove_from_index_maps("CanPGNItemList", removed_id) CanPGNItemList.pop(0) if PGNID in self.allowed_pgn_ids: PGNSignl = self.Frame_analoy_PGN_Signal(PGNID, Framedata[12 * index + 12:]) SignalItemData = [hex(PGNID)[2:].upper(), CanID, PGNdata, PGNSignl] # 使用快速查找 index_in_list = self.find_index("CanPGNItemList", CanID) if index_in_list is not None: CanPGNItemList[index_in_list] = SignalItemData else: CanPGNItemList.append(SignalItemData) new_index = len(CanPGNItemList) - 1 self.update_index_maps("CanPGNItemList", new_index, CanID) # if CanID in self.last_frame_time: # last_time = self.last_frame_time[CanID] # cycle = (current_time - last_time) * 1000 # 转换为毫秒 # if CanID == "18F00F52" : # print(cycle) # if cycle > 10: # if cycle<self.cycle_dict[CanID] : # self.cycle_dict[CanID] = cycle # elif self.cycle_dict[CanID] == 0 : # self.cycle_dict[CanID] = cycle # else: # self.cycle_dict[CanID] = 0 # 首次出现,周期设为0 # # if "18F00F52" in self.cycle_dict: # # print(self.cycle_dict["18F00F52"]) # self.last_frame_time[CanID] = current_time # 更新列表 if CanID in self.last_frame_time : last_time = self.last_frame_time[CanID] cycle_ms = (current_time - last_time) * 1000 # 有效周期过滤 # if 10 < cycle_ms < 10000: # 10ms-10s合理范围 self.cycle_dict[CanID] = cycle_ms else: # 新ID初始化 self.cycle_dict[CanID] = 0 # 更新最后出现时间 self.last_frame_time[CanID] = current_time filtered_cycles = getattr(self, 'filter_cycles', []) is_filtered = False if filtered_cycles: for filter_cycle in filtered_cycles: if filtered_cycle == 20: if cycle < 50: is_filtered = True break elif filtered_cycle == 50: if 50 <= cycle <= 100: is_filtered = True break elif filtered_cycle == 100: if 100 <= cycle <= 190: is_filtered = True break elif filtered_cycle == 200: if 200 <= cycle <= 400: is_filtered = True break elif filtered_cycle == 500: if 500 <= cycle <= 600: is_filtered = True break elif filtered_cycle == 1000: if 600 <= cycle <= 1000: is_filtered = True break index_in_obd = self.find_index("CanOBDItemList", CanID) index_in_filtered = self.find_index("filteredCanOBDItemList", CanID) # 根据过滤状态更新相应的列表 if is_filtered: if index_in_filtered is not None: filteredCanOBDItemList[index_in_filtered] = CanItemData self.data_updated_ids.add(CanID) else: # 如果存在于主列表,先移除 if index_in_obd is not None: self.remove_from_index_maps("CanOBDItemList", CanID) CanOBDItemList.pop(index_in_obd) self.data_updated_ids.add(CanID) filteredCanOBDItemList.append(CanItemData) new_index = len(filteredCanOBDItemList) - 1 self.update_index_maps("filteredCanOBDItemList", new_index, CanID) self.new_added_ids.add(CanID) else: index_in_list = self.find_index("CanOBDItemList", CanID) if index_in_obd is not None: CanOBDItemList[index_in_obd] = CanItemData self.data_updated_ids.add(CanID) else: # 如果存在于过滤列表,先移除 if index_in_filtered is not None: self.remove_from_index_maps("filteredCanOBDItemList", CanID) filteredCanOBDItemList.pop(index_in_filtered) CanOBDItemList.append(CanItemData) new_index = len(CanOBDItemList) - 1 self.update_index_maps("CanOBDItemList", new_index, CanID) self.new_added_ids.add(CanID) if "0C00002A" in self.cycle_dict: print(self.cycle_dict["0C00002A"]) def find_index(self, list_name, can_id): """快速查找索引的O(1)方法""" if list_name == "CanOBDItemList": return self.can_obd_index_map.get(can_id) elif list_name == "filteredCanOBDItemList": return self.filtered_index_map.get(can_id) elif list_name == "CanPGNItemList": return self.can_pgn_index_map.get(can_id) return None def update_index_maps(self, list_name, index, can_id): """更新索引映射""" if list_name == "CanOBDItemList": self.can_obd_index_map[can_id] = index elif list_name == "filteredCanOBDItemList": self.filtered_index_map[can_id] = index elif list_name == "CanPGNItemList": self.can_pgn_index_map[can_id] = index def remove_from_index_maps(self, list_name, can_id): """从索引映射中移除条目""" if list_name == "CanOBDItemList": self.can_obd_index_map.pop(can_id, None) elif list_name == "filteredCanOBDItemList": self.filtered_index_map.pop(can_id, None) elif list_name == "CanPGNItemList": self.can_pgn_index_map.pop(can_id, None) def Frame_analoy_PGN_Signal(self, PGNID, Framedata): # 确保数据是整数列表(0-255) if not all(isinstance(x, int) for x in Framedata): Framedata = [int(x) for x in Framedata] # 根据J1939规范解析 if PGNID == 0xFEF1: # 车速 (CCVS1) # 位置2-3 (索引1-2),大端序,单位1/256 km/h raw_val = (Framedata[1] << 8) | Framedata[2] return raw_val / 256.0 elif PGNID == 0xFE6C: # 车速 (TCO1) - 新增 # 位置7-8 (索引6-7),大端序,单位1/256 km/h raw_val = (Framedata[6] << 8) | Framedata[7] return raw_val / 256.0 elif PGNID == 0xF004: # 发动机转速+负载 # 负载:位置3 (索引2),单位1% engine_load = Framedata[2] & 0x7F # 取7位 # 转速:位置4-5 (索引3-4),大端序,单位0.125 RPM raw_rpm = (Framedata[3] << 8) | Framedata[4] rpm = raw_rpm * 0.125 return f'{engine_load}|{rpm}' elif PGNID == 0xFEC1: # 里程表 (VDHR) # 位置1-4 (索引0-3),大端序,单位0.125米 raw_val = int(Framedata[3] * 0x1000000) + int(Framedata[2] * 0x10000) + int(Framedata[1] * 0x100) + int(Framedata[0]) return raw_val * 0.125 # 转换为米 elif PGNID == 0xFEE5: # 发动机小时数 # 位置1-4 (索引0-3),大端序,单位0.05小时 raw_val = (Framedata[0] << 24) | (Framedata[1] << 16) | (Framedata[2] << 8) | Framedata[3] return raw_val * 0.05 elif PGNID == 0xFEF2: # 平均油耗 # 位置1-2 (索引0-1),大端序,单位0.05 L/h raw_val = (Framedata[0] << 8) | Framedata[1] return raw_val * 0.05 elif PGNID == 0xFEEE: # 冷却液温度 # 位置1 (索引0),单位1°C,偏移-40 return Framedata[0] - 40 elif PGNID == 0xFE56: # 燃油液位 # 位置1 (索引0),单位0.4% return Framedata[0] * 0.4 elif PGNID == 0xF005: # 档位 # 位置4 (索引3),直接返回值 return Framedata[3] return None # def start_reading(self): # self.read_thread = threading.Thread(target=self.Com_read_frame, daemon=True) # self.read_thread.start() 这是serialpro文件 # -*- coding: utf-8 -*- import threading # Form implementation generated from reading ui file 'CanOBD.ui' # # Created by: PyQt5 UI code generator 5.15.9 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtCore import QThread, pyqtSignal,QTimer from PyQt5.QtWidgets import QApplication, QTableWidget, QTableWidgetItem,QTreeWidget, QTreeWidgetItem from SerialPro import SerialPort as SerialThread from SerialPro import CanOBDItemList,CanPGNItemList,filteredCanOBDItemList import time import serial import serial.tools.list_ports import binascii import struct class RichTextDelegate(QtWidgets.QStyledItemDelegate): def paint(self, painter, option, index): if index.column() == 4: text = index.data(QtCore.Qt.DisplayRole) if not text: return # 创建 QTextDocument 并设置 HTML 内容 doc = QtGui.QTextDocument() doc.setHtml(text) # 设置基础字体(可以显式调大字体) font = option.font font.setPointSize(font.pointSize() + 2) # 调大字体 2pt,比如原来是 10pt → 12pt doc.setDefaultFont(font) # 调整绘制区域(不影响字体大小,仅调整边距) rect = option.rect.adjusted(2, 0, -2, 0) # 左右各留出 2 像素的空白 # 绘制背景 style = QtWidgets.QApplication.style() style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, option, painter) # 绘制文本 painter.save() painter.translate(rect.topLeft()) doc.drawContents(painter, QtCore.QRectF(0, 0, rect.width(), rect.height())) painter.restore() else: super().paint(painter, option, index) class RichTextTreeDelegate(QtWidgets.QStyledItemDelegate): def paint(self, painter, option, index): if index.column() == 2: # 只处理数据列 text = index.data(QtCore.Qt.DisplayRole) if not text: return super().paint(painter, option, index) # 创建QTextDocument并设置HTML内容 doc = QtGui.QTextDocument() doc.setHtml(text) # 设置基础字体 font = option.font font.setPointSize(font.pointSize()) doc.setDefaultFont(font) # 调整绘制区域 rect = option.rect.adjusted(2, 0, -2, 0) # 绘制背景 style = QtWidgets.QApplication.style() style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, option, painter) # 绘制文本 painter.save() painter.translate(rect.topLeft()) doc.drawContents(painter, QtCore.QRectF(0, 0, rect.width(), rect.height())) painter.restore() else: super().paint(painter, option, index) class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(1220, 940) font = QtGui.QFont() font.setPointSize(12) MainWindow.setFont(font) self.CanOBDHiddenList = [] # 新增隐藏列表 self.last_checked_cycles = [] # 记录上一次的过滤条件 self.filter_changed = False # 过滤条件是否变化 self.filter_cycles = [] # 添这一行用于存储过滤周期 self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.tabWidget = QtWidgets.QTabWidget(self.centralwidget) self.tabWidget.setGeometry(QtCore.QRect(0, 0, 1041, 940)) self.tabWidget.setObjectName("tabWidget") self.tab = QtWidgets.QWidget() self.tab.setObjectName("tab") self.tableWidget = QtWidgets.QTableWidget(self.tab) self.tableWidget.setGeometry(QtCore.QRect(0, 0, 1031, 940)) self.tableWidget.setObjectName("tableWidget") self.tableWidget.setColumnCount(5) self.tableWidget.setRowCount(150) #富文本委托 self.tableWidget.setItemDelegate(RichTextDelegate()) for num in range(0,150,1): item = QtWidgets.QTableWidgetItem() self.tableWidget.setVerticalHeaderItem(num, item) item = QtWidgets.QTableWidgetItem() font = QtGui.QFont() font.setKerning(False) item.setFont(font) for line in range(0,5,1): self.tableWidget.setHorizontalHeaderItem(line, item) item = QtWidgets.QTableWidgetItem() self.tabWidget.addTab(self.tab, "") self.tab_2 = QtWidgets.QWidget() self.tab_2.setObjectName("CanOBD Cfg Set") self.mSpeedTreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mSpeedTreeWidget.setGeometry(QtCore.QRect(10, 0, 1031, 101)) self.mSpeedTreeWidget.setObjectName("mSpeedTreeWidget") font = QtGui.QFont() font.setPointSize(12) self.mSpeedTreeWidget.headerItem().setFont(0, font) self.mSpeedTreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mSpeedTreeWidget.headerItem().setFont(3, font) self.mRPMTreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mRPMTreeWidget.setGeometry(QtCore.QRect(10, 100, 1031, 91)) self.mRPMTreeWidget.setObjectName("mRPMTreeWidget") font = QtGui.QFont() font.setPointSize(12) self.mRPMTreeWidget.headerItem().setFont(0, font) self.mRPMTreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mRPMTreeWidget.headerItem().setFont(3, font) self.mVDHRTreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mVDHRTreeWidget.setGeometry(QtCore.QRect(10, 190, 1031, 91)) self.mVDHRTreeWidget.setObjectName("mVDHRTreeWidget") font = QtGui.QFont() font.setPointSize(12) self.mVDHRTreeWidget.headerItem().setFont(0, font) self.mVDHRTreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mVDHRTreeWidget.headerItem().setFont(3, font) self.mHoursTreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mHoursTreeWidget.setGeometry(QtCore.QRect(10, 280, 1031, 101)) self.mHoursTreeWidget.setObjectName("mHoursTreeWidget") font = QtGui.QFont() font.setPointSize(12) self.mHoursTreeWidget.headerItem().setFont(0, font) self.mHoursTreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mHoursTreeWidget.headerItem().setFont(3, font) self.mEECTreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mEECTreeWidget.setGeometry(QtCore.QRect(10, 380, 1031, 91)) self.mEECTreeWidget.setObjectName("mEECTreeWidget") font = QtGui.QFont() font.setPointSize(12) self.mEECTreeWidget.headerItem().setFont(0, font) self.mEECTreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mEECTreeWidget.headerItem().setFont(3, font) self.mET1TreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mET1TreeWidget.setGeometry(QtCore.QRect(10, 470, 1031, 101)) self.mET1TreeWidget.setObjectName("mET1TreeWidget") font = QtGui.QFont() font.setPointSize(12) self.mET1TreeWidget.headerItem().setFont(0, font) self.mET1TreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mET1TreeWidget.headerItem().setFont(3, font) self.mAT1T1ITreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mAT1T1ITreeWidget.setGeometry(QtCore.QRect(10, 570, 1031, 91)) self.mAT1T1ITreeWidget.setObjectName("mAT1T1ITreeWidget") font = QtGui.QFont() font.setPointSize(12) self.mAT1T1ITreeWidget.headerItem().setFont(0, font) self.mAT1T1ITreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mAT1T1ITreeWidget.headerItem().setFont(3, font) self.mLFETreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mLFETreeWidget.setGeometry(QtCore.QRect(10, 660, 1031, 101)) self.mLFETreeWidget.setObjectName("mLFETreeWidget") font = QtGui.QFont() font.setPointSize(12) self.mLFETreeWidget.headerItem().setFont(0, font) self.mLFETreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mLFETreeWidget.headerItem().setFont(3, font) self.mETC2TreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mETC2TreeWidget.setGeometry(QtCore.QRect(10, 760, 1031, 101)) self.mETC2TreeWidget.setObjectName("mETC2TreeWidget") font = QtGui.QFont() font.setPointSize(12) self.mETC2TreeWidget.headerItem().setFont(0, font) self.mETC2TreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mETC2TreeWidget.headerItem().setFont(3, font) self.tabWidget.addTab(self.tab_2, "") self.mComCfgBox = QtWidgets.QGroupBox(self.centralwidget) self.mComCfgBox.setGeometry(QtCore.QRect(1040, 10, 191, 231)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mComCfgBox.setFont(font) self.mComCfgBox.setObjectName("mComCfgBox") self.mPortName = QtWidgets.QLabel(self.mComCfgBox) self.mPortName.setGeometry(QtCore.QRect(20, 30, 61, 21)) self.mPortName.setObjectName("mPortName") self.mBpsName = QtWidgets.QLabel(self.mComCfgBox) self.mBpsName.setGeometry(QtCore.QRect(20, 60, 61, 21)) self.mBpsName.setObjectName("mBpsName") self.mDatabitName = QtWidgets.QLabel(self.mComCfgBox) self.mDatabitName.setGeometry(QtCore.QRect(20, 90, 61, 21)) self.mDatabitName.setObjectName("mDatabitName") self.mStopName = QtWidgets.QLabel(self.mComCfgBox) self.mStopName.setGeometry(QtCore.QRect(20, 120, 61, 21)) self.mStopName.setObjectName("mStopName") self.mOddName = QtWidgets.QLabel(self.mComCfgBox) self.mOddName.setGeometry(QtCore.QRect(20, 150, 61, 21)) self.mOddName.setObjectName("mOddName") self.mDatabitVal = QtWidgets.QLabel(self.mComCfgBox) self.mDatabitVal.setGeometry(QtCore.QRect(100, 90, 54, 21)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mDatabitVal.setFont(font) self.mDatabitVal.setLayoutDirection(QtCore.Qt.LeftToRight) self.mDatabitVal.setAlignment(QtCore.Qt.AlignCenter) self.mDatabitVal.setObjectName("mDatabitVal") self.mStopBitVal = QtWidgets.QLabel(self.mComCfgBox) self.mStopBitVal.setGeometry(QtCore.QRect(100, 120, 54, 21)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mStopBitVal.setFont(font) self.mStopBitVal.setLayoutDirection(QtCore.Qt.LeftToRight) self.mStopBitVal.setAlignment(QtCore.Qt.AlignCenter) self.mStopBitVal.setObjectName("mStopBitVal") self.mOddVal = QtWidgets.QLabel(self.mComCfgBox) self.mOddVal.setGeometry(QtCore.QRect(100, 150, 54, 21)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mOddVal.setFont(font) self.mOddVal.setLayoutDirection(QtCore.Qt.LeftToRight) self.mOddVal.setAlignment(QtCore.Qt.AlignCenter) self.mOddVal.setObjectName("mOddVal") self.mPortVal = QtWidgets.QComboBox(self.mComCfgBox) self.mPortVal.setGeometry(QtCore.QRect(90, 30, 81, 22)) self.mPortVal.setObjectName("mPortVal") self.mPortVal.addItem("") self.mPortVal.addItem("") self.mPortVal.addItem("") self.mPortVal.addItem("") self.mPortVal.addItem("") self.mPortVal.addItem("") self.mBPSVal = QtWidgets.QComboBox(self.mComCfgBox) self.mBPSVal.setGeometry(QtCore.QRect(90, 60, 81, 22)) self.mBPSVal.setObjectName("mBPSVal") self.mBPSVal.addItem("") self.mBPSVal.addItem("") self.mBPSVal.addItem("") self.mBPSVal.addItem("") self.mBPSVal.addItem("") self.mBPSVal.addItem("") self.mOpenSerial = QtWidgets.QDialogButtonBox(self.mComCfgBox) self.mOpenSerial.setGeometry(QtCore.QRect(20, 190, 156, 31)) self.mOpenSerial.setStandardButtons(QtWidgets.QDialogButtonBox.Close|QtWidgets.QDialogButtonBox.Open) self.mOpenSerial.setObjectName("mOpenSerial") self.mCycleCfgBox = QtWidgets.QGroupBox(self.centralwidget) self.mCycleCfgBox.setGeometry(QtCore.QRect(1040, 260, 191, 221)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mCycleCfgBox.setFont(font) self.mCycleCfgBox.setObjectName("mCycleCfgBox") self.mcheck1000ms = QtWidgets.QCheckBox(self.mCycleCfgBox) self.mcheck1000ms.setGeometry(QtCore.QRect(20, 180, 141, 31)) self.mcheck1000ms.setObjectName("mcheck1000ms") self.mcheck500ms = QtWidgets.QCheckBox(self.mCycleCfgBox) self.mcheck500ms.setGeometry(QtCore.QRect(20, 150, 141, 31)) self.mcheck500ms.setObjectName("mcheck500ms") self.mcheck100ms = QtWidgets.QCheckBox(self.mCycleCfgBox) self.mcheck100ms.setGeometry(QtCore.QRect(20, 90, 141, 31)) self.mcheck100ms.setObjectName("mcheck100ms") self.mcheck50ms = QtWidgets.QCheckBox(self.mCycleCfgBox) self.mcheck50ms.setGeometry(QtCore.QRect(20, 60, 141, 31)) self.mcheck50ms.setObjectName("mcheck50ms") self.mcheck20ms = QtWidgets.QCheckBox(self.mCycleCfgBox) self.mcheck20ms.setGeometry(QtCore.QRect(20, 30, 141, 31)) self.mcheck20ms.setObjectName("mcheck20ms") self.mcheck200ms = QtWidgets.QCheckBox(self.mCycleCfgBox) self.mcheck200ms.setGeometry(QtCore.QRect(20, 120, 141, 31)) self.mcheck200ms.setObjectName("mcheck200ms") self.mEventSigBox = QtWidgets.QGroupBox(self.centralwidget) self.mEventSigBox.setGeometry(QtCore.QRect(1050, 490, 191, 151)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mEventSigBox.setFont(font) self.mEventSigBox.setObjectName("mEventSigBox") self.radioLeftREvent = QtWidgets.QRadioButton(self.mEventSigBox) self.radioLeftREvent.setGeometry(QtCore.QRect(10, 30, 151, 16)) self.radioLeftREvent.setObjectName("radioLeftREvent") self.radioKiilEvent = QtWidgets.QRadioButton(self.mEventSigBox) self.radioKiilEvent.setGeometry(QtCore.QRect(10, 90, 151, 16)) self.radioKiilEvent.setObjectName("radioKiilEvent") self.radioPEvent = QtWidgets.QRadioButton(self.mEventSigBox) self.radioPEvent.setGeometry(QtCore.QRect(10, 120, 151, 16)) self.radioPEvent.setObjectName("radioPEvent") self.radioOpenCloseEvent = QtWidgets.QRadioButton(self.mEventSigBox) self.radioOpenCloseEvent.setGeometry(QtCore.QRect(10, 60, 151, 16)) self.radioOpenCloseEvent.setObjectName("radioOpenCloseEvent") self.mReadOBDinfBox = QtWidgets.QGroupBox(self.centralwidget) self.mReadOBDinfBox.setGeometry(QtCore.QRect(1050, 660, 191, 171)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mReadOBDinfBox.setFont(font) self.mReadOBDinfBox.setObjectName("mReadOBDinfBox") self.radioVinRead = QtWidgets.QRadioButton(self.mReadOBDinfBox) self.radioVinRead.setGeometry(QtCore.QRect(10, 40, 141, 21)) self.radioVinRead.setObjectName("radioVinRead") self.mVinInfShow = QtWidgets.QTextBrowser(self.mReadOBDinfBox) self.mVinInfShow.setGeometry(QtCore.QRect(10, 70, 171, 91)) self.mVinInfShow.setObjectName("mVinInfShow") MainWindow.setCentralWidget(self.centralwidget) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) self.retranslateUi(MainWindow) self.tabWidget.setCurrentIndex(0) QtCore.QMetaObject.connectSlotsByName(MainWindow) self.mSpeedTreeWidget.setItemDelegate(RichTextTreeDelegate()) self.mRPMTreeWidget.setItemDelegate(RichTextTreeDelegate()) self.mVDHRTreeWidget.setItemDelegate(RichTextTreeDelegate()) self.mHoursTreeWidget.setItemDelegate(RichTextTreeDelegate()) self.mEECTreeWidget.setItemDelegate(RichTextTreeDelegate()) self.mET1TreeWidget.setItemDelegate(RichTextTreeDelegate()) self.mAT1T1ITreeWidget.setItemDelegate(RichTextTreeDelegate()) self.mLFETreeWidget.setItemDelegate(RichTextTreeDelegate()) self.mETC2TreeWidget.setItemDelegate(RichTextTreeDelegate()) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) for num in range(0, 150, 1): item = self.tableWidget.verticalHeaderItem(num) item.setText(_translate("MainWindow", str(num +1))) item = self.tableWidget.horizontalHeaderItem(0) item.setText(_translate("MainWindow", "时间标识")) item = self.tableWidget.horizontalHeaderItem(1) item.setText(_translate("MainWindow", "帧ID")) item = self.tableWidget.horizontalHeaderItem(2) item.setText(_translate("MainWindow", "帧类型")) item = self.tableWidget.horizontalHeaderItem(3) item.setText(_translate("MainWindow", "长度")) item = self.tableWidget.horizontalHeaderItem(4) item.setText(_translate("MainWindow", "数据 (BIT7--BIT0 大端模式)")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", "Tab 1")) self.mSpeedTreeWidget.headerItem().setText(0, _translate("MainWindow", "速度[CCVS1]")) self.mSpeedTreeWidget.headerItem().setText(1, _translate("MainWindow", "CanID")) self.mSpeedTreeWidget.headerItem().setText(2, _translate("MainWindow", "Data")) self.mSpeedTreeWidget.headerItem().setText(3, _translate("MainWindow", "Signal(km/h)")) self.mSpeedTreeWidget.setColumnWidth(0, 150) self.mSpeedTreeWidget.setColumnWidth(1, 150) self.mSpeedTreeWidget.setColumnWidth(2, 550) self.mSpeedTreeWidget.setColumnWidth(3, 150) self.mRPMTreeWidget.headerItem().setText(0, _translate("MainWindow", "转速[EEC1]")) self.mRPMTreeWidget.headerItem().setText(1, _translate("MainWindow", "CanID")) self.mRPMTreeWidget.headerItem().setText(2, _translate("MainWindow", "Data")) self.mRPMTreeWidget.headerItem().setText(3, _translate("MainWindow", "Signal(rpm)")) self.mRPMTreeWidget.setColumnWidth(0, 150) self.mRPMTreeWidget.setColumnWidth(1, 150) self.mRPMTreeWidget.setColumnWidth(2, 550) self.mRPMTreeWidget.setColumnWidth(3, 150) self.mVDHRTreeWidget.headerItem().setText(0, _translate("MainWindow", "里程[VDHR]")) self.mVDHRTreeWidget.headerItem().setText(1, _translate("MainWindow", "CanID")) self.mVDHRTreeWidget.headerItem().setText(2, _translate("MainWindow", "Data")) self.mVDHRTreeWidget.headerItem().setText(3, _translate("MainWindow", "Signal(km)")) self.mVDHRTreeWidget.setColumnWidth(0, 150) self.mVDHRTreeWidget.setColumnWidth(1, 150) self.mVDHRTreeWidget.setColumnWidth(2, 550) self.mVDHRTreeWidget.setColumnWidth(3, 150) self.mHoursTreeWidget.headerItem().setText(0, _translate("MainWindow", "工作时长[HOURS]")) self.mHoursTreeWidget.headerItem().setText(1, _translate("MainWindow", "CanID")) self.mHoursTreeWidget.headerItem().setText(2, _translate("MainWindow", "Data")) self.mHoursTreeWidget.headerItem().setText(3, _translate("MainWindow", "Signal(hours)")) self.mHoursTreeWidget.setColumnWidth(0, 150) self.mHoursTreeWidget.setColumnWidth(1, 150) self.mHoursTreeWidget.setColumnWidth(2, 550) self.mHoursTreeWidget.setColumnWidth(3, 150) self.mEECTreeWidget.headerItem().setText(0, _translate("MainWindow", "发动机负载[EEC1]")) self.mEECTreeWidget.headerItem().setText(1, _translate("MainWindow", "CanID")) self.mEECTreeWidget.headerItem().setText(2, _translate("MainWindow", "Data")) self.mEECTreeWidget.headerItem().setText(3, _translate("MainWindow", "Signal(%)")) self.mEECTreeWidget.setColumnWidth(0, 150) self.mEECTreeWidget.setColumnWidth(1, 150) self.mEECTreeWidget.setColumnWidth(2, 550) self.mEECTreeWidget.setColumnWidth(3, 150) self.mET1TreeWidget.headerItem().setText(0, _translate("MainWindow", "冷却液温度[ET1]")) self.mET1TreeWidget.headerItem().setText(1, _translate("MainWindow", "CanID")) self.mET1TreeWidget.headerItem().setText(2, _translate("MainWindow", "Data")) self.mET1TreeWidget.headerItem().setText(3, _translate("MainWindow", "Signal(°)")) self.mET1TreeWidget.setColumnWidth(0, 150) self.mET1TreeWidget.setColumnWidth(1, 150) self.mET1TreeWidget.setColumnWidth(2, 550) self.mET1TreeWidget.setColumnWidth(3, 150) self.mAT1T1ITreeWidget.headerItem().setText(0, _translate("MainWindow", "燃油液面[AT1T1I]")) self.mAT1T1ITreeWidget.headerItem().setText(1, _translate("MainWindow", "CanID")) self.mAT1T1ITreeWidget.headerItem().setText(2, _translate("MainWindow", "Data")) self.mAT1T1ITreeWidget.headerItem().setText(3, _translate("MainWindow", "Signal(%)")) self.mAT1T1ITreeWidget.setColumnWidth(0, 150) self.mAT1T1ITreeWidget.setColumnWidth(1, 150) self.mAT1T1ITreeWidget.setColumnWidth(2, 550) self.mAT1T1ITreeWidget.setColumnWidth(3, 150) self.mLFETreeWidget.headerItem().setText(0, _translate("MainWindow", "平均油耗[LFE]")) self.mLFETreeWidget.headerItem().setText(1, _translate("MainWindow", "CanID")) self.mLFETreeWidget.headerItem().setText(2, _translate("MainWindow", "Data")) self.mLFETreeWidget.headerItem().setText(3, _translate("MainWindow", "Signal(L/h)")) self.mLFETreeWidget.setColumnWidth(0, 150) self.mLFETreeWidget.setColumnWidth(1, 150) self.mLFETreeWidget.setColumnWidth(2, 550) self.mLFETreeWidget.setColumnWidth(3, 150) self.mETC2TreeWidget.headerItem().setText(0, _translate("MainWindow", "档位[ETC2]")) self.mETC2TreeWidget.headerItem().setText(1, _translate("MainWindow", "CanID")) self.mETC2TreeWidget.headerItem().setText(2, _translate("MainWindow", "Data")) self.mETC2TreeWidget.headerItem().setText(3, _translate("MainWindow", "Signal")) self.mETC2TreeWidget.setColumnWidth(0, 150) self.mETC2TreeWidget.setColumnWidth(1, 150) self.mETC2TreeWidget.setColumnWidth(2, 550) self.mETC2TreeWidget.setColumnWidth(3, 150) self.tableWidget.setColumnWidth(0, 200) self.tableWidget.setColumnWidth(1, 150) self.tableWidget.setColumnWidth(4,450) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", "CanOBD Inf Show")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "CanOBD J1939 Show")) self.mComCfgBox.setTitle(_translate("MainWindow", "串口配置")) self.mPortName.setText(_translate("MainWindow", "端口号")) self.mBpsName.setText(_translate("MainWindow", "波特率")) self.mDatabitName.setText(_translate("MainWindow", "数据位")) self.mStopName.setText(_translate("MainWindow", "停止位")) self.mOddName.setText(_translate("MainWindow", "检验位")) self.mDatabitVal.setText(_translate("MainWindow", "8")) self.mStopBitVal.setText(_translate("MainWindow", "1")) self.mOddVal.setText(_translate("MainWindow", "无")) self.mBPSVal.setItemText(0, _translate("MainWindow", "9600")) self.mBPSVal.setItemText(1, _translate("MainWindow", "19200")) self.mBPSVal.setItemText(2, _translate("MainWindow", "115200")) self.mBPSVal.setItemText(3, _translate("MainWindow", "230400")) self.mBPSVal.setItemText(4, _translate("MainWindow", "256000")) self.mBPSVal.setItemText(5, _translate("MainWindow", "460800")) port_list = list(serial.tools.list_ports.comports()) if port_list.__len__() is not 0: for num in range(port_list.__len__()): self.mPortVal.setItemText(num, _translate("MainWindow", str(port_list[num].device))) serialport = self.mPortVal.currentText() serialbaudrate = self.mBPSVal.currentText() self.LSerial = SerialThread(serialport, serialbaudrate) self.mCycleCfgBox.setTitle(_translate("MainWindow", "过滤设置(周期)")) self.mcheck1000ms.setText(_translate("MainWindow", "1000ms 周期")) self.mcheck500ms.setText(_translate("MainWindow", "500ms 周期")) self.mcheck100ms.setText(_translate("MainWindow", "100ms 周期")) self.mcheck50ms.setText(_translate("MainWindow", "50ms 周期")) self.mcheck20ms.setText(_translate("MainWindow", "20ms 周期")) self.mcheck200ms.setText(_translate("MainWindow", "200ms 周期")) self.mEventSigBox.setTitle(_translate("MainWindow", "事件信号策略")) self.radioLeftREvent.setText(_translate("MainWindow", "左右转 事件")) self.radioKiilEvent.setText(_translate("MainWindow", "刹车 事件")) self.radioPEvent.setText(_translate("MainWindow", "档位 事件")) self.radioOpenCloseEvent.setText(_translate("MainWindow", "开关门 事件")) self.mReadOBDinfBox.setTitle(_translate("MainWindow", "主动读取信息")) self.radioVinRead.setText(_translate("MainWindow", "VIN 信息")) self.radioLeftREvent.toggled.connect(self.on_radio_left_r_event_toggled) def OpenSerial(self): if self.LSerial != None: if self.LSerial.SerialIsOpen(): self.LSerial.__del__() port_list = list(serial.tools.list_ports.comports()) if port_list.__len__() != 0: serialport = self.mPortVal.currentText() serialbaudrate = self.mBPSVal.currentText() self.LSerial.__init__(serialport,serialbaudrate) # 开启线程 self.thread = Worker() # 创建线程对象 self.thread.update_signal.connect(self.CanOBDdatarefresh) # 连接信号和槽 self.thread.update_signal.connect(self.CanOBDSignalAnalyPro) # 连接信号和槽 # self.thread.update_signal.connect(self.LSerial.Com_read_frame) # 连接信号和槽 self.thread.start() # 启动线程 #self.LSerial.Com_read_frame() self.LSerial.start_reading() # <-- 在这里启动读取线程 def CloseSerial(self): if self.LSerial.SerialIsOpen(): self.LSerial.close() def Serialconnectslot(self): self.mOpenSerial.accepted.connect(self.OpenSerial) self.mOpenSerial.rejected.connect(self.CloseSerial) def on_radio_left_r_event_toggled(self, checked): if checked: # 勾选所有周期过滤项 self.mcheck20ms.setChecked(True) self.mcheck50ms.setChecked(True) self.mcheck100ms.setChecked(True) self.mcheck200ms.setChecked(True) self.mcheck500ms.setChecked(True) self.mcheck1000ms.setChecked(True) def get_checked_cycles(self): """返回用户勾选的所有周期值(毫秒)""" checked_cycles = [] if self.mcheck20ms.isChecked(): checked_cycles.append(20) if self.mcheck50ms.isChecked(): checked_cycles.append(50) if self.mcheck100ms.isChecked(): checked_cycles.append(100) if self.mcheck200ms.isChecked(): checked_cycles.append(200) if self.mcheck500ms.isChecked(): checked_cycles.append(500) if self.mcheck1000ms.isChecked(): checked_cycles.append(1000) return checked_cycles def is_cycle_filtered(self, cycle, filtered_cycles): """检查给定周期是否在过滤范围内,并根据周期大小动态调整容差""" if cycle == 0: # 如果没有记录周期,默认不过滤 return False for filtered_cycle in filtered_cycles: # 定义过滤范围 if filtered_cycle == 20: if cycle <= 50: return True elif filtered_cycle == 50: if 50 <= cycle <= 100: return True elif filtered_cycle == 100: if 100 <= cycle <= 200: return True elif filtered_cycle == 200: if 200 <= cycle <= 400: return True elif filtered_cycle == 500: if 500 <= cycle <= 600: return True elif filtered_cycle == 1000: if 600 <= cycle <= 1000: return True return False def refresh_full_table(self): """全表刷新:保留表格结构,只更新内容""" # 获取当前过滤条件 filtered_cycles = self.get_checked_cycles() # 合并所有数据(注意不要提前 clear) all_items = [] all_items.extend(CanOBDItemList) all_items.extend(filteredCanOBDItemList) # 清空原列表 CanOBDItemList.clear() filteredCanOBDItemList.clear() # 用 set 来记录已经入的数据,防止重复插入 added_can_ids = set() # 重新分配数据到两个列表 for item in all_items: can_id = item[1] if can_id == 0: continue # 跳过无效数据 cycle = self.LSerial.cycle_dict.get(can_id, 0) if self.is_cycle_filtered(cycle, filtered_cycles): if can_id not in added_can_ids: filteredCanOBDItemList.append(item) added_can_ids.add(can_id) else: if can_id not in added_can_ids: CanOBDItemList.append(item) added_can_ids.add(can_id) # 保留表格结构(150行),只清空所有内容 for row in range(self.tableWidget.rowCount()): for col in range(self.tableWidget.columnCount()): item = self.tableWidget.item(row, col) if item: item.setText("") # 填充符合条件的行 for row_index, item_data in enumerate(CanOBDItemList): if item_data[1] == 0: # 跳过无效数据 continue self.update_table_row(row_index, item_data) self.tableWidget.show() def update_table_row(self, row_index, item_data): """更新表格的指定行,标记变化字节""" # 前4列正常显示 for col in range(4): self.tableWidget.setItem(row_index, col, QtWidgets.QTableWidgetItem(str(item_data[col]))) # 第4列(数据)特殊处理:标记变化字节 can_id = item_data[1] changed_indices = [] if hasattr(self, 'LSerial') and self.LSerial is not None: changed_indices = self.LSerial.changed_bytes_dict.get(can_id, []) data_bytes = item_data[4].split() rich_text = "" for idx, byte_str in enumerate(data_bytes): if idx in changed_indices: rich_text += f'<font color="red">{byte_str}</font> ' else: rich_text += f'{byte_str} ' # 创建富文本显示项 item = QtWidgets.QTableWidgetItem() item.setData(QtCore.Qt.DisplayRole, item_data[4]) # 原始数据用于排序 item.setData(QtCore.Qt.EditRole, rich_text) # 富文本用于显示 self.tableWidget.setItem(row_index, 4, item) def CanOBDdatarefresh(self): filtered_cycles = self.get_checked_cycles() if hasattr(self, 'LSerial'): self.LSerial.set_filter_cycles(filtered_cycles) # 判断是否需要执行全表刷新 current_cycles = self.get_checked_cycles() if sorted(current_cycles) != sorted(self.last_checked_cycles): self.refresh_full_table() self.last_checked_cycles = sorted(current_cycles) return # 获取更新和新增的CAN ID all_update_ids = self.LSerial.data_updated_ids | self.LSerial.new_added_ids # 遍历每个可能变化的ID for can_id in all_update_ids: found_in_main = False found_in_filtered = False # 查找在主列表中的索引 main_index = None for idx, item in enumerate(CanOBDItemList): if item[1] == can_id: main_index = idx found_in_main = True break # 查找在过滤列表中的索引 for idx, item in enumerate(filteredCanOBDItemList): if item[1] == can_id: found_in_filtered = True break cycle = self.LSerial.cycle_dict.get(can_id, 0) should_filter = self.is_cycle_filtered(cycle, filtered_cycles) if found_in_main: if should_filter: # 应该过滤 -> 移动到过滤列表并清空对应行 filtered_item = CanOBDItemList.pop(main_index) filteredCanOBDItemList.append(filtered_item) self.clear_table_row(main_index) else: # 不应过滤 -> 更新行数据 new_data = next((item for item in CanOBDItemList + [filtered_item for filtered_item in filteredCanOBDItemList if filtered_item[1] == can_id]), None) if new_data: self.update_table_row(main_index, new_data) elif found_in_filtered: if not should_filter: # 不应该过滤 -> 从过滤列表移到主列表并恢复行数据 for idx, item in enumerate(filteredCanOBDItemList): if item[1] == can_id: unfiltered_item = filteredCanOBDItemList.pop(idx) CanOBDItemList.append(unfiltered_item) new_row_index = len(CanOBDItemList) - 1 self.update_table_row(new_row_index, unfiltered_item) break else: # 新出现的数据 new_data = next((item for item in SerialPro.CanOBDItemList if item[1] == can_id), None) if new_data: cycle = self.LSerial.cycle_dict.get(can_id, 0) if self.is_cycle_filtered(cycle, filtered_cycles): filteredCanOBDItemList.append(new_data) else: CanOBDItemList.append(new_data) new_row_index = len(CanOBDItemList) - 1 self.update_table_row(new_row_index, new_data) # 统一刷新所有行,确保状态一致 self.sync_table_with_data() self.LSerial.data_updated_ids.clear() self.LSerial.new_added_ids.clear() def sync_table_with_data(self): """ 同步表格与数据源,确保前 len(CanOBDItemList) 行是真实数据, 超出部分保持空白或清除内容。 """ max_rows = self.tableWidget.rowCount() for row in range(max_rows): if row < len(CanOBDItemList): item_data = CanOBDItemList[row] if item_data and item_data[1] != 0: self.update_table_row(row, item_data) else: self.clear_table_row(row) else: # 清除多余行的内容(设置为空白) for col in range(self.tableWidget.columnCount()): item = self.tableWidget.item(row, col) if item: item.setText("") def clear_table_row(self, row_index): """清除行内容时重置富文本显示""" for col in range(5): item = self.tableWidget.item(row_index, col) if item: item.setText("") # 清除富文本格式 item.setData(QtCore.Qt.EditRole, "") else: self.tableWidget.setItem(row_index, col, QtWidgets.QTableWidgetItem("")) def CanOBDSignalAnalyPro(self): index = 0 bfindflag = 0 if all(not sublist for sublist in CanPGNItemList) or CanPGNItemList[0][0] == 0: if len(CanPGNItemList): CanPGNItemList.pop(0) else: for signalindex in CanPGNItemList: value = ''.join(c for c in signalindex[0].lower() if c in '0123456789abcdef') if len(value) % 2 != 0: value = '0' + value signalindex[0] = value PGNCanID = bytes.fromhex(str(signalindex[0])).hex() # 车速 - CCVS1 if (PGNCanID == bytes.fromhex("FEF1").hex()): self.update_tree_widget(self.mSpeedTreeWidget, signalindex, "速度[CCVS1]") # 发动机转速 - EEC1 发动机负载 elif (PGNCanID == bytes.fromhex("F004").hex()): self.update_tree_widget(self.mRPMTreeWidget, signalindex, "转速[EEC1]") self.update_tree_widget(self.mEECTreeWidget, signalindex, "转速[EEC1]") # 里程表 - VDHR elif (PGNCanID == bytes.fromhex("FEC1").hex()): self.update_tree_widget(self.mVDHRTreeWidget, signalindex, "里程[VDHR]") # 发动机工作小时数 - HOURS elif (PGNCanID == bytes.fromhex("FEE5").hex()): self.update_tree_widget(self.mHoursTreeWidget, signalindex, "工作时长[HOURS]") # 平均油耗 - LFE elif (PGNCanID == bytes.fromhex("FEF2").hex()): self.update_tree_widget(self.mLFETreeWidget, signalindex, "平均油耗[LFE]") # 冷却液温度 - ET1 elif (PGNCanID == bytes.fromhex("FEEE").hex()): self.update_tree_widget(self.mET1TreeWidget, signalindex, "冷却液温度[ET1]") # 发动机负载 - EEC1 elif (PGNCanID == bytes.fromhex("F004").hex()): self.update_tree_widget(self.mEECTreeWidget, signalindex, "发动机负载[EEC1]") # 燃油液位 - AT1T1I elif (PGNCanID == bytes.fromhex("FE56").hex()): self.update_tree_widget(self.mAT1T1ITreeWidget, signalindex, "燃油液面[AT1T1I]") # 档位 - ETC2 elif (PGNCanID == bytes.fromhex("F005").hex()): self.update_tree_widget(self.mETC2TreeWidget, signalindex, "档位[ETC2]") def update_tree_widget(self, tree_widget, signal_data, signal_name): """更新树形部件,添空格并标记变化字节,确保0值显示为00""" num_top_items = tree_widget.topLevelItemCount() bfindflag = False can_id = str(signal_data[1]) pgn_id = signal_data[0].lower() # 确保数据是16位长度(8个字节) data_str = signal_data[2] if len(data_str) != 16: # 如果不是16位,可能是0值被省略 # 补齐到16位 data_str = data_str.zfill(16) # 获取变化字节索引 changed_indices = [] if hasattr(self, 'LSerial') and self.LSerial is not None: try: pgn_id_int = int(pgn_id, 16) changed_indices = self.LSerial.changed_pgn_bytes_dict.get(pgn_id_int, []) except ValueError: pass # 格式化数据字符串(添空格并确保两位显示) formatted_data = "" for i in range(0, len(data_str), 2): byte_str = data_str[i:i + 2] # 确保每个字节都是两位显示 if len(byte_str) == 1: byte_str = '0' + byte_str if i // 2 in changed_indices: formatted_data += f'<font color="red">{byte_str}</font> ' else: formatted_data += f'{byte_str} ' formatted_data = formatted_data.strip() # 检查是否已存在该CanID的条目 for index in range(num_top_items): if tree_widget.topLevelItem(index).text(1) == can_id: tree_widget.topLevelItem(index).setText(0, signal_name) tree_widget.topLevelItem(index).setText(1, can_id) tree_widget.topLevelItem(index).setText(2, formatted_data) tree_widget.topLevelItem(index).setText(3, str(signal_data[3])) bfindflag = True break # 如果不存在则创建新条目 if not bfindflag: item = QTreeWidgetItem(tree_widget) item.setText(0, signal_name) item.setText(1, can_id) item.setText(2, formatted_data) item.setText(3, str(signal_data[3])) tree_widget.addTopLevelItem(item) tree_widget.expandAll() tree_widget.show() class Worker(QThread): update_signal = pyqtSignal(int) # 定义一个信号,用于传递更新信息到主线程 def run(self): # 模拟耗时操作 while True: time.sleep(0.3) self.update_signal.emit(1) # 发射信号,传递更新信息 这是canobd文件 现在这段程序需要追一项功能,就是能够识别事件帧(EVENT)和周期帧(CYCLE)反馈到CanFramType,应该怎么修改代码
最新发布
07-23
<think>为了识别事件帧(EVENT)和周期帧(CYCLE),我们需要在CAN帧解析过程中增判断逻辑。事件帧通常是指当某个事件发生时才发送的帧(例如开关门、刹车等),而周期帧则是按照固定时间间隔发送的帧。 我们可以通过以下方式来判断: 1. 周期帧:我们可以通过计算同一个CAN ID的帧之间的时间间隔,如果间隔相对固定(在一定的容差范围内),则认为是周期帧。 2. 事件帧:如果同一个CAN ID的帧出现的时间间隔不固定,或者间隔很长(比如只有事件发生时才有),则认为是事件帧。 但是,在实际应用中,我们可能没有足够的历史数据来立即判断是周期帧还是事件帧。因此,我们可以采用以下策略: - 对于每个新出现的CAN ID,初始标记为未知(UNKNOWN)。 - 当同一个CAN ID出现多次后,计算其时间间隔,如果时间间隔相对稳定(例如在10%的波动范围内),则标记为周期帧(CYCLE)。 - 如果时间间隔变化很大,或者两次出现的时间间隔很长(比如超过1秒)且没有明显的周期性,则标记为事件帧(EVENT)。 然而,考虑到实时显示的需求,我们可能不需要非常精确的判断。我们可以设定一个阈值,比如同一个ID出现3次以上,并且间隔稳定,则认为是周期帧。否则,如果出现次数少(比如小于3次)或者间隔不稳定,则认为是事件帧。 具体修改步骤: 1. 在SerialPort类中增一个字典,用于记录每个CAN ID的历史时间戳(比如最近几次的时间戳),以及帧类型(初始为"UNKNOWN")。 2. 在解析每一帧时,更新该CAN ID的时间戳记录,并计算最近几次的时间间隔,判断其周期性。 3. 根据判断结果,将帧类型设置为"CYCLE"或"EVENT"。 但是注意:由于事件可能很少发生,我们可能需要一个超时机制,将长时间未出现的周期帧重新标记为未知?或者不需要,因为周期帧一旦确定就可以保持。 考虑到实时性要求,我们简化处理: - 对于每个CAN ID,记录上次出现的时间戳和当前计算的周期(如果稳定)。 - 当再次收到该ID的帧时,计算与上一次的时间间隔(delta_t)。 - 如果当前周期为0(即第一次出现间隔),则设置当前周期为delta_t,并标记为未知(UNKNOWN)?或者先不标记,等两次间隔。 - 当有两次间隔后,我们可以计算两次间隔的差值,如果差值在允许范围内,则认为是周期帧,并更新周期值为平均值。否则标记为事件帧。 具体实现: 在SerialPort类中,我们已经有了`last_frame_time`字典(记录每个ID的最后出现时间)和`cycle_dict`(记录每个ID的周期)。我们可以扩展记录,增一个`frame_type_dict`,用于存储每个ID的帧类型(字符串:"CYCLE", "EVENT", "UNKNOWN"),以及一个`interval_count_dict`用于记录间隔计算的次数(用于稳定判断)。 修改`Frame_analoy_process`方法中的处理逻辑: 步骤: a. 在解析每个CAN帧时,获取当前时间戳`current_time`。 b. 如果该ID在`last_frame_time`中存在,则计算时间间隔`delta_t = current_time - last_time`(单位为秒)。 c. 然后根据历史记录判断帧类型: - 如果该ID还没有在`frame_type_dict`中,则初始化为"UNKNOWN"。 - 如果该ID已经出现过至少两次(即已经有一次间隔),则用当前的间隔与之前计算的周期(cycle_dict中)进行比较: 允许波动范围:比如10%以内则认为是周期帧,否则标记为事件帧。 - 如果还没有计算过周期(即只有一次间隔),则将当前间隔作为周期,并继续标记为"UNKNOWN",直到有足够的间隔来判断。 d. 如果该ID是第一次出现(即不在`last_frame_time`中),则初始化`frame_type_dict`为"UNKNOWN",并且没有间隔。 但是,为了简单起见,我们可以这样: - 当同一个ID出现两次后,我们就计算间隔,并标记为周期帧(假设大多数都是周期帧),然后后续如果间隔变化很大(超过阈值),则改为事件帧。 然而,事件帧可能只出现一次,所以我们可能无法在两次内判断。因此,我们可以设定在出现3次后再判断。 由于判断逻辑相对复杂,而且我们的主要目的是实时显示,我们可以简化: - 默认所有的帧都是周期帧(因为大多数是),但当我们发现某个ID的间隔变化很大(比如超过50%的波动)时,就将其标记为事件帧。 具体步骤(在`Frame_analoy_process`中): 1. 获取当前时间戳`current_time = time.time()` 2. 如果`last_frame_time`中存在该CanID: last_time = self.last_frame_time[can_id] delta_t = current_time - last_time # 单位秒 # 更新最后时间 self.last_frame_time[can_id] = current_time # 如果之前已经计算过周期(即cycle_dict中有值) if can_id in self.cycle_dict: last_cycle = self.cycle_dict[can_id] # 计算当前间隔与上次周期的差异率 diff_ratio = abs(delta_t - last_cycle) / last_cycle if diff_ratio > 0.5: # 如果差异超过50%,则认为是事件帧 self.frame_type_dict[can_id] = "EVENT" else: # 更新周期为权平均(或者直接使用当前间隔?) # 这里我们使用权平均:新周期 = 0.7 * 旧周期 + 0.3 * 当前间隔 new_cycle = 0.7 * last_cycle + 0.3 * delta_t self.cycle_dict[can_id] = new_cycle self.frame_type_dict[can_id] = "CYCLE" else: # 第一次计算间隔,设置周期为当前间隔,并标记为周期帧(假设) self.cycle_dict[can_id] = delta_t self.frame_type_dict[can_id] = "CYCLE" # 先标记为周期帧,后续如果波动大再改 3. 如果不存在(即第一次出现),则: self.last_frame_time[can_id] = current_time self.cycle_dict[can_id] = 0 # 周期初始为0 self.frame_type_dict[can_id] = "UNKNOWN" # 第一次出现,未知 4. 然后,在构建CanItemData时,使用`self.frame_type_dict.get(can_id, "UNKNOWN")`作为帧类型。 但是注意:事件帧可能不会周期性出现,所以当事件帧出现时,我们将其标记为事件帧,但后续如果它又周期性出现怎么办?所以我们需要持续监测。 另外,我们也可以考虑在事件帧出现后,如果后续连续几次都以稳定的间隔出现,则将其重新标记为周期帧。 不过,为了简化,我们可以只根据最近两次的间隔来判断,并动态更新帧类型。 此外,我们还需要在`SerialPort`的`__init__`中初始化`frame_type_dict`: self.frame_type_dict = {} # 存储每个CAN ID的帧类型 修改`Frame_analoy_process`中构建`CanItemData`的部分: # 获取帧类型 can_frame_type = self.frame_type_dict.get(CanID, "UNKNOWN") CanItemData = [Cantime, CanID, can_frame_type, Len, CanDataSpace] 同样,在PGN解析部分,我们可能不需要帧类型,因为PGN主要是信号解析。 但是,注意:事件帧和周期帧的区分可能并不绝对,我们的判断逻辑可能会出现错误。因此,我们也可以考虑只根据周期是否稳定来标记,不稳定就标记为事件帧。 由于我们已经在`SerialPort`类中维护了`cycle_dict`,所以我们可以根据周期是否为0来判断是否计算过周期。在显示时,如果周期为0,则显示"UNKNOWN";如果周期非0且稳定,则显示"CYCLE";如果周期非0但不稳定(即被标记为事件帧),则显示"EVENT"。 但是,我们上面已经用`frame_type_dict`记录了类型,所以直接使用即可。 考虑到代码的修改,我们需要在`SerialPort`类的`__init__`中初始化`frame_type_dict`。 另外,在`Frame_analoy_process`中,我们对于每个ID都要更新帧类型,所以上面的逻辑需要嵌入。 但是,注意:我们之前已经有一个`cycle_dict`,现在要增`frame_type_dict`和更新周期值的逻辑。 我们修改`Frame_analoy_process`中关于时间戳和周期处理的部分: current_time = time.time() current_frame_ids = set() # 记录当前帧中已处理的ID for index in range(0, FrameNum): # ... [前面的代码] CanID = ... # 获取CAN ID # 处理时间间隔和帧类型 if CanID in self.last_frame_time: last_time = self.last_frame_time[CanID] delta_t = current_time - last_time # 单位:秒 # 更新最后出现时间 self.last_frame_time[CanID] = current_time # 如果之前有记录周期 if CanID in self.cycle_dict: last_cycle = self.cycle_dict[CanID] # 计算差异率 diff_ratio = abs(delta_t - last_cycle) / last_cycle if last_cycle > 0 else 1.0 if diff_ratio > 0.5: # 超过50%的差异,认为是事件帧 self.frame_type_dict[CanID] = "EVENT" else: # 更新周期:使用权平均,平滑周期值 new_cycle = 0.7 * last_cycle + 0.3 * delta_t self.cycle_dict[CanID] = new_cycle self.frame_type_dict[CanID] = "CYCLE" else: # 第一次计算间隔,设置周期为当前间隔,并标记为周期帧(假设) self.cycle_dict[CanID] = delta_t self.frame_type_dict[CanID] = "CYCLE" else: # 第一次出现 self.last_frame_time[CanID] = current_time self.cycle_dict[CanID] = 0 # 周期初始为0,表示还未计算 self.frame_type_dict[CanID] = "UNKNOWN" # 获取帧类型 can_frame_type = self.frame_type_dict.get(CanID, "UNKNOWN") # 然后构建CanItemData CanItemData = [Cantime, CanID, can_frame_type, Len, CanDataSpace] # ... [后面的代码] 注意:这里我们假设在同一个循环中,同一个ID只出现一次。因为CAN报文是按帧处理的,所以一个ID在一帧数据中可能只出现一次。 另外,由于我们的时间间隔计算是基于上一次的时间,所以每次更新。 但是,注意:我们这里计算的是相邻两帧的时间间隔,而不是多个周期的平均。所以,当出现抖动时,可能会误判。 我们可以考虑记录多个间隔,然后计算平均值和方差,但这样会增复杂度。目前先按相邻两次的间隔判断。 修改后,在显示表格的“帧类型”列就会显示为"CYCLE"、"EVENT"或"UNKNOWN"。 接下来,我们需要在UI表格中显示这个帧类型。在`Ui_MainWindow`类的`update_table_row`方法中,我们已经将`item_data[2]`(即帧类型)显示在第二列(列索引2)。所以不需要额外修改,因为我们在构建`CanItemData`时已经将帧类型放在了第三个元素(索引2)。 但是,注意:在`CanItemData`中,索引2原本是写死的"Cycle",现在我们改为动态的`can_frame_type`。 因此,我们只需要修改`SerialPort`类中的`Frame_analoy_process`方法,将原来的写死改为动态获取帧类型。 另外,在`CanOBDItemList`和`filteredCanOBDItemList`中存储的每个条目的第三个元素就是帧类型。 最后,注意:在初始化`SerialPort`类时,记得在`__init__`方法中初始化`frame_type_dict`。 修改`SerialPort`类的`__init__`方法: self.frame_type_dict = {} # 存储每个CAN ID的帧类型(CYCLE, EVENT, UNKNOWN) 同时,在`Frame_analoy_process`方法中,我们修改构建`CanItemData`的部分: # 原来:CanFramType = "Cycle" # 这里原来是写死的 # 改为: can_frame_type = self.frame_type_dict.get(CanID, "UNKNOWN") CanItemData = [Cantime, CanID, can_frame_type, Len, CanDataSpace] 这样,我们就完成了帧类型的动态判断。 但是,我们还需要注意:当同一个ID在短时间内连续出现(比如事件帧连续触发),我们也会计算间隔,如果间隔很小且稳定,可能会被误判为周期帧。不过,事件帧连续出现的情况较少,而且事件帧的间隔通常不固定。所以暂时按此逻辑。 另外,我们也可以增一个条件:如果同一个ID的帧间隔非常小(比如小于5ms),则认为是周期帧,因为事件帧通常不会这么频繁。但具体应用场景不同,这里我们保持简单。 由于我们的周期计算单位是秒,而CAN周期帧通常在几十毫秒到几秒,事件帧间隔可能很长。所以我们的判断阈值(50%)可能对于周期帧来说过于敏感。我们可以考虑将阈值放宽,比如100%的波动(即当前间隔在0.5倍到2倍上次周期之间)都算周期帧,超出则算事件帧。 修改判断逻辑: diff_ratio = abs(delta_t - last_cycle) / last_cycle if diff_ratio > 1.0: # 超过100%的差异,认为是事件帧 self.frame_type_dict[CanID] = "EVENT" else: # 更新周期 new_cycle = 0.7 * last_cycle + 0.3 * delta_t self.cycle_dict[CanID] = new_cycle self.frame_type_dict[CanID] = "CYCLE" 这样,容忍度更高,避免因为小的抖动而误判。 另外,对于周期为0的情况(即第一次计算间隔),我们直接设置为周期帧,因为此时还没有历史周期,无法判断。 最后,我们还需要考虑一种情况:如果一个周期帧突然停止发送,然后又重新出现,那么间隔会很大,会被误判为事件帧。不过,重新出现的第一帧我们无法避免误判,但后续帧会重新计算周期,并可能重新标记为周期帧。 综上,我们按照上述思路修改代码。 注意:由于我们修改了`Frame_analoy_process`方法,请确保在循环内部每个ID都进行了上述处理。 由于代码较长,下面只给出关键修改部分的代码片段: 1. 在`SerialPort`类的`__init__`方法中增: self.frame_type_dict = {} # 存储每个CAN ID的帧类型 2. 在`Frame_analoy_process`方法中,修改时间戳和周期处理的部分,以及帧类型的赋值。 具体修改如下(在`Frame_analoy_process`方法中,处理每个CAN ID的循环内): current_time = time.time() # ... [前面的代码] for index in range(0,FrameNum): # ... [获取CanID等] # 处理时间间隔和帧类型 if CanID in self.last_frame_time: last_time = self.last_frame_time[CanID] delta_t = current_time - last_time # 单位:秒 # 更新最后出现时间 self.last_frame_time[CanID] = current_time if CanID in self.cycle_dict: last_cycle = self.cycle_dict[CanID] if last_cycle > 0: # 避免除以0 diff_ratio = abs(delta_t - last_cycle) / last_cycle if diff_ratio > 1.0: # 差异超过100%,标记为事件帧 self.frame_type_dict[CanID] = "EVENT" else: # 使用权平均更新周期 new_cycle = 0.7 * last_cycle + 0.3 * delta_t self.cycle_dict[CanID] = new_cycle self.frame_type_dict[CanID] = "CYCLE" else: # 上一次周期为0(第一次出现后),现在有间隔了,设置为周期帧 self.cycle_dict[CanID] = delta_t self.frame_type_dict[CanID] = "CYCLE" else: # 理论上不会进入这里,因为cycle_dict在下面会初始化,但为了安全 self.cycle_dict[CanID] = delta_t self.frame_type_dict[CanID] = "CYCLE" else: # 第一次出现,初始化 self.last_frame_time[CanID] = current_time self.cycle_dict[CanID] = 0 # 初始周期为0 self.frame_type_dict[CanID] = "UNKNOWN" # 获取帧类型 can_frame_type = self.frame_type_dict.get(CanID, "UNKNOWN") # 然后构建CanItemData CanItemData = [Cantime, CanID, can_frame_type, Len, CanDataSpace] # ... [后面的代码] 注意:在`Frame_analoy_process`方法中,我们之前已经有一个`current_frame_ids`,但这里没有使用,可以保留。 这样修改后,帧类型就会动态更新了。 但是,注意:在`Frame_analoy_process`方法中,我们之前已经有一个`CanFramType = "Cycle"`的写死赋值,现在改为使用`can_frame_type`。 另外,在`SerialPort`类中,我们之前已经定义了`last_frame_time`和`cycle_dict`,现在只需增`frame_type_dict`。 由于修改较多,请仔细测试。 由于我们无法在此运行测试,请根据实际数据调整阈值和逻辑。 下面给出修改后的`Frame_analoy_process`方法中相关部分的代码(仅关键部分):</think>要识别事件帧(EVENT)和周期帧(CYCLE),我们需要修改代码以区分这两种帧类型。以下是需要修改的关键部分: ### 修改方案: 1. **在 `SerialPort` 类中添帧类型判断逻辑** 2. **修改 `Frame_analoy_process` 方法中的帧类型赋值** 3. **在 `CanInfShow_Item` 类中支持帧类型字段** ### 具体代码修改: #### 1. 在 `SerialPort` 类中添帧类型判断逻辑 ```python class SerialPort: def __init__(self, port, baudrate): # ... 已有代码 ... self.frame_type_dict = {} # 新增:存储每个CAN ID的帧类型 # ... 已有代码 ... def determine_frame_type(self, can_id): """ 判断帧类型:周期帧(CYCLE)或事件帧(EVENT) 基于历史间隔的稳定性判断 """ if can_id not in self.cycle_dict or self.cycle_dict[can_id] == 0: return "UNKNOWN" # 初始状态 # 获取最近几次间隔值(示例:使用最后3次间隔) recent_intervals = self.last_intervals.get(can_id, []) if len(recent_intervals) < 2: return "CYCLE" # 默认周期帧 # 计算间隔稳定性(标准差) mean = sum(recent_intervals) / len(recent_intervals) variance = sum((x - mean) ** 2 for x in recent_intervals) / len(recent_intervals) std_dev = variance ** 0.5 # 判断标准:如果间隔变化超过平均值的30%,视为事件帧 if std_dev / mean > 0.3: return "EVENT" else: return "CYCLE" # 在 __init__ 中初始化 last_intervals self.last_intervals = {} # 存储每个ID的最后几次间隔 ``` #### 2. 修改 `Frame_analoy_process` 方法 ```python def Frame_analoy_process(self, Framedata): # ... 已有代码 ... for index in range(0, FrameNum): # ... 已有代码 ... # 替换原有的固定帧类型 # 原代码: CanFramType = "Cycle" # 动态判断帧类型 frame_type = self.determine_frame_type(CanID) # 更新间隔记录 if CanID in self.last_frame_time: delta = current_time - self.last_frame_time[CanID] if CanID not in self.last_intervals: self.last_intervals[CanID] = [] self.last_intervals[CanID].append(delta) # 只保留最近的3次记录 if len(self.last_intervals[CanID]) > 3: self.last_intervals[CanID].pop(0) CanItemData = [Cantime, CanID, frame_type, Len, CanDataSpace] # ... 后续代码保持不变 ... ``` #### 3. 在 `CanInfShow_Item` 类中支持帧类型字段 ```python class CanInfShow_Item: def __init__(self, SystemCycle, CanID, CanFrameType, Len, CanDataInf): self.SystemCycle = SystemCycle self.CanID = CanID self.CanFrameType = CanFrameType # 修改为支持动态帧类型 self.CanDataLen = Len self.CanData = CanDataInf ``` ### 修改说明: 1. **帧类型判断逻辑**: - 使用 `last_intervals` 字典记录每个CAN ID的最后几次间隔时间 - 通过计算间隔的标准差判断稳定性 - 如果间隔变化超过平均值的30%,标记为`EVENT`,否则标记为`CYCLE` 2. **动态更新**: - 每次收到帧时更新间隔记录 - 只保留最近的3次间隔用于计算稳定性 - 初始状态或数据不足时返回`UNKNOWN` 3. **数据结构修改**: - `CanInfShow_Item` 类现在支持动态帧类型 - UI显示层会自动使用新的帧类型字段 ### 相关问题:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值