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_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.count=0
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):
# # 接收数据
# if self.ser.isOpen():
# data = self.ser.read(self.ser.inWaiting())
# if data: # 有数据时更新时间戳
# self.last_data_time = time.time()
# return data
def recv(self, chunk_size=1024):
if self.ser.isOpen():
# 每次最多读取1024字节
data = self.ser.read(min(self.ser.inWaiting(), chunk_size))
if data:
self.last_data_time = time.time()
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)
# 计算总长度(从第2字节开始)
length_high = self.current_frame[2]
length_low = byte
self.expected_length = (length_high << 8) | length_low
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() # 获取当前精确时间戳
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 == "18FEBD00":
# self.count=self.count+1
# print(CanDataSpace,self.count)
# ✅ 只有在白名单内的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 len(CanPGNItemList):
CanPGNItemList.pop(0)
CanPGNItemList.insert(0, SignalItemData)
else:
Listpos = self.find_in_2d_list(CanPGNItemList, CanID)
if Listpos is not None:
CanPGNItemList[Listpos[0]] = SignalItemData
else:
CanPGNItemList.append(SignalItemData)
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 filtered_cycle in filtered_cycles:
if filtered_cycle == 20:
if cycle_ms < 50:
is_filtered = True
break
elif filtered_cycle == 50:
if 50 <= cycle_ms <= 100:
is_filtered = True
break
elif filtered_cycle == 100:
if 100 <= cycle_ms <= 190:
is_filtered = True
break
elif filtered_cycle == 200:
if 200 <= cycle_ms <= 400:
is_filtered = True
break
elif filtered_cycle == 500:
if 500 <= cycle_ms <= 600:
is_filtered = True
break
elif filtered_cycle == 1000:
if 600 <= cycle_ms <= 1000:
is_filtered = True
break
# 根据过滤状态更新相应的列表
if is_filtered:
# 更新到filteredCanOBDItemList
Listpos = self.find_in_2d_list(filteredCanOBDItemList, CanID)
if Listpos is not None:
filteredCanOBDItemList[Listpos[0]] = CanItemData
self.data_updated_ids.add(CanID)
else:
filteredCanOBDItemList.append(CanItemData)
self.new_added_ids.add(CanID)
else:
# 更新到CanOBDItemList
Listpos = self.find_in_2d_list(CanOBDItemList, CanID)
if Listpos is not None:
CanOBDItemList[Listpos[0]] = CanItemData
self.data_updated_ids.add(CanID)
else:
CanOBDItemList.append(CanItemData)
self.new_added_ids.add(CanID)
self.last_data_time = time.time() # 解析到有效帧时更新时间
if "0CF00400" in self.cycle_dict:
print(self.cycle_dict["0CF00400"])
def find_in_2d_list(self,matrix, target):
for i, row in enumerate(matrix):
if any(x == target for x in row):
return (i, row.index(target)) # 使用row.index()找到具体列的索引
return 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
这是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.5)
self.update_signal.emit(1) # 发射信号,传递更新信息
这是canobd文件,现在这个程序需要追加一项功能,分辨事件帧和周期帧,周期帧定时发送,事件帧的出现则没有规则,现在要求制定策略识别,现有的思路是每个帧id定下一定的接收次数,每一次数内都记录当前算出的周期值,如果指定次数达到,开始判断周期的波动,如果超出误差,则定为事件帧。注意20ms以下周期的信号测量会存在10ms的误差,接收次数不能太多,因为存在10s的周期信号
最新发布