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_frame_ids = set() # 记录当前帧中已处理的ID for index in range(0,FrameNum): # 时间戳 current_time = time.time() 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 filtered_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 "18FEBD00" in self.cycle_dict: print(self.cycle_dict["18FEBD00"]) 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()
这段程序在测试计算固定CANID周期,经过测试接收到了全部的数据,但是显示的时间间隔加起来却与总发送时间不符合(相差2倍)。找出计算的逻辑问题
最新发布