解决 Out of range value adjusted for column 'ID' at row 1

升级MySQL至5.0.17后,执行特定INSERT语句时遇到错误#1264。原因是新版本增强了字段值的严格检查。解决方法涉及修改my.ini文件中的sql-mode设置。

MySQL升级到5.0.17后,在执行sql语句
INSERT INTO `news` (`ID`, `Title`, `Content`) VALUES ('', '标题', '正文');
时出现错误:
#1264 - Out of range value adjusted for column 'ID' at row 1
原因:
新版本的MySQL对字段的严格检查。
解决方法:
修改my.ini,将
sql-mode="STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
改为
sql-mode="NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"。
重新启动MySQL。
以后写sql语句时,类型和值最好严格一些。

import os from docx import Document from openpyxl import Workbook from openpyxl.styles import Alignment from openpyxl.utils import get_column_letter def get_merged_regions(table): """获取表格所有合并区域信息""" merged = [] for merge in table._tbl.xpath('.//w:gridSpan | .//w:vMerge'): cell = merge.getparent().getparent() row_idx = int(cell.xpath('count(ancestor::w:tr/preceding-sibling::w:tr)')) col_idx = int(cell.xpath('count(preceding-sibling::w:tc)')) # 处理水平合并 if 'gridSpan' in merge.tag: colspan = int(merge.get('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}val', 1)) merged.append(('h', row_idx, col_idx, colspan)) # 处理垂直合并 elif 'vMerge' in merge.tag: if merge.get('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}val') != 'continue': rowspan = 1 next_row = row_idx + 1 while next_row < len(table.rows): next_cell = table.cell(next_row, col_idx)._tc if next_cell.xpath('.//w:vMerge'): v_merge_val = next_cell.xpath('.//w:vMerge')[0].get( '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}val') if v_merge_val == 'continue': rowspan += 1 next_row += 1 else: break else: break merged.append(('v', row_idx, col_idx, rowspan)) return merged def get_table_data_range(table, start_text, end_text): """获取表格中从start_text到end_text之间的数据范围""" start_row = None end_row = None for row_idx, row in enumerate(table.rows): for cell in row.cells: if start_text in cell.text: start_row = row_idx if end_text in cell.text: end_row = row_idx if start_row is not None and end_row is not None: break return start_row, end_row def convert_table_to_excel(table, ws, start_row=None, end_row=None): """将Word表格转换为Excel工作表(保留合并和样式)""" # 确定处理的行范围 if start_row is None: start_row = 0 if end_row is None: end_row = len(table.rows) # 获取合并区域信息 merged_regions = get_merged_regions(table) # 创建数据矩阵,处理合并单元格的值 data_matrix = [] last_values = {} # 跟踪每列的最后一个非空值,处理合并单元格 for row_idx, row in enumerate(table.rows[start_row:end_row]): original_row_idx = start_row + row_idx row_data = [] for col_idx, cell in enumerate(row.cells): # 检查当前单元格是否属于垂直合并区域 is_in_merged = False for merge_type, m_row, m_col, m_span in merged_regions: if merge_type == 'v' and m_col == col_idx and m_row <= original_row_idx < m_row + m_span: is_in_merged = True # 如果是合并区域的起始行,使用当前单元格的值 if original_row_idx == m_row: value = cell.text.strip() last_values[col_idx] = value # 否则使用该列的最后一个非空值 else: value = last_values.get(col_idx, "") break # 如果不是合并区域,直接使用当前单元格的值 if not is_in_merged: value = cell.text.strip() last_values[col_idx] = value row_data.append(value) data_matrix.append(row_data) # 调整合并区域的行索引以匹配数据矩阵 adjusted_merged_regions = [] for merge_type, row, col, span in merged_regions: if start_row <= row < end_row: new_row = row - start_row adjusted_merged_regions.append((merge_type, new_row, col, span)) # 写入数据并合并单元格 for row_idx, row_data in enumerate(data_matrix): for col_idx, value in enumerate(row_data): cell = ws.cell(row=row_idx + 1, column=col_idx + 1, value=value) cell.alignment = Alignment(horizontal='center', vertical='center') # 应用合并区域 for merge_type, row, col, span in adjusted_merged_regions: if merge_type == 'h': start_col = col + 1 end_col = start_col + span - 1 ws.merge_cells(start_row=row + 1, start_column=start_col, end_row=row + 1, end_column=end_col) elif merge_type == 'v': start_row = row + 1 end_row = start_row + span - 1 ws.merge_cells(start_row=start_row, start_column=col + 1, end_row=end_row, end_column=col + 1) # 调整列宽(智能适应内容) for col_idx, _ in enumerate(data_matrix[0]): max_length = 0 for row in data_matrix: try: cell_value = row[col_idx] except IndexError: continue if cell_value and len(cell_value) > max_length: max_length = len(cell_value) adjusted_width = (max_length + 2) * 1.2 ws.column_dimensions[get_column_letter(col_idx + 1)].width = adjusted_width # 使用示例 path = './岗位说明书' os.makedirs("EXCEL", exist_ok=True) for file in os.listdir(path): if not file.endswith(('.docx', '.doc')): continue file_path = os.path.join(path, file) try: doc = Document(file_path) except Exception as e: print(f"无法处理文件 {file}: {e}") continue wb = Workbook() wb.remove(wb.active) # 查找目标表格 target_table = None start_row = None end_row = None for table in doc.tables: start, end = get_table_data_range(table, "主要职责及评价标准:", "岗位法律及规范风险点:") if start is not None: target_table = table start_row = start end_row = end break if target_table and start_row is not None: ws = wb.create_sheet("岗位职责") # 如果找到了结束标记,处理到结束标记前一行 if end_row is not None: convert_table_to_excel(target_table, ws, start_row, end_row) else: # 如果没有找到结束标记,处理从开始标记到表格末尾 convert_table_to_excel(target_table, ws, start_row) excel_name = os.path.splitext(file)[0] + "_转换结果.xlsx" wb.save(os.path.join("EXCEL", excel_name)) print(f"已成功转换: {excel_name}") else: print(f"未找到'主要职责及评价标准'表格或'岗位法律及规范风险点'标记: {file}") "工作职责及目的(描述该岗位主要活动及要达到的结果,每一应负责任请依其重要性排列)" 列中序号需要保留在内容中,“重要性”和"工作领域列"中如果为内容重复列就合并
05-14
# 使用此指令前,请确保安装必要的Python库,例如使用以下命令安装: # pip install pandas openpyxl import pandas as pd import os from collections import defaultdict import openpyxl from openpyxl.styles import PatternFill, Font, Alignment, Border, Side from openpyxl.utils import get_column_letter from typing import * try: from xbot.app.logging import trace as print except: from xbot import print def process_excel_by_batch_with_config(order_file, config_file, output_file): """ title: 按工艺批次整理Excel数据(订单优化版) description: 对表格内数据进行整理,按相同工艺分组,严格控制每批货品数量不超过配置表中的"每批最多数量",确保相同工艺下的相同订单号必须排在一起。输出结果按批次号排序,只对批次汇总行应用颜色标记,使表格更加简洁清晰。 inputs: - order_file (file): 订单数据Excel文件,eg: "订单数据.xlsx" - config_file (file): 工艺配置Excel文件,包含每批最大数量,eg: "工艺配置.xlsx" - output_file (str): 处理后保存的Excel文件路径,eg: "订单数据_批次处理.xlsx" outputs: - output_file (str): 处理后的Excel文件路径,eg: "订单数据_批次处理.xlsx" """ # 检查输入文件是否存在 for file_path in [order_file, config_file]: if not os.path.exists(file_path): raise FileNotFoundError(f"输入文件 '{file_path}' 不存在") # 检查输出路径是否有效 output_dir = os.path.dirname(output_file) if output_dir and not os.path.exists(output_dir): raise FileNotFoundError(f"输出目录 '{output_dir}' 不存在") # 读取订单Excel文件 try: df_orders = pd.read_excel(order_file) except Exception as e: raise ValueError(f"读取订单Excel文件失败: {str(e)}") # 读取配置Excel文件 try: df_config = pd.read_excel(config_file) except Exception as e: raise ValueError(f"读取配置Excel文件失败: {str(e)}") # 检查订单文件中的工艺列 craft_column_name = None possible_craft_columns = ['工艺', '工艺名称'] for col in possible_craft_columns: if col in df_orders.columns: craft_column_name = col break if craft_column_name is None: raise ValueError(f"订单Excel文件中缺少工艺列,请确保文件包含 '工艺' 或 '工艺名称' 列") # 检查配置文件中的工艺列 config_craft_column = None for col in possible_craft_columns: if col in df_config.columns: config_craft_column = col break if config_craft_column is None: raise ValueError(f"配置Excel文件中缺少工艺列,请确保文件包含 '工艺' 或 '工艺名称' 列") # 检查必要的列是否存在 required_order_columns = [craft_column_name, '货品数量', '物流单编号'] for col in required_order_columns: if col not in df_orders.columns: raise ValueError(f"订单Excel文件中缺少必要的列: '{col}'") required_config_columns = [config_craft_column, '一波最少数量', '每批最多数量'] for col in required_config_columns: if col not in df_config.columns: raise ValueError(f"配置Excel文件中缺少必要的列: '{col}'") # 检查是否有订单编号列 has_order_id = '订单编号' in df_orders.columns if not has_order_id: print("警告: 订单Excel文件中缺少'订单编号'列,将无法按订单号分组") # 创建工艺配置字典 craft_config = {} for _, row in df_config.iterrows(): craft_name = row[config_craft_column] min_qty = row['一波最少数量'] max_qty = row['每批最多数量'] craft_config[craft_name] = {'min': min_qty, 'max': max_qty} # 默认配置,当工艺在配置表中找不到时使用 default_config = {'min': 5, 'max': 30} # 按工艺分组并计算批次 def _calculate_batches(): # 创建一个新的DataFrame用于存储结果 result_df = df_orders.copy() # 检查是否已存在批次列,如果存在则删除 if '批次' in result_df.columns: result_df = result_df.drop(columns=['批次']) # 添加批次列 batch_col_idx = df_orders.columns.get_loc('物流单编号') + 1 result_df.insert(batch_col_idx, '批次', '') # 按工艺分组处理 craft_groups = defaultdict(list) if has_order_id: # 首先按工艺和订单号分组 craft_order_groups = defaultdict(lambda: defaultdict(list)) for idx, row in df_orders.iterrows(): craft = row[craft_column_name] order_id = row['订单编号'] qty = row['货品数量'] craft_order_groups[craft][order_id].append((idx, qty)) # 将订单作为整体添加到工艺组 for craft, order_groups in craft_order_groups.items(): for order_id, items in order_groups.items(): indices = [item[0] for item in items] total_qty = sum(item[1] for item in items) craft_groups[craft].append((indices, total_qty, order_id)) else: # 如果没有订单编号列,直接按工艺分组 for idx, row in df_orders.iterrows(): craft = row[craft_column_name] qty = row['货品数量'] craft_groups[craft].append(([idx], qty, None)) # 为每个工艺组分配批次 batch_colors = { 0: 'FFFF00', # 黄色 1: 'FF99CC', # 粉色 2: 'CCFFFF', # 浅蓝色 3: 'CCFFCC', # 浅绿色 4: 'FFCC99', # 橙色 } batch_info = {} # 存储每行的批次信息 {index: (batch_name, color)} batch_summary = {} # 存储每个批次的汇总信息 {batch_name: (total_qty, color, craft)} for craft, items in craft_groups.items(): # 获取该工艺的配置,如果不存在则使用默认配置 # 先尝试完全匹配 config = craft_config.get(craft, None) # 如果完全匹配失败,尝试部分匹配 if config is None: for craft_name, cfg in craft_config.items(): if craft_name in craft or craft in craft_name: config = cfg break # 如果仍然没有匹配,使用默认配置 if config is None: config = default_config min_qty = config['min'] max_qty = config['max'] # 对订单按数量排序,优先处理大订单 sorted_items = sorted(items, key=lambda x: x[1], reverse=True) batch_num = 1 batches = [] # 存储所有批次 [(batch_indices, batch_qty)] current_batch_indices = [] current_batch_qty = 0 for order_indices, order_qty, order_id in sorted_items: # 如果当前订单的数量已经超过最大批次数量,单独作为一个批次 if order_qty > max_qty: batches.append((order_indices, order_qty)) continue # 尝试将订单添加到现有批次中 added_to_existing = False # 尝试填充现有批次 for i, (batch_indices, batch_qty) in enumerate(batches): if batch_qty + order_qty <= max_qty: # 更新批次 batches[i] = (batch_indices + order_indices, batch_qty + order_qty) added_to_existing = True break if not added_to_existing: # 如果添加当前订单会超过最大批次数量,先完成当前批次 if current_batch_qty + order_qty > max_qty and current_batch_indices: batches.append((current_batch_indices, current_batch_qty)) current_batch_indices = order_indices current_batch_qty = order_qty else: # 添加到当前批次 current_batch_indices.extend(order_indices) current_batch_qty += order_qty # 处理最后一个批次 if current_batch_indices: batches.append((current_batch_indices, current_batch_qty)) # 分配批次号和颜色 for i, (batch_indices, batch_qty) in enumerate(batches): if batch_qty >= min_qty: # 只处理达到最小数量的批次 color_idx = i % len(batch_colors) color = batch_colors[color_idx] batch_name = f"{craft}-第{batch_num}批" for idx in batch_indices: batch_info[idx] = (batch_name, color) # 记录批次汇总信息 batch_summary[batch_name] = (batch_qty, color, craft) batch_num += 1 else: # 对于不满足最小数量的批次,尝试合并到其他批次 # 如果没有其他批次或无法合并,仍然创建一个新批次 if batches and i > 0: # 尝试合并到前一个批次 prev_batch_indices, prev_batch_qty = batches[i-1] if prev_batch_qty + batch_qty <= max_qty: # 可以合并 color_idx = (i-1) % len(batch_colors) color = batch_colors[color_idx] batch_name = f"{craft}-第{batch_num-1}批" for idx in batch_indices: batch_info[idx] = (batch_name, color) # 更新批次汇总信息 batch_summary[batch_name] = (prev_batch_qty + batch_qty, color, craft) else: # 无法合并,创建新批次 color_idx = i % len(batch_colors) color = batch_colors[color_idx] batch_name = f"{craft}-第{batch_num}批" for idx in batch_indices: batch_info[idx] = (batch_name, color) # 记录批次汇总信息 batch_summary[batch_name] = (batch_qty, color, craft) batch_num += 1 else: # 没有其他批次,创建新批次 color_idx = i % len(batch_colors) color = batch_colors[color_idx] batch_name = f"{craft}-第{batch_num}批" for idx in batch_indices: batch_info[idx] = (batch_name, color) # 记录批次汇总信息 batch_summary[batch_name] = (batch_qty, color, craft) batch_num += 1 return result_df, batch_info, batch_summary # 计算批次 result_df, batch_info, batch_summary = _calculate_batches() # 将批次信息填入DataFrame for idx, (batch_name, _) in batch_info.items(): result_df.loc[idx, '批次'] = batch_name # 按批次排序,确保同一批次的订单排在一起 # 如果有订单编号列,则按批次、订单编号排序,确保相同订单号排在一起 if has_order_id: result_df = result_df.sort_values(by=['批次', '订单编号', craft_column_name]) else: result_df = result_df.sort_values(by=['批次', craft_column_name]) # 保存到Excel文件,并设置不同批次的颜色和汇总行 try: # 先将DataFrame保存到Excel result_df.to_excel(output_file, index=False) # 然后使用openpyxl添加汇总行和设置样式 workbook = openpyxl.load_workbook(output_file) worksheet = workbook.active # 获取列数 num_cols = len(result_df.columns) # 获取批次列的索引 batch_col_idx = result_df.columns.get_loc('批次') + 1 # Excel列从1开始 qty_col_idx = result_df.columns.get_loc('货品数量') + 1 # Excel列从1开始 craft_col_idx = result_df.columns.get_loc(craft_column_name) + 1 # Excel列从1开始 # 获取物流单编号和订单编号列的索引(如果存在) logistics_col_idx = result_df.columns.get_loc('物流单编号') + 1 if '物流单编号' in result_df.columns else None order_col_idx = result_df.columns.get_loc('订单编号') + 1 if has_order_id else None # 检查是否有商家编码列 merchant_code_col_idx = None possible_merchant_code_columns = ['商家编码', '商品编码', '货号'] for col in possible_merchant_code_columns: if col in result_df.columns: merchant_code_col_idx = result_df.columns.get_loc(col) + 1 break # 设置单元格样式 - 只用于汇总行 def _set_summary_cell_style(cell, color): cell.fill = PatternFill(start_color=color, end_color=color, fill_type="solid") cell.font = Font(bold=True) cell.alignment = Alignment(horizontal='center', vertical='center') thin_border = Border( left=Side(style='thin'), right=Side(style='thin'), top=Side(style='thin'), bottom=Side(style='thin') ) cell.border = thin_border # 设置普通单元格样式 - 只添加边框,不添加颜色 def _set_normal_cell_style(cell): cell.alignment = Alignment(vertical='center', wrap_text=True) thin_border = Border( left=Side(style='thin'), right=Side(style='thin'), top=Side(style='thin'), bottom=Side(style='thin') ) cell.border = thin_border # 设置标题行样式 header_fill = PatternFill(start_color="D9D9D9", end_color="D9D9D9", fill_type="solid") header_font = Font(bold=True) header_alignment = Alignment(horizontal='center', vertical='center') header_border = Border( left=Side(style='thin'), right=Side(style='thin'), top=Side(style='thin'), bottom=Side(style='thin') ) for col_idx in range(1, num_cols + 1): cell = worksheet.cell(row=1, column=col_idx) cell.fill = header_fill cell.font = header_font cell.alignment = header_alignment cell.border = header_border # 为所有数据行添加边框 for row_idx in range(2, worksheet.max_row + 1): for col_idx in range(1, num_cols + 1): cell = worksheet.cell(row=row_idx, column=col_idx) _set_normal_cell_style(cell) # 找出每个批次的最后一行 batch_last_rows = {} current_batch = None current_batch_last_row = None for row_idx in range(2, worksheet.max_row + 1): batch_cell = worksheet.cell(row=row_idx, column=batch_col_idx) if batch_cell.value: if batch_cell.value != current_batch: if current_batch: batch_last_rows[current_batch] = current_batch_last_row current_batch = batch_cell.value current_batch_last_row = row_idx # 添加最后一个批次 if current_batch: batch_last_rows[current_batch] = current_batch_last_row # 按批次添加汇总行 # 记录已插入的行数,用于调整后续行的索引 inserted_rows = 0 # 按批次顺序处理 for batch_name, last_row in sorted(batch_last_rows.items()): if batch_name in batch_summary: batch_qty, color, craft = batch_summary[batch_name] # 在批次的最后一行后面插入汇总行 adjusted_last_row = last_row + inserted_rows worksheet.insert_rows(adjusted_last_row + 1) inserted_rows += 1 # 设置汇总行 summary_row = adjusted_last_row + 1 # 设置工艺列 craft_summary_cell = worksheet.cell(row=summary_row, column=craft_col_idx) craft_summary_cell.value = craft _set_summary_cell_style(craft_summary_cell, color) # 设置批次列 batch_summary_cell = worksheet.cell(row=summary_row, column=batch_col_idx) batch_summary_cell.value = f"{batch_name}汇总" _set_summary_cell_style(batch_summary_cell, color) # 设置数量列 qty_summary_cell = worksheet.cell(row=summary_row, column=qty_col_idx) qty_summary_cell.value = batch_qty _set_summary_cell_style(qty_summary_cell, color) # 设置其他列 for col_idx in range(1, num_cols + 1): if col_idx != batch_col_idx and col_idx != qty_col_idx and col_idx != craft_col_idx: cell = worksheet.cell(row=summary_row, column=col_idx) _set_summary_cell_style(cell, color) # 自动调整列宽 column_widths = {} # 首先计算每列的最大宽度 for col_idx in range(1, num_cols + 1): column = get_column_letter(col_idx) max_length = 0 for row_idx in range(1, worksheet.max_row + 1): cell = worksheet.cell(row=row_idx, column=col_idx) if cell.value: cell_length = len(str(cell.value)) if cell_length > max_length: max_length = cell_length # 设置列宽,但对特定列进行特殊处理 if col_idx == logistics_col_idx or col_idx == order_col_idx or col_idx == merchant_code_col_idx: # 物流单号、订单编号和商家编码列设置更宽一些,确保完整显示 adjusted_width = max(max_length + 2, 20) else: # 其他列根据内容自动调整,但设置最小和最大宽度 adjusted_width = min(max(max_length + 2, 10), 30) worksheet.column_dimensions[column].width = adjusted_width # 设置物流单号、订单编号和商家编码列的数字格式为文本格式,确保完整显示 text_format_columns = [logistics_col_idx, order_col_idx, merchant_code_col_idx] for col_idx in text_format_columns: if col_idx: for row_idx in range(2, worksheet.max_row + 1): cell = worksheet.cell(row=row_idx, column=col_idx) if cell.value: cell.number_format = '@' # 设置为文本格式 # 冻结首行 worksheet.freeze_panes = 'A2' # 保存修改后的Excel文件 workbook.save(output_file) except Exception as e: raise ValueError(f"保存Excel文件失败: {str(e)}") return output_file
最新发布
09-07
def format_attendance_report(output_path, exclude_columns=["匹配状态"]): """ 优化考勤报表格式的专用函数 参数: output_path - 需要格式化的文件路径 exclude_columns - 需要排除格式化的列(默认包含"匹配状态") """ try: wb = load_workbook(output_path) # 定义通用样式 header_font = Font(bold=True, color="FFFFFF") header_fill = PatternFill(start_color="4F81BD", end_color="4F81BD", fill_type="solid") body_font = Font(name="微软雅黑", size=10) thin_border = Border(left=Side(style='thin'), right=Side(style='thin'), top=Side(style='thin'), bottom=Side(style='thin')) for sheet_name in wb.sheetnames: ws = wb[sheet_name] # 设置列宽自适应 for col in ws.columns: max_length = 0 column = col[0].column for cell in col: if cell.value and cell.column not in exclude_columns: try: if len(str(cell.value)) > max_length: max_length = len(str(cell.value)) except: pass adjusted_width = (max_length + 2) * 1.2 ws.column_dimensions[get_column_letter(column)].width = min(adjusted_width, 30) # 设置表头样式 for cell in ws[1]: if cell.column not in exclude_columns: cell.font = header_font cell.fill = header_fill cell.alignment = Alignment(horizontal="center") # 设置正文样式 for row in ws.iter_rows(min_row=2): for cell in row: if cell.column not in exclude_columns: cell.font = body_font cell.border = thin_border if isinstance(cell.value, (int, float)): cell.alignment = Alignment(horizontal="right") else: cell.alignment = Alignment(horizontal="left") # 设置冻结窗格 ws.freeze_panes = "A2" # 设置时间列格式 time_columns = [col for col in ws[1] if "时间" in str(col.value)] for col in time_columns: column_letter = get_column_letter(col.column) for cell in ws[column_letter][1:]: cell.number_format = "yyyy-mm-dd hh:mm:ss" wb.save(output_path) return True except Exception as e: print(f"格式化失败:{str(e)}") return False请对这个excel处理函数进行优化,要求,1,所有单元格字体设置为微软雅黑。2.首行字体大小16,加粗,白色,其余为黑色12。3.首行行高22,其余行行高18。4.开启首行筛选,并冻结首行。5.A~I列,列宽12,J列列宽99。6.A~I列,高度长度居中,J列高度居中,长度靠左
03-10
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’{byte_str} ’ 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’{byte_str} ’ 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文件,现在这个程序需要追加一项功能,分辨事件帧和周期帧,周期帧稳定一直发送,事件帧会突然出现,然后按照固定周期发送,持续一段时间,然后事件结束又会停止发送
07-24
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值