import sys
import logging
import time
import datetime
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import Qt, QThread, pyqtSignal, pyqtSlot
import CanBusToolsApi
class CanWorker(QThread):
"""CAN总线工作线程"""
log_signal = pyqtSignal(str)
data_received = pyqtSignal(list)
test_result = pyqtSignal(str, str) # 测试名称, 结果
operation_finished = pyqtSignal(str, bool) # 操作名称, 成功状态
# 添加测试信号
run_test_signal = pyqtSignal(str, int, list, int)
def __init__(self, parent=None):
super().__init__(parent)
self.can_handle = None
self.running = False
self.device_name = "PCAN_USB_PRO_FD"
self.device_index = 0x00
self.can_channel = 0x01
self.can_bus_type = "CANFD"
self.baud_rate = 500000
self.baud_rate_fd = 2000000
# 连接测试信号
self.run_test_signal.connect(self.run_sim_test_async)
def init_can_device(self):
"""初始化CAN设备"""
try:
self.log_signal.emit("正在初始化CAN设备...")
self.can_handle = CanBusToolsApi.CanToolsUnifiedInterface(
DeviceName=self.device_name,
DeviceIndex=self.device_index,
FrameType="STANDARD"
)
# 打开CAN通道
result = self.can_handle.CanOpen(
BusType=self.can_bus_type,
Channel=self.can_channel,
BaudRate=self.baud_rate,
BaudRateFD=self.baud_rate_fd,
SocketCfg=["192.168.0.178", 8000, 0x00]
)
if result == 0x00:
self.log_signal.emit("CAN设备初始化成功")
return True
else:
self.log_signal.emit(f"CAN设备初始化失败,错误代码: 0x{result:02X}")
return False
except Exception as e:
self.log_signal.emit(f"初始化CAN设备时发生错误: {str(e)}")
return False
def set_filter(self, from_id, to_id):
"""设置滤波器"""
try:
result = self.can_handle.CanFilterMessages(
Channel=self.can_channel,
FromId=from_id,
ToId=to_id,
ClearFlag=False
)
if result == 0x00:
self.log_signal.emit(f"滤波器设置成功: 0x{from_id:03X} - 0x{to_id:03X}")
else:
self.log_signal.emit(f"滤波器设置失败: 0x{result:02X}")
return result == 0x00
except Exception as e:
self.log_signal.emit(f"设置滤波器时发生错误: {str(e)}")
return False
def clear_buffer(self):
"""清空缓冲区"""
try:
result = self.can_handle.CanClearBuffer(Channel=self.can_channel)
if result == 0x00:
self.log_signal.emit("缓冲区清空成功")
else:
self.log_signal.emit(f"缓冲区清空失败: 0x{result:02X}")
return result == 0x00
except Exception as e:
self.log_signal.emit(f"清空缓冲区时发生错误: {str(e)}")
return False
def send_message(self, can_id, data, data_len, read_response=True, response_timeout=0.1):
"""发送CAN消息并可选读取响应"""
try:
result = self.can_handle.CanWrite(
Id=can_id,
DataList=data,
DataLen=data_len,
BusType=self.can_bus_type,
Channel=self.can_channel,
SelfTxRx=True
)
if result == 0x00:
data_hex = ' '.join([f'{x:02X}' for x in data])
self.log_signal.emit(f"发送成功: ID=0x{can_id:03X}, Data={data_hex}, Len={data_len}")
# 如果需要读取响应
if read_response:
time.sleep(response_timeout) # 等待响应
recv_list = self.read_message()
if recv_list:
# self.log_signal.emit(f"收到 {len(recv_list)} 条响应:")
for i, recv_data in enumerate(recv_list):
if len(recv_data) >= 3:
recv_id = recv_data[0]
recv_len = recv_data[1]
recv_data_bytes = recv_data[2]
recv_data_hex = ' '.join([f'{x:02X}' for x in recv_data_bytes])
self.log_signal.emit(
f"响应报文: ID=0x{recv_id:03X}, Len={recv_len}, Data={recv_data_hex}")
else:
self.log_signal.emit(f"响应报文: 无效格式 {recv_data}")
return True, recv_list # 返回成功状态和响应列表
else:
self.log_signal.emit("未收到响应数据")
return True, [] # 发送成功但无响应
else:
return True, [] # 发送成功但不读取响应
else:
self.log_signal.emit(f"发送失败: 0x{result:02X}")
return False, []
except Exception as e:
self.log_signal.emit(f"发送消息时发生错误: {str(e)}")
return False, []
def read_message(self):
"""读取CAN消息"""
try:
if self.can_handle:
return self.can_handle.CanRead(
BusType=self.can_bus_type,
Channel=self.can_channel
)
return []
except Exception as e:
self.log_signal.emit(f"读取数据时发生错误: {str(e)}")
return []
def log_can_messages_hex(self, recv_list):
"""将CAN消息列表转换为16进制格式的日志字符串"""
if not recv_list:
return "无消息"
log_lines = []
for i, msg in enumerate(recv_list):
if len(msg) >= 3: # 至少包含 can_id, data_length, data_bytes
can_id = msg[0]
data_length = msg[1]
data_bytes = msg[2] # 数据在第三个位置
# 转换为16进制
can_id_hex = f"0x{can_id:03X}"
data_hex = ' '.join([f'{x:02X}' for x in data_bytes])
log_lines.append(f"{i + 1}. ID: {can_id_hex}, Len: {data_length}, Data: {data_hex}")
else:
log_lines.append(f"{i + 1}. 无效消息格式: {msg}")
return "\n".join(log_lines) if log_lines else "无效消息格式"
def enter_eol_mode(self):
"""进入EOL模式"""
try:
self.clear_buffer()
send_data = [0x72E, [0x02, 0x10, 0x03], 0x08]
self.send_message(send_data[0], send_data[1], send_data[2])
time.sleep(0.1)
send_data = [0x72E, [0x02, 0x10, 0x60], 0x08]
self.send_message(send_data[0], send_data[1], send_data[2])
time.sleep(0.1)
return True
except Exception as e:
self.log_signal.emit(f"进入EOL模式时发生错误: {str(e)}")
return False
def eol_read(self, can_id, cmd, dlc):
"""执行EOL读取,返回CAN响应数据列表"""
try:
send_data = [can_id, cmd, dlc]
SendStatus = self.send_message(send_data[0], send_data[1], send_data[2])
time.sleep(0.1)
# 打印接收到的报文(16进制格式)
if SendStatus[0] and SendStatus[1]: # 如果发送成功且有响应
hex_log = self.log_can_messages_hex(SendStatus[1])
self.log_signal.emit(f"收到响应:\n{hex_log}")
# 检查响应数据
if SendStatus[1] and len(SendStatus[1]) > 0:
response_data = SendStatus[1][0][2] # 第一个响应的数据部分
# 转换为16进制进行比较
response_hex = [f'{x:02X}' for x in response_data[:8]] # 只比较前8个字节
expected_hex = ['04', '71', '01', 'C0', 'CD', 'CC', 'CC', 'CC']
if response_hex == expected_hex:
self.log_signal.emit("✓ 响应匹配: 04 71 01 C0 CD CC CC CC")
RID1 = send_data[1][4] # 0xC0 -> 192
RID2 = send_data[1][5] # 0xCD -> 205
# 发送31 03报文
time.sleep(1)
SendData2 = [0x72E, [0x04, 0x31, 0x03, RID1, RID2], 0x08]
SendStatus2 = self.send_message(SendData2[0], SendData2[1], SendData2[2])
time.sleep(0.1)
# 打印第二次响应的16进制
if SendStatus2[0] and SendStatus2[1]:
hex_log2 = self.log_can_messages_hex(SendStatus2[1])
self.log_signal.emit(f"第二次响应:\n{hex_log2}")
# 返回实际的CAN响应数据列表
return SendStatus2[1]
else:
self.log_signal.emit(
f"✗ 响应不匹配。期望: {' '.join(expected_hex)},实际: {' '.join(response_hex)}")
# 如果没有收到预期的响应,返回空列表
return []
except Exception as e:
self.log_signal.emit(f"EOL读取时发生错误: {str(e)}")
return []
def dec_list_to_ascii(self, dec_list, replace_char='?'):
"""
将10进制列表转换为ASCII字符串
参数:
dec_list: 10进制数字列表
replace_char: 对于非ASCII字符的替换字符
返回:
ASCII字符串
"""
ascii_chars = []
for num in dec_list:
if 0 <= num <= 127: # ASCII范围
ascii_chars.append(chr(num))
else:
ascii_chars.append(replace_char)
return ''.join(ascii_chars)
def run_sim_test(self, test_name, can_id, cmd, dlc, expected_data=None, data_range=None):
"""执行SIM卡测试"""
try:
self.log_signal.emit(f"开始执行 {test_name}...")
# 进入EOL模式
if not self.enter_eol_mode():
self.test_result.emit(test_name, "FAIL")
return False
# 执行读取,现在result是CAN响应数据列表
result = self.eol_read(can_id, cmd, dlc)
if not result or len(result) == 0:
self.test_result.emit(test_name, "FAIL")
return False
# 获取第一个响应的数据部分
if len(result) > 0 and len(result[0]) >= 3:
response_data = result[0][2] # 数据在第三个位置
else:
self.test_result.emit(test_name, "FAIL")
return False
if test_name == "SIM卡状态检测":
print(response_data)
sim_state = response_data[9] if len(response_data) > 6 else -1
status = "PASS" if sim_state == 2 else "FAIL"
self.log_signal.emit(f"SIM卡状态: {sim_state} - {status}")
self.test_result.emit(test_name, status)
return status == "PASS"
elif test_name == "IMEI读取":
# IMEI数据从第9个字节开始(索引8)
sim_imei_rec = response_data[9:24] if len(response_data) > 23 else []
expected = [56, 54, 51, 54] # 8636
imei = self.dec_list_to_ascii(sim_imei_rec)
status = "PASS" if sim_imei_rec[:4] == expected else "FAIL"
self.log_signal.emit(f"IMEI: {imei} - {status}")
self.test_result.emit(test_name, status)
return status == "PASS"
elif test_name == "ICCID读取":
sim_iccid_rec = response_data[9:24] if len(response_data) > 28 else []
expected = [56, 57, 56, 53] # 8985
iccid = self.dec_list_to_ascii(sim_iccid_rec)
status = "PASS" if sim_iccid_rec[:4] == expected else "FAIL"
self.log_signal.emit(f"ICCID: {iccid} - {status}")
self.test_result.emit(test_name, status)
return status == "PASS"
elif test_name == "IMSI读取":
sim_imsi_rec = response_data[9:24] if len(response_data) > 23 else []
expected = [52, 53, 52, 51] # 4543
imsi = self.dec_list_to_ascii(sim_imsi_rec)
status = "PASS" if sim_imsi_rec[:4] == expected else "FAIL"
self.log_signal.emit(f"IMSI: {imsi} - {status}")
self.test_result.emit(test_name, status)
return status == "PASS"
elif test_name == "SIM卡激活状态检测":
sim_network_state = response_data[9] if len(response_data) > 6 else -1
status = "PASS" if sim_network_state == 2 else "FAIL"
self.log_signal.emit(f"SIM卡激活状态: {sim_network_state} - {status}")
self.test_result.emit(test_name, status)
return status == "PASS"
self.test_result.emit(test_name, "FAIL")
return False
except Exception as e:
self.log_signal.emit(f"执行 {test_name} 时发生错误: {str(e)}")
self.test_result.emit(test_name, "ERROR")
return False
def close_device(self):
"""关闭CAN设备"""
try:
if self.can_handle:
result = self.can_handle.CanClose()
if result == 0x00:
self.log_signal.emit("CAN设备关闭成功")
else:
self.log_signal.emit(f"CAN设备关闭失败: 0x{result:02X}")
except Exception as e:
self.log_signal.emit(f"关闭CAN设备时发生错误: {str(e)}")
@pyqtSlot()
def init_can_device_async(self):
"""异步初始化CAN设备"""
success = self.init_can_device()
self.operation_finished.emit("init", success)
@pyqtSlot(int, int)
def set_filter_async(self, from_id, to_id):
"""异步设置滤波器"""
success = self.set_filter(from_id, to_id)
self.operation_finished.emit("filter", success)
@pyqtSlot()
def clear_buffer_async(self):
"""异步清空缓冲区"""
success = self.clear_buffer()
self.operation_finished.emit("clear", success)
@pyqtSlot(str, int, list, int)
def run_sim_test_async(self, test_name, can_id, cmd, dlc):
"""异步执行SIM卡测试"""
self.run_sim_test(test_name, can_id, cmd, dlc)
class SIMTester(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.can_worker = CanWorker()
self.test_results = {}
self.init_ui()
self.connect_signals()
# 启动工作线程
self.can_worker.start()
def init_ui(self):
"""初始化用户界面"""
self.setWindowTitle("SIM卡测试工具 - CAN总线UDS诊断")
self.setGeometry(100, 100, 1000, 700)
# 中央部件
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
# 主布局
main_layout = QtWidgets.QVBoxLayout()
central_widget.setLayout(main_layout)
# 控制面板
control_panel = QtWidgets.QGroupBox("设备控制")
control_layout = QtWidgets.QHBoxLayout()
self.init_btn = QtWidgets.QPushButton("初始化CAN设备")
self.filter_btn = QtWidgets.QPushButton("设置滤波器")
self.clear_btn = QtWidgets.QPushButton("清空缓冲区")
self.filter_btn.setEnabled(False)
self.clear_btn.setEnabled(False)
control_layout.addWidget(self.init_btn)
control_layout.addWidget(self.filter_btn)
control_layout.addWidget(self.clear_btn)
control_layout.addStretch()
control_panel.setLayout(control_layout)
# 测试面板
test_panel = QtWidgets.QGroupBox("SIM卡测试")
test_layout = QtWidgets.QGridLayout()
# 测试按钮
tests = [
("SIM卡状态检测", 0x72E, [0x00, 0x3E, 0x31, 0x01, 0xC0, 0xCD, 0x01, 0x01], 0x62),
("IMEI读取", 0x72E, [0x00, 0x3E, 0x31, 0x01, 0xC0, 0xCD, 0x01, 0x02], 0x62),
("ICCID读取", 0x72E, [0x00, 0x3E, 0x31, 0x01, 0xC0, 0xCD, 0x01, 0x03], 0x62),
("IMSI读取", 0x72E, [0x00, 0x3E, 0x31, 0x01, 0xC0, 0xCD, 0x01, 0x04], 0x62),
("SIM卡激活状态检测", 0x72E, [0x00, 0x3E, 0x31, 0x01, 0xC0, 0xCD, 0x01, 0x05], 0x62)
]
self.test_buttons = []
for i, (test_name, can_id, cmd, dlc) in enumerate(tests):
btn = QtWidgets.QPushButton(test_name)
btn.setProperty("test_params", (test_name, can_id, cmd, dlc))
btn.setEnabled(False)
self.test_buttons.append(btn)
test_layout.addWidget(btn, i // 2, i % 2)
# 批量测试按钮
self.batch_test_btn = QtWidgets.QPushButton("执行全部测试")
self.batch_test_btn.setEnabled(False)
test_layout.addWidget(self.batch_test_btn, 3, 0, 1, 2)
test_panel.setLayout(test_layout)
# 结果显示面板
result_panel = QtWidgets.QGroupBox("测试结果")
result_layout = QtWidgets.QVBoxLayout()
self.result_table = QtWidgets.QTableWidget()
self.result_table.setColumnCount(3)
self.result_table.setHorizontalHeaderLabels(["测试项目", "结果", "时间"])
self.result_table.horizontalHeader().setStretchLastSection(True)
result_layout.addWidget(self.result_table)
result_panel.setLayout(result_layout)
# 日志面板
log_panel = QtWidgets.QGroupBox("日志")
log_layout = QtWidgets.QVBoxLayout()
self.log_text = QtWidgets.QTextEdit()
self.log_text.setReadOnly(True)
self.log_text.setMaximumHeight(150)
log_layout.addWidget(self.log_text)
log_panel.setLayout(log_layout)
# 添加到主布局
main_layout.addWidget(control_panel)
main_layout.addWidget(test_panel)
main_layout.addWidget(result_panel)
main_layout.addWidget(log_panel)
# 设置样式
self.apply_styles()
def apply_styles(self):
"""应用样式"""
self.setStyleSheet("""
QMainWindow {
background-color: #f0f0f0;
}
QGroupBox {
font-weight: bold;
border: 2px solid #cccccc;
border-radius: 5px;
margin-top: 1ex;
padding-top: 10px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 5px 0 5px;
}
QPushButton {
background-color: #4CAF50;
border: none;
color: white;
padding: 8px 16px;
text-align: center;
text-decoration: none;
font-size: 14px;
margin: 4px 2px;
border-radius: 4px;
}
QPushButton:hover {
background-color: #45a049;
}
QPushButton:disabled {
background-color: #cccccc;
}
QTextEdit {
background-color: white;
border: 1px solid #cccccc;
border-radius: 3px;
}
QTableWidget {
background-color: white;
border: 1px solid #cccccc;
border-radius: 3px;
gridline-color: #cccccc;
}
""")
def connect_signals(self):
"""连接信号和槽"""
self.init_btn.clicked.connect(self.init_can_device)
self.filter_btn.clicked.connect(self.set_filter)
self.clear_btn.clicked.connect(self.clear_buffer)
self.can_worker.log_signal.connect(self.add_log)
self.can_worker.test_result.connect(self.update_test_result)
self.can_worker.operation_finished.connect(self.handle_operation_result)
# 连接测试按钮信号
for button in self.test_buttons:
test_name, can_id, cmd, dlc = button.property("test_params")
button.clicked.connect(
lambda checked, tn=test_name, ci=can_id, cm=cmd, dl=dlc:
self.run_single_test(tn, ci, cm, dl)
)
self.batch_test_btn.clicked.connect(self.run_all_tests)
def init_can_device(self):
"""初始化CAN设备"""
self.init_btn.setEnabled(False)
self.add_log("正在初始化CAN设备...")
self.can_worker.init_can_device_async()
def set_filter(self):
"""设置滤波器"""
self.filter_btn.setEnabled(False)
self.can_worker.set_filter_async(0x700, 0x7FF)
def clear_buffer(self):
"""清空缓冲区"""
self.clear_btn.setEnabled(False)
self.can_worker.clear_buffer_async()
def run_single_test(self, test_name, can_id, cmd, dlc):
"""运行单个测试"""
self.can_worker.run_test_signal.emit(test_name, can_id, cmd, dlc)
def run_all_tests(self):
"""运行所有测试"""
self.batch_test_btn.setEnabled(False)
self.add_log("开始执行全部测试...")
# 使用QTimer来顺序执行测试,避免阻塞
self.current_test_index = 0
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.execute_next_test)
self.timer.start(500) # 500ms后开始第一个测试
def execute_next_test(self):
"""执行下一个测试"""
if self.current_test_index >= len(self.test_buttons):
self.timer.stop()
self.batch_test_btn.setEnabled(True)
self.add_log("全部测试完成")
return
button = self.test_buttons[self.current_test_index]
test_name, can_id, cmd, dlc = button.property("test_params")
self.add_log(f"开始执行: {test_name}")
self.can_worker.run_test_signal.emit(test_name, can_id, cmd, dlc)
self.current_test_index += 1
def handle_operation_result(self, operation, success):
"""处理操作结果"""
if operation == "init":
self.init_btn.setEnabled(True)
if success:
self.filter_btn.setEnabled(True)
self.clear_btn.setEnabled(True)
for btn in self.test_buttons:
btn.setEnabled(True)
self.batch_test_btn.setEnabled(True)
self.add_log("CAN设备初始化成功,所有功能已启用")
else:
self.add_log("CAN设备初始化失败")
elif operation == "filter":
self.filter_btn.setEnabled(True)
if success:
self.add_log("滤波器设置成功")
else:
self.add_log("滤波器设置失败")
elif operation == "clear":
self.clear_btn.setEnabled(True)
if success:
self.add_log("缓冲区清空成功")
else:
self.add_log("缓冲区清空失败")
def update_test_result(self, test_name, result):
"""更新测试结果"""
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
# 更新结果字典
self.test_results[test_name] = (result, timestamp)
# 更新表格
self.result_table.setRowCount(len(self.test_results))
for i, (name, (result, time_str)) in enumerate(self.test_results.items()):
self.result_table.setItem(i, 0, QtWidgets.QTableWidgetItem(name))
self.result_table.setItem(i, 1, QtWidgets.QTableWidgetItem(result))
self.result_table.setItem(i, 2, QtWidgets.QTableWidgetItem(time_str))
# 设置颜色
if result == "PASS":
self.result_table.item(i, 1).setBackground(QtGui.QColor(144, 238, 144))
elif result == "FAIL":
self.result_table.item(i, 1).setBackground(QtGui.QColor(255, 99, 71))
else:
self.result_table.item(i, 1).setBackground(QtGui.QColor(255, 165, 0))
self.result_table.resizeColumnsToContents()
self.result_table.scrollToBottom()
def add_log(self, message):
"""添加日志消息"""
timestamp = datetime.datetime.now().strftime("%H:%M:%S.%f")[:-3]
log_message = f"[{timestamp}] {message}"
self.log_text.append(log_message)
# 自动滚动到底部
self.log_text.verticalScrollBar().setValue(
self.log_text.verticalScrollBar().maximum()
)
def closeEvent(self, event):
"""关闭事件处理"""
self.can_worker.running = False
self.can_worker.quit()
self.can_worker.wait(2000) # 等待2秒线程结束
self.can_worker.close_device()
event.accept()
def main():
app = QtWidgets.QApplication(sys.argv)
app.setStyle('Fusion')
# 设置应用程序信息
app.setApplicationName("测试上位机")
app.setApplicationVersion("1.0.0")
window = SIMTester()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()在这份代码中增加以上提到的功能