frame buffer属性打印

本文详细介绍了如何通过自定义工具打印Framebuffer的基本信息,包括分辨率、内存地址、偏移量及BPP等,并提供了具体的编译和运行步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(一):写在前面

在这里我们主要实现了打印frame buffer的一些信息,其中包括分辨率,所占内存地址的开始地址,偏移量,BPP以及所占内存的宽度和高度等信息.主要是通过对这些参数的打印操作来实现对frame buffer的进一步深入的了解.

(二):如何编译以及运行

首先,我们看到,在我们的目录中有三个文件,fbtool.h,fbtool.c以及test.c,其中main函数位于test.c文件中.下面是我们的编译命令.

gcc fbtool.h fbtool.c test.c -o test

编译完成之后,就会生成test的可执行文件.

一般情况下,我们只要使用

./test

便能够运行该程序文件,但是由于在运行过程中,需要打开/dev/fb0文件,所以需要使用特权用户运行该程序,我们使用下面的命令来运行我们的程序文件.

sudo ./test

这样程序就能够顺利执行了.

(三):执行结果展示

下面我们来看一下我们的执行结果:

frame buffer所占内存的开始地址为:-1341710336
frame buffer 的类型为:0
frame buffer的可见清晰度为:
    x = 1366
    y = 768
frame buffer的虚拟清晰度为:
    x = 1366
    y = 768
frame buffer的虚拟分辨率到可见分辨率的偏移量为:
    x = 0
    y = 0
frame buffer的BPP为:32
这个后面再说!!
frame buffer在内存中所占的高度和宽度分别是:
    宽度 = -1
    高度 = -1

这样就能明显的看出frame buffer的一些属性了.

(四):该程序的构成

在这里我们说一下我们如果构建的该程序.

1:首先我们需要定义一个结构体,该结构体包括四个变量:

    int fb;   //framebuffer打开后的唯一标识符
    struct fb_fix_screeninfo fb_fix;  
    struct fb_var_screeninfo fb_var;
    char dev[20];  //设备名称

2:定义完成结构体之后,我们就需要定义操作frame buffer的各个方法,包括打开,关闭fb0设备,打印相关信息的函数等

//打开framebuffer
int fb_open(PFBDEV pFbdev);
//关闭framebuffer
int fb_close(PFBDEV pFbdev);
//打印frame buffer 所占内存的开始地址 
void pmem_start(PFBDEV pFbdev);
//打印FB_TYPE
void p_type(PFBDEV pFbdev);
//打印可见清晰度
void p_visible_res(PFBDEV pFbdev);
//打印虚拟分辨率
void p_virt_res(PFBDEV pFbdev);
//打印虚拟到可见的偏移量
void p_offset(PFBDEV pFbdev);
//打印每个像素的位数
void p_bpp(PFBDEV pFbdev);
//打印R,G,B和透明度
void p_rgbt(PFBDEV pFbdev);
//打印在内存中的高度和宽度
void p_hw(PFBDEV pFbdev);

3:接着就是这些函数的实现

这些函数的实现都是比较简单的,基本上是打印结构体中的一些信息.

4:编写测试程序,运行测试

最后就是编写我们的main函数,进行编译测试.

(五):后期展望

在后面一个测试中,着重讲解fb_var里面的R,G,B和透明度的值.

(六):写在后面

宝剑锋从磨砺出,梅花香自苦寒来.

注意:源代码我已经长传到github中,下面是地址:

源代码下载

转载于:https://www.cnblogs.com/bobo1223/p/7287509.html

# -*- coding: utf-8 -*- import threading import time import sys import inspect import ctypes import os import cv2 import numpy as np import logging from .MvCameraControl_class import * from datetime import datetime from ctypes import * from enum import Enum from ctypes import byref, cast, POINTER, c_ubyte from .MvCameraControl_class import MvCamera from .Cam_eraConstants import * from .Camera_Params_header import MV_FRAME_OUT_INFO_EX, PixelType_Gvsp_BGR8_Packed # 配置日志系统 def setup_logging(log_level=logging.INFO): """配置全局日志系统""" logging.basicConfig( level=log_level, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("camera_operation.log"), logging.StreamHandler() ] ) logging.info("日志系统初始化完成") setup_logging(logging.INFO) # 像素格式兼容处理 try: # 尝试导入像素类型定义 from .PixelType_header import * logging.info("成功从 PixelType_header 导入像素类型定义") except ImportError: try: # 尝试从父目录导入 from .PixelType_header import * logging.info("成功从全局 PixelType_header 导入像素类型定义") except ImportError: logging.warning("警告: 无法导入 PixelType_header,创建虚拟定义") # 创建必要的虚拟定义 PixelType_Gvsp_Undefined = -1 PixelType_Gvsp_Mono8 = 0x01000008 PixelType_Gvsp_BayerGR8 = 0x01080008 PixelType_Gvsp_RGB8_Packed = 0x02180014 # 定义像素格式映射表 PIXEL_FORMATS = { "MONO8": PixelType_Gvsp_Mono8 if 'PixelType_Gvsp_Mono8' in globals() else 0x01080001, "MONO10": PixelType_Gvsp_Mono10 if 'PixelType_Gvsp_Mono10' in globals() else 0x01100003, "MONO12": PixelType_Gvsp_Mono12 if 'PixelType_Gvsp_Mono12' in globals() else 0x01100005, "BAYER_BG8": PixelType_Gvsp_BayerBG8 if 'PixelType_Gvsp_BayerBG8' in globals() else 0x0108000B, "BAYER_GB8": PixelType_Gvsp_BayerGB8 if 'PixelType_Gvsp_BayerGB8' in globals() else 0x0108000A, "BAYER_GR8": PixelType_Gvsp_BayerGR8 if 'PixelType_Gvsp_BayerGR8' in globals() else 0x01080008, "BAYER_RG8": PixelType_Gvsp_BayerRG8 if 'PixelType_Gvsp_BayerRG8' in globals() else 0x01080009, "RGB8": PixelType_Gvsp_RGB8_Packed if 'PixelType_Gvsp_RGB8_Packed' in globals() else 0x02180014, "BGR8": PixelType_Gvsp_BGR8_Packed, "YUV422": PixelType_Gvsp_YUV422_Packed if 'PixelType_Gvsp_YUV422_Packed' in globals() else 0x02100032, "YUV422_YUYV": PixelType_Gvsp_YUV422_YUYV_Packed if 'PixelType_Gvsp_YUV422_YUYV_Packed' in globals() else 0x0210001F } # 像素格式枚举 class PixelFormat(Enum): MONO8 = PIXEL_FORMATS["MONO8"] MONO10 = PIXEL_FORMATS["MONO10"] MONO12 = PIXEL_FORMATS["MONO12"] BAYER_BG8 = PIXEL_FORMATS["BAYER_BG8"] BAYER_GB8 = PIXEL_FORMATS["BAYER_GB8"] BAYER_GR8 = PIXEL_FORMATS["BAYER_GR8"] BAYER_RG8 = PIXEL_FORMATS["BAYER_RG8"] RGB8 = PIXEL_FORMATS["RGB8"] YUV422 = PIXEL_FORMATS["YUV422"] YUV422_YUYV = PIXEL_FORMATS["YUV422_YUYV"] def is_mono_data(enGvspPixelType): """判断是否为单色图像格式""" mono_formats = [ PIXEL_FORMATS["MONO8"], PIXEL_FORMATS["MONO10"], PIXEL_FORMATS["MONO12"], 0x010C0004, # Mono10 Packed 0x010C0006, # Mono12 Packed 0x01080002, # Mono8 Signed 0x0110000C # Mono16 ] return enGvspPixelType in mono_formats def is_color_data(enGvspPixelType): """判断是否为彩色图像格式""" color_formats = [ # Bayer格式 PIXEL_FORMATS["BAYER_BG8"], PIXEL_FORMATS["BAYER_GB8"], PIXEL_FORMATS["BAYER_GR8"], PIXEL_FORMATS["BAYER_RG8"], 0x01100011, # BayerBG10 0x01100010, # BayerGB10 0x0110000E, # BayerGR10 0x0110000F, # BayerRG10 0x01100017, # BayerBG12 0x01100016, # BayerGB12 0x01100014, # BayerGR12 0x01100015, # BayerRG12 # YUV格式 PIXEL_FORMATS["YUV422"], PIXEL_FORMATS["YUV422_YUYV"], # RGB格式 PIXEL_FORMATS["RGB8"], 0x0220001E, # RGB10_Packed 0x02300020, # RGB12_Packed 0x02400021, # RGB16_Packed 0x02180032 # BGR8_Packed ] return enGvspPixelType in color_formats def get_pixel_format_name(pixel_value): """根据像素值获取可读的名称""" for name, value in PIXEL_FORMATS.items(): if value == pixel_value: return name return f"未知格式: 0x{pixel_value:08X}" # 强制关闭线程 def async_raise(tid, exctype): """安全终止线程""" tid = ctypes.c_long(tid) if not inspect.isclass(exctype): exctype = type(exctype) res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype)) if res == 0: raise ValueError("invalid thread id") elif res != 1: ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None) raise SystemError("PyThreadState_SetAsyncExc failed") def stop_thread(thread): """停止指定线程""" async_raise(thread.ident, SystemExit) # ... 前面的导入和辅助函数保持不变 ... # 相机操作类 class CameraOperation: __public_methods__ = [ 'open_device', 'close_device', 'start_grabbing', 'stop_grabbing', 'set_trigger_mode', 'set_continue_mode', 'trigger_once', 'get_parameters', 'set_param', 'save_image', 'capture_frame', 'get_current_frame', 'is_frame_available' ] # 状态码定义 MV_OK = 0 MV_E_CALLORDER = -2147483647 MV_E_PARAMETER = -2147483646 MV_E_NO_DATA = -2147483645 MV_E_SAVE_IMAGE = -2147483644 MV_E_STATE = -2147483643 MV_E_HANDLE = -2147483642 MV_E_SUPPORT = -2147483641 def __init__(self, obj_cam, st_device_list, n_connect_num=0): """ 初始化相机操作对象 :param obj_cam: 相机对象 :param st_device_list: 设备列表 :param n_connect_num: 连接编号 """ self.device_handle = None self.is_open = False # 确保cam属性存在 self.cam = obj_cam # 使用cam作为相机对象的统一引用 self.obj_cam = obj_cam # 保持旧代码兼容性 self.st_device_list = st_device_list self.n_connect_num = n_connect_num # 状态标志 - 确保所有属性初始化 self.b_open_device = False self.b_start_grabbing = False self.b_thread_running = False self.b_exit = False self.connected = False # 连接状态标志 self.b_frame_received = False # 帧接收状态 # 线程相关 self.h_thread_handle = None # 图像数据 self.st_frame_info = None self.buf_save_image = None self.n_save_image_size = 0 self.current_frame = None # 参数 self.frame_rate = 0.0 self.exposure_time = 0.0 self.gain = 0.0 # 线程安全锁 self.buf_lock = threading.Lock() # 图像缓冲区锁 self.frame_lock = threading.Lock() # 当前帧锁 self.param_lock = threading.Lock() # 参数锁 self.frame_count = 0 # 帧计数 self.last_frame_time = None # 最后帧时间 self.is_streaming = False # 取流状态 logging.info("相机操作对象初始化完成") def is_frame_available(self): """检查是否有可用帧图像""" try: # 检查是否有有效帧图像 with self.frame_lock: return self.current_frame is not None and self.current_frame.size > 0 except Exception as e: logging.error(f"检查帧可用性失败: {str(e)}") return False def capture_frame(self): """捕获当前帧""" try: with self.frame_lock: if self.current_frame is not None: return self.current_frame.copy() return None except Exception as e: logging.error(f"捕获帧失败: {str(e)}") return None def get_current_frame(self): """获取当前帧图像""" try: with self.frame_lock: if self.current_frame is not None: return self.current_frame.copy() return None except Exception as e: logging.error(f"获取图像失败: {str(e)}") return None def get_frame_info(self): """ 获取当前帧的详细信息 :return: 帧信息字典 """ info = { "available": False, "width": 0, "height": 0, "format": "未知", "size": 0 } try: with self.frame_lock: if self.current_frame is not None: info["available"] = True info["width"] = self.current_frame.shape[1] info["height"] = self.current_frame.shape[0] info["format"] = f"{self.current_frame.dtype}" info["size"] = self.current_frame.size if self.st_frame_info: info["sdk_width"] = self.st_frame_info.nWidth info["sdk_height"] = self.st_frame_info.nHeight info["sdk_format"] = get_pixel_format_name(self.st_frame_info.enPixelType) info["sdk_size"] = self.st_frame_info.nFrameLen except Exception as e: logging.exception(f"获取帧信息异常: {str(e)}") return info def set_pixel_format(self, format_name): if format_name in PIXEL_FORMATS: format_value = PIXEL_FORMATS[format_name] ret = self.obj_cam.MV_CC_SetPixelType(format_value) if ret != self.MV_OK: logging.error(f"设置像素格式失败: {hex(ret)}") return ret else: logging.error(f"不支持的像素格式: {format_name}") return self.MV_E_PARAMETER def save_frame_to_file(self, frame, file_path, save_format="bmp", quality=95): """ 将帧保存到文件 :param frame: 要保存的帧 (numpy数组) :param file_path: 文件路径 :param save_format: 保存格式 (bmp, jpg, png, tiff) :param quality: 保存质量 (仅对jpg有效) :return: 保存结果 (MV_OK 或 错误码) """ if frame is None or frame.size == 0: logging.error("无法保存无效帧: 帧为空") return self.MV_E_NO_DATA try: # 确保目录存在 directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): os.makedirs(directory, exist_ok=True) # 根据格式保存图像 save_format = save_format.lower() if save_format == "bmp": cv2.imwrite(file_path, frame) elif save_format in ["jpg", "jpeg"]: cv2.imwrite(file_path, frame, [cv2.IMWRITE_JPEG_QUALITY, quality]) elif save_format == "png": cv2.imwrite(file_path, frame, [cv2.IMWRITE_PNG_COMPRESSION, 9]) elif save_format in ["tiff", "tif"]: cv2.imwrite(file_path, frame, [cv2.IMWRITE_TIFF_COMPRESSION, 1]) else: logging.error(f"不支持的图像格式: {save_format}") return self.MV_E_PARAMETER # 验证保存结果 if not os.path.exists(file_path): logging.error(f"文件保存失败: {file_path}") return self.MV_E_SAVE_IMAGE file_size = os.path.getsize(file_path) if file_size < 1024: # 小于1KB视为无效 logging.error(f"文件大小异常: {file_path} ({file_size} 字节)") os.remove(file_path) # 删除无效文件 return self.MV_E_SAVE_IMAGE logging.info(f"图像已保存: {file_path} ({file_size} 字节)") return self.MV_OK except Exception as e: logging.exception(f"保存图像异常: {str(e)}") return self.MV_E_SAVE_IMAGE def stop_grabbing(self): """停止取流""" # 关键修复:添加状态检查 if not hasattr(self, 'cam') or self.cam is None: logging.error("无法停止采集:相机对象不存在") return self.MV_E_STATE if not self.connected: logging.error("无法停止采集:相机未连接") return self.MV_E_STATE try: ret = self.cam.stop_grabbing() if ret == MV_OK: self.is_grabbing = False self.last_frame = None # 清除最后帧 self.image_buffer = [] # 清空缓冲区 return ret except Exception as e: logging.exception(f"停止采集时发生异常: {e}") return self.MV_E_STATE def is_ready(self): """检查设备是否就绪""" return self.b_open_device and not self.b_exit def open_device(self, device_index=0, access_mode=0): """打开相机设备""" if self.b_open_device: logging.warning("设备已打开,无需重复操作") return self.MV_OK if self.n_connect_num < 0: logging.error("无效的连接编号") return self.MV_E_CALLORDER try: # 选择设备并创建句柄 nConnectionNum = int(self.n_connect_num) stDeviceList = cast(self.st_device_list.pDeviceInfo[int(nConnectionNum)], POINTER(MV_CC_DEVICE_INFO)).contents # 确保相机对象存在 if self.obj_cam is None: self.obj_cam = MvCamera() self.cam = self.obj_cam # 确保cam属性存在 # 创建相机句柄 ret = self.obj_cam.MV_CC_CreateHandle(stDeviceList) if ret != self.MV_OK: self.obj_cam.MV_CC_DestroyHandle() logging.error(f"创建句柄失败: {hex(ret)}") return ret # 打开设备 ret = self.obj_cam.MV_CC_OpenDevice() if ret != self.MV_OK: logging.error(f"打开设备失败: {hex(ret)}") return ret # 设备已成功打开 self.b_open_device = True self.connected = True # 设置连接状态 self.b_exit = False logging.info("设备打开成功") # 探测网络最佳包大小(仅对GigE相机有效) if stDeviceList.nTLayerType in [MV_GIGE_DEVICE, MV_GENTL_GIGE_DEVICE]: nPacketSize = self.obj_cam.MV_CC_GetOptimalPacketSize() if int(nPacketSize) > 0: ret = self.obj_cam.MV_CC_SetIntValue("GevSCPSPacketSize", nPacketSize) if ret != self.MV_OK: logging.warning(f"设置包大小失败: {hex(ret)}") else: logging.warning(f"获取最佳包大小失败: {hex(nPacketSize)}") # 获取采集帧率使能状态 stBool = c_bool(False) ret = self.obj_cam.MV_CC_GetBoolValue("AcquisitionFrameRateEnable", stBool) if ret != self.MV_OK: logging.warning(f"获取帧率使能状态失败: {hex(ret)}") # 现在设备已打开,可以安全设置触发模式 ret = self.set_continue_mode() if ret != self.MV_OK: logging.warning(f"设置连续模式失败: {hex(ret)}") return self.MV_OK except Exception as e: logging.exception(f"打开设备异常: {str(e)}") # 尝试清理资源 try: if hasattr(self, 'obj_cam'): self.obj_cam.MV_CC_CloseDevice() self.obj_cam.MV_CC_DestroyHandle() except: pass self.b_open_device = False self.connected = False return self.MV_E_STATE def close_device(self): """关闭相机设备""" if not self.b_open_device: logging.warning("设备未打开,无需关闭") return self.MV_OK try: # 停止采集(如果正在采集) if self.b_start_grabbing: self.stop_grabbing() # 关闭设备 ret = self.obj_cam.MV_CC_CloseDevice() if ret != self.MV_OK: logging.error(f"关闭设备失败: {hex(ret)}") # 销毁句柄 ret = self.obj_cam.MV_CC_DestroyHandle() if ret != self.MV_OK: logging.error(f"销毁句柄失败: {hex(ret)}") self.b_open_device = False self.connected = False # 重置连接状态 self.b_exit = True logging.info("设备关闭成功") return self.MV_OK except Exception as e: logging.exception(f"关闭设备异常: {str(e)}") return self.MV_E_STATE def start_grabbing(self, winHandle=None): """开始图像采集""" if not self.b_open_device: logging.error("设备未打开,无法开始采集") return self.MV_E_CALLORDER if self.b_start_grabbing: logging.warning("采集已在进行中") return self.MV_OK try: # 开始采集 ret = self.obj_cam.MV_CC_StartGrabbing() if ret != self.MV_OK: logging.error(f"开始采集失败: {hex(ret)}") return ret self.b_start_grabbing = True self.b_exit = False # 启动采集线程 self.h_thread_handle = threading.Thread( target=self.work_thread, args=(winHandle,), daemon=True ) self.h_thread_handle.start() self.b_thread_running = True logging.info("图像采集已开始") return self.MV_OK except Exception as e: logging.exception(f"开始采集异常: {str(e)}") return self.MV_E_STATE def set_trigger_mode(self, enable=True, source=MV_TRIGGER_SOURCE_SOFTWARE): """ 设置触发模式 :param enable: 是否启用触发模式 :param source: 触发源 (默认软件触发) :return: 操作结果 """ if not self.b_open_device: logging.error("设备未打开,无法设置触发模式") return self.MV_E_CALLORDER try: mode = MV_TRIGGER_MODE_ON if enable else MV_TRIGGER_MODE_OFF ret = self.obj_cam.MV_CC_SetEnumValue("TriggerMode", mode) if ret != self.MV_OK: logging.error(f"设置触发模式失败: {hex(ret)}") return ret if enable: ret = self.obj_cam.MV_CC_SetEnumValue("TriggerSource", source) if ret != self.MV_OK: logging.error(f"设置触发源失败: {hex(ret)}") return ret logging.info(f"触发模式设置成功: {'启用' if enable else '禁用'}") return self.MV_OK except Exception as e: logging.exception(f"设置触发模式异常: {str(e)}") return self.MV_E_STATE def set_continue_mode(self): """设置连续采集模式""" # 添加设备状态检查 if not self.b_open_device: logging.error("设备未打开,无法设置连续模式") return self.MV_E_CALLORDER logging.info("尝试设置连续采集模式") try: # 禁用触发模式 ret = self.obj_cam.MV_CC_SetEnumValue("TriggerMode", MV_TRIGGER_MODE_OFF) if ret != self.MV_OK: logging.error(f"禁用触发模式失败: {hex(ret)}") return ret # 设置采集模式为连续 ret = self.obj_cam.MV_CC_SetEnumValue("AcquisitionMode", 2) # 2表示连续采集 if ret != self.MV_OK: logging.error(f"设置连续采集模式失败: {hex(ret)}") return ret logging.info("连续采集模式设置成功") return self.MV_OK except Exception as e: logging.exception(f"设置连续模式异常: {str(e)}") return self.MV_E_STATE def trigger_once(self): """执行一次软触发""" if not self.b_open_device: logging.error("设备未打开,无法触发") return self.MV_E_CALLORDER try: ret = self.obj_cam.MV_CC_SetCommandValue("TriggerSoftware") if ret != self.MV_OK: logging.error(f"软触发失败: {hex(ret)}") return ret logging.info("软触发成功") return self.MV_OK except Exception as e: logging.exception(f"软触发异常: {str(e)}") return self.MV_E_STATE def get_parameters(self): """获取相机参数""" if not self.b_open_device: logging.error("设备未打开,无法获取参数") return self.MV_E_CALLORDER try: # 使用锁保护参数访问 with self.param_lock: # 初始化返回值为整数错误码 return_code = self.MV_OK # 帧率 stFrameRate = MVCC_FLOATVALUE() memset(byref(stFrameRate), 0, sizeof(MVCC_FLOATVALUE)) ret = self.obj_cam.MV_CC_GetFloatValue("AcquisitionFrameRate", stFrameRate) if ret == self.MV_OK: self.frame_rate = stFrameRate.fCurValue logging.debug(f"获取帧率成功: {self.frame_rate}") else: logging.warning(f"获取帧率失败: {hex(ret)}") if return_code == self.MV_OK: # 保留第一个错误码 return_code = ret # 曝光时间 stExposure = MVCC_FLOATVALUE() memset(byref(stExposure), 0, sizeof(MVCC_FLOATVALUE)) ret = self.obj_cam.MV_CC_GetFloatValue("ExposureTime", stExposure) if ret == self.MV_OK: self.exposure_time = stExposure.fCurValue logging.debug(f"获取曝光时间成功: {self.exposure_time}") else: logging.warning(f"获取曝光时间失败: {hex(ret)}") if return_code == self.MV_OK: return_code = ret # 增益 stGain = MVCC_FLOATVALUE() memset(byref(stGain), 0, sizeof(MVCC_FLOATVALUE)) ret = self.obj_cam.MV_CC_GetFloatValue("Gain", stGain) if ret == self.MV_OK: self.gain = stGain.fCurValue logging.debug(f"获取增益成功: {self.gain}") else: logging.warning(f"获取增益失败: {hex(ret)}") if return_code == self.MV_OK: return_code = ret # 返回整数错误码 return return_code except Exception as e: logging.exception(f"获取参数异常: {str(e)}") return self.MV_E_STATE # 添加兼容拼写错误的方法 def get_parame(self): """兼容拼写错误的方法名:get_parame""" logging.warning("调用get_parame方法 - 可能是拼写错误,建议使用get_parameters()") return self.get_parameters() # 添加动态属性处理 def __getattr__(self, name): # 处理保存方法 if name == "save_image": logging.warning("动态处理save_image调用 - 映射到save_image") return self.save_image if name == "save_bmp": logging.warning("动态处理save_bmp调用 - 映射到save_bmp") return self.save_bmp # 处理其他可能的拼写错误 method_map = { "get_parame": self.get_parame, "get_parm": self.get_parameters, "get_parmeter": self.get_parameters, "get_parma": self.get_parameters, "get_parameter": self.get_parameters, "get_param": self.get_parameters } if name in method_map: logging.warning(f"动态处理{name}调用 - 可能是拼写错误") return method_map[name] # 关键修复:处理b_frame_received属性 if name == "b_frame_received": logging.warning("动态访问b_frame_received属性 - 提供默认值") return False # 关键修复:处理cam属性 if name == "cam": if not hasattr(self, 'obj_cam') or self.obj_cam is None: raise AttributeError( f"'{type(self).__name__}' 对象没有 'cam' 属性。" "可能原因: 1) 相机未初始化 2) 连接未建立" ) return self.obj_cam raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'") def set_param(self, frame_rate=None, exposure_time=None, gain=None): """ 设置相机参数 :param frame_rate: 帧率 (None表示不修改) :param exposure_time: 曝光时间 (None表示不修改) :param gain: 增益 (None表示不修改) :return: 操作结果 """ if not self.b_open_device: logging.error("设备未打开,无法设置参数") return self.MV_E_CALLORDER try: # 使用锁保护参数修改 with self.param_lock: # 禁用自动曝光 ret = self.obj_cam.MV_CC_SetEnumValue("ExposureAuto", 0) if ret != self.MV_OK: logging.warning(f"禁用自动曝光失败: {hex(ret)}") # 设置帧率 if frame_rate is not None: ret = self.obj_cam.MV_CC_SetFloatValue("AcquisitionFrameRate", float(frame_rate)) if ret != self.MV_OK: logging.error(f"设置帧率失败: {hex(ret)}") return ret self.frame_rate = float(frame_rate) # 设置曝光时间 if exposure_time is not None: ret = self.obj_cam.MV_CC_SetFloatValue("ExposureTime", float(exposure_time)) if ret != self.MV_OK: logging.error(f"设置曝光时间失败: {hex(ret)}") return ret self.exposure_time = float(exposure_time) # 设置增益 if gain is not None: ret = self.obj_cam.MV_CC_SetFloatValue("Gain", float(gain)) if ret != self.MV_OK: logging.error(f"设置增益失败: {hex(ret)}") return ret self.gain = float(gain) logging.info(f"参数设置成功: 帧率={self.frame_rate}, 曝光={self.exposure_time}, 增益={self.gain}") return self.MV_OK except Exception as e: logging.exception(f"设置参数异常: {str(e)}") return self.MV_E_STATE def work_thread(self, winHandle=None): """图像采集工作线程""" stOutFrame = MV_FRAME_OUT() memset(byref(stOutFrame), 0, sizeof(stOutFrame)) logging.info("采集线程启动") while not self.b_exit: try: # 获取图像缓冲区 ret = self.obj_cam.MV_CC_GetImageBuffer(stOutFrame, 1000) if ret != self.MV_OK: if ret != self.MV_E_NO_DATA: # 忽略无数据错误 logging.warning(f"获取图像缓冲区失败: {hex(ret)}") time.sleep(0.01) continue # 更新帧信息 self.st_frame_info = stOutFrame.stFrameInfo # 关键修复:标记帧已接收 self.b_frame_received = True # 记录像素格式信息 pixel_format = get_pixel_format_name(self.st_frame_info.enPixelType) logging.debug(f"获取到图像: {self.st_frame_info.nWidth}x{self.st_frame_info.nHeight}, " f"格式: {pixel_format}, 大小: {self.st_frame_info.nFrameLen}字节") # 分配/更新图像缓冲区 frame_size = stOutFrame.stFrameInfo.nFrameLen with self.buf_lock: if self.buf_save_image is None or self.n_save_image_size < frame_size: self.buf_save_image = (c_ubyte * frame_size)() self.n_save_image_size = frame_size # 复制图像数据 cdll.msvcrt.memcpy(byref(self.buf_save_image), stOutFrame.pBufAddr, frame_size) # 更新当前帧 self.update_current_frame() # 显示图像(如果指定了窗口句柄) if winHandle is not None: stDisplayParam = MV_DISPLAY_FRAME_INFO() memset(byref(stDisplayParam), 0, sizeof(stDisplayParam)) stDisplayParam.hWnd = int(winHandle) stDisplayParam.nWidth = self.st_frame_info.nWidth stDisplayParam.nHeight = self.st_frame_info.nHeight stDisplayParam.enPixelType = self.st_frame_info.enPixelType stDisplayParam.pData = self.buf_save_image stDisplayParam.nDataLen = frame_size self.obj_cam.MV_CC_DisplayOneFrame(stDisplayParam) # 释放图像缓冲区 self.obj_cam.MV_CC_FreeImageBuffer(stOutFrame) except Exception as e: logging.exception(f"采集线程异常: {str(e)}") time.sleep(0.1) # 清理资源 with self.buf_lock: if self.buf_save_image is not None: del self.buf_save_image self.buf_save_image = None self.n_save_image_size = 0 logging.info("采集线程退出") def update_current_frame(self): """将原始图像数据转换为OpenCV格式并存储""" if not self.st_frame_info or not self.buf_save_image: logging.warning("更新当前帧时缺少帧信息或缓冲区") return try: # 获取图像信息 width = self.st_frame_info.nWidth height = self.st_frame_info.nHeight pixel_type = self.st_frame_info.enPixelType # 复制缓冲区数据 with self.buf_lock: buffer_copy = bytearray(self.buf_save_image) # 转换为numpy数组 np_buffer = np.frombuffer(buffer_copy, dtype=np.uint8) # 根据像素类型处理图像 frame = None if is_mono_data(pixel_type): # 单色图像 frame = np_buffer.reshape(height, width) elif pixel_type == PIXEL_FORMATS["BAYER_BG8"]: # Bayer BG格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerBG2RGB) elif pixel_type == PIXEL_FORMATS["BAYER_GB8"]: # Bayer GB格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerGB2RGB) elif pixel_type == PIXEL_FORMATS["BAYER_GR8"]: # Bayer GR格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerGR2RGB) elif pixel_type == PIXEL_FORMATS["BAYER_RG8"]: # Bayer RG格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerRG2RGB) elif pixel_type == PIXEL_FORMATS["RGB8"]: # RGB格式 frame = np_buffer.reshape(height, width, 3) elif pixel_type in [PIXEL_FORMATS["YUV422"], PIXEL_FORMATS["YUV422_YUYV"]]: # YUV格式 frame = cv2.cvtColor(np_buffer.reshape(height, width, 2), cv2.COLOR_YUV2RGB_YUYV) else: # 尝试自动处理其他格式 if pixel_type in [PIXEL_FORMATS["MONO10"], PIXEL_FORMATS["MONO12"]]: # 10位或12位单色图像需要特殊处理 frame = self.process_high_bit_depth(np_buffer, width, height, pixel_type) else: logging.warning(f"不支持的像素格式: {get_pixel_format_name(pixel_type)}") return # 更新当前帧 - 使用线程安全锁 with self.frame_lock: self.current_frame = frame logging.debug(f"当前帧更新成功: {frame.shape if frame is not None else '无数据'}") except Exception as e: logging.exception(f"更新当前帧异常: {str(e)}") # 调试:保存原始数据用于分析 try: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") debug_path = f"frame_debug_{timestamp}.bin" with open(debug_path, "wb") as f: f.write(buffer_copy) logging.info(f"已保存原始帧数据到: {debug_path}") except: logging.error("保存调试帧数据失败") def process_high_bit_depth(self, buffer, width, height, pixel_type): """处理高位深度图像格式""" try: # 10位或12位图像处理 if pixel_type == PIXEL_FORMATS["MONO10"]: # 将10位数据转换为16位 data_16bit = np.frombuffer(buffer, dtype=np.uint16) # 10位数据存储方式:每个像素占用2字节,但只有10位有效 data_16bit = (data_16bit >> 6) # 右移6位使10位数据对齐到低10位 frame = data_16bit.reshape(height, width).astype(np.uint16) elif pixel_type == PIXEL_FORMATS["MONO12"]: # 将12位数据转换为16位 data_16bit = np.frombuffer(buffer, dtype=np.uint16) # 12位数据存储方式:每个像素占用2字节,但只有12位有效 data_16bit = (data_16bit >> 4) # 右移4位使12位数据对齐到低12位 frame = data_16bit.reshape(height, width).astype(np.uint16) else: logging.warning(f"不支持的高位深度格式: {get_pixel_format_name(pixel_type)}") return None # 归一化到8位用于显示(如果需要) frame_8bit = cv2.convertScaleAbs(frame, alpha=(255.0/4095.0)) return frame_8bit except Exception as e: logging.exception(f"处理高位深度图像异常: {str(e)}") return None def save_image(self, file_path, save_format="bmp", quality=95): """ 安全保存当前帧到文件 - 使用原始缓冲区数据 """ if not self.b_open_device or not self.b_start_grabbing: logging.error("设备未就绪,无法保存图像") return self.MV_E_CALLORDER # 使用缓冲区锁确保数据一致性 with self.buf_lock: if not self.buf_save_image or not self.st_frame_info: logging.error("无可用图像数据") return self.MV_E_NO_DATA # 获取图像信息 width = self.st_frame_info.nWidth height = self.st_frame_info.nHeight pixel_type = self.st_frame_info.enPixelType frame_size = self.st_frame_info.nFrameLen # 复制缓冲区数据 buffer_copy = bytearray(self.buf_save_image) try: # 确保目录存在 directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): os.makedirs(directory, exist_ok=True) logging.info(f"创建目录: {directory}") # 根据像素类型处理图像 np_buffer = np.frombuffer(buffer_copy, dtype=np.uint8) # 根据像素格式转换图像 if is_mono_data(pixel_type): # 单色图像 frame = np_buffer.reshape(height, width) elif pixel_type == PIXEL_FORMATS["BAYER_BG8"]: # Bayer BG格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerBG2BGR) elif pixel_type == PIXEL_FORMATS["BAYER_GB8"]: # Bayer GB格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerGB2BGR) elif pixel_type == PIXEL_FORMATS["BAYER_GR8"]: # Bayer GR格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerGR2BGR) elif pixel_type == PIXEL_FORMATS["BAYER_RG8"]: # Bayer RG格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerRG2BGR) elif pixel_type == PIXEL_FORMATS["RGB8"]: # RGB格式 frame = np_buffer.reshape(height, width, 3) frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) elif pixel_type in [PIXEL_FORMATS["YUV422"], PIXEL_FORMATS["YUV422_YUYV"]]: # YUV格式 frame = cv2.cvtColor(np_buffer.reshape(height, width, 2), cv2.COLOR_YUV2BGR_YUYV) else: # 尝试自动处理其他格式 if pixel_type in [PIXEL_FORMATS["MONO10"], PIXEL_FORMATS["MONO12"]]: # 10位或12位单色图像需要特殊处理 frame = self.process_high_bit_depth(np_buffer, width, height, pixel_type) else: logging.error(f"不支持的像素格式: {get_pixel_format_name(pixel_type)}") return self.MV_E_PARAMETER # 根据格式保存图像 save_format = save_format.lower() try: if save_format == "bmp": cv2.imwrite(file_path, frame) elif save_format in ["jpg", "jpeg"]: cv2.imwrite(file_path, frame, [cv2.IMWRITE_JPEG_QUALITY, quality]) elif save_format == "png": cv2.imwrite(file_path, frame, [cv2.IMWRITE_PNG_COMPRESSION, 9]) elif save_format in ["tiff", "tif"]: cv2.imwrite(file_path, frame, [cv2.IMWRITE_TIFF_COMPRESSION, 1]) else: logging.error(f"不支持的图像格式: {save_format}") return self.MV_E_PARAMETER # 验证保存结果 if not os.path.exists(file_path): logging.error(f"文件保存失败: {file_path}") return self.MV_E_SAVE_IMAGE file_size = os.path.getsize(file_path) if file_size < 1024: # 小于1KB视为无效 logging.error(f"文件大小异常: {file_path} ({file_size} 字节)") os.remove(file_path) # 删除无效文件 return self.MV_E_SAVE_IMAGE logging.info(f"图像已保存: {file_path} ({file_size} 字节)") return self.MV_OK except Exception as e: logging.exception(f"保存图像异常: {str(e)}") return self.MV_E_SAVE_IMAGE except Exception as e: logging.exception(f"图像处理异常: {str(e)}") return self.MV_E_SAVE_IMAGE # 兼容旧方法的保存接口 def save_jpg(self, file_path=None, quality=95): """保存为JPEG格式""" if file_path is None: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") file_path = f"capture_{timestamp}.jpg" return self.save_image(file_path, "jpg", quality) def save_bmp(self, file_path=None): """保存为BMP格式""" if file_path is None: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") file_path = f"capture_{timestamp}.bmp" return self.save_image(file_path, "bmp") def save_png(self, file_path=None): """保存为PNG格式""" if file_path is None: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") file_path = f"capture_{timestamp}.png" return self.save_image(file_path, "png") def save_tiff(self, file_path=None): """保存为TIFF格式""" if file_path is None: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") file_path = f"capture_{timestamp}.tiff" return self.save_image(file_path, "tiff") # 新增方法:获取帧状态详细信息 def get_frame_status(self): """获取当前帧状态详细信息""" # 安全访问b_frame_received属性 frame_received = getattr(self, 'b_frame_received', False) status = { "camera_open": self.b_open_device, "grabbing_started": self.b_start_grabbing, "thread_running": self.b_thread_running, "frame_received": frame_received, # 使用安全访问 "frame_size": self.n_save_image_size if self.buf_save_image else 0, "current_frame": self.current_frame is not None, "connected": self.connected # 新增连接状态 } if self.st_frame_info: status.update({ "width": self.st_frame_info.nWidth, "height": self.st_frame_info.nHeight, "pixel_format": get_pixel_format_name(self.st_frame_info.enPixelType), "frame_num": self.st_frame_info.nFrameNum }) return status # 析构函数确保资源释放 def __del__(self): """确保相机资源被正确释放""" try: if self.b_start_grabbing: self.stop_grabbing() if self.b_open_device: self.close_device() except Exception as e: logging.error(f"析构函数中释放资源失败: {str(e)}") 可是我这个Cam_Operation_class.py里的命名就是小写啊
最新发布
07-14
import sys sys.path.append(‘/UI/Login’) from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtWidgets import QApplication, QWidget #from PyQt5.QtCore import QThread, pyqtSignal,QTimer from Login import Ui_Form as Login_ui from CanOBD import Ui_MainWindow as CanOBD_ui from PyQt5.QtWidgets import QMessageBox import time #import threading class LogInViewUI(QtWidgets.QWidget,Login_ui): def int(self): super(LogInViewUI,self).int() self.setupUi(self) def connectslot(self): #self.m_loginbuttonOn.clicked.connect(self.login_button) self.mloginButton.clicked.connect(self.login_button) def login_button(self): if self.mPassWordEdit.acceptRichText() == “”: QMessageBox.warning(self, ‘警告’, ‘密码不能为空,请输入!’) return None # 1打开新窗口 CanOBDWidget.show() # 2关闭本窗口 LoginWidget.close() class CanOBDViewUI(QtWidgets.QWidget,CanOBD_ui): def int(self): super(CanOBDViewUI,self).int() self.setupUi(self) def update_tree_widgets(self, signals): self.signals_data = signals # 更新 Speed Tree # if “CCVS1” in signals: # self.mSpeedTreeWidget.topLevelItem(0).setText(3, f"{signals[‘CCVS1’]} km/h") # # # 更新 RPM Tree # if “EEC1_rpm” in signals: # self.mRPMTreeWidget.topLevelItem(0).setText(3, f"{signals[‘EEC1_rpm’]} rpm") # if “EEC1_torque” in signals: # self.mEECTreeWidget.topLevelItem(0).setText(3, f"{signals[‘EEC1_torque’]} %“) # # # 更新 VDHR (里程) # if “VDHR” in signals: # self.mVDHRTreeWidget.topLevelItem(0).setText(3, f”{signals[‘VDHR’]} km") # # # 更新 HOURS (工作时长) # if “HOURS” in signals: # self.mHoursTreeWidget.topLevelItem(0).setText(3, f"{signals[‘HOURS’]} hours") # # # 更新 LFE (平均油耗) # if “LFE” in signals: # self.mLFETreeWidget.topLevelItem(0).setText(3, f"{signals[‘LFE’]} L/100km") # # # 更新 ET1 (冷却液温度) # if “ET1” in signals: # self.mET1TreeWidget.topLevelItem(0).setText(3, f"{signals[‘ET1’]} °C") # # # 更新 AT1T1I (燃油液面) # if “AT1T1I” in signals: # self.mAT1T1ITreeWidget.topLevelItem(0).setText(3, f"{signals[‘AT1T1I’]} %") # # # 更新 ETC2 (档位) # if “ETC2” in signals: # self.mETC2TreeWidget.topLevelItem(0).setText(3, signals[‘ETC2’]) if name == “main”: app = QtWidgets.QApplication(sys.argv) #MainWidget = QtWidgets.QDockWidget() LoginWidget = QtWidgets.QWidget() CanOBDWidget = QtWidgets.QMainWindow() loginui = LogInViewUI() CanOBDui = CanOBDViewUI() loginui.setupUi(LoginWidget) loginui.connectslot() CanOBDui.setupUi(CanOBDWidget) CanOBDui.Serialconnectslot() LoginWidget.show() sys.exit(app.exec_()) 这是main文件 – 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 from SerialPro import SerialPort as SerialThread from SerialPro import CanOBDItemList from can_parser import parse_custom_can_frame import time import serial import serial.tools.list_ports 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.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) 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) 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 信息”)) 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.LSerial.Com_read_frame) # 连接信号和槽 self.thread.start() # 启动线程 #self.LSerial.Com_read_frame() 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 update_j1939_widgets(self, can_id, data): “”“根据 CAN ID 和原始数据更新对应的 J1939 显示控件”“” # 解析 CAN 帧 signals = parse_custom_can_frame(can_id, data) # 定义信号组与控件的映射关系 signal_group_map = { “CCVS1”: self.mSpeedTreeWidget, “EEC1_RPM”: self.mRPMTreeWidget, “VDHR”: self.mVDHRTreeWidget, “HOURS”: self.mHoursTreeWidget, “EEC1_Torque”: self.mEECTreeWidget, “ET1”: self.mET1TreeWidget, “AT1T1I”: self.mAT1T1ITreeWidget, “LFE”: self.mLFETreeWidget, “ETC2”: self.mETC2TreeWidget } # 遍历解析出的信号 for signal_name, signal_value in signals.items(): # 获取信号组前缀(例如 “CCVS1”) signal_group = signal_name.split('‘)[0] # 找到对应的 TreeWidget tree_widget = None for key, widget in signal_group_map.items(): if key.startswith(signal_group): tree_widget = widget break if not tree_widget: continue # 清除现有内容(可选,根据需要决定是否清除) tree_widget.clear() # 创建树节点 item = QtWidgets.QTreeWidgetItem(tree_widget) # 设置各列数据 item.setText(0, signal_name) # 信号名称 item.setText(1, f"0x{can_id:08X}“) # CAN ID (十六进制格式) # 格式化原始数据为十六进制字符串 if isinstance(data, bytes): hex_data = ’ '.join(f”{b:02X}" for b in data) else: hex_data = str(data) item.setText(2, hex_data) # 原始数据 # 显示信号值 item.setText(3, str(signal_value)) # 设置列宽自适应 for i in range(tree_widget.columnCount()): tree_widget.resizeColumnToContents(i) def parse_can_data(self, raw_data): “”“增强版CAN数据解析”“” try: # 尝试直接处理字节数据 if isinstance(raw_data, (bytes, bytearray)): return raw_data # 处理整数类型 if isinstance(raw_data, int): # 将整数转换为字节数组 length = (raw_data.bit_length() + 7) // 8 return raw_data.to_bytes(length, ‘big’) # 处理字符串类型 cleaned_str = str(raw_data).strip().upper() # 移除常见前缀 for prefix in [‘0X’, ‘\X’, ‘X’, ‘HEX:’]: if cleaned_str.startswith(prefix): cleaned_str = cleaned_str[len(prefix):] # 移除所有非十六进制字符 import re cleaned_str = re.sub(r’0-9A-F’, ‘’, cleaned_str) # 处理空字符串 if not cleaned_str: return b’’ # 处理奇数长度 if len(cleaned_str) % 2 != 0: cleaned_str = ‘0’ + cleaned_str # 高位补零 return bytes.fromhex(cleaned_str) except Exception as e: print(f"解析CAN数据错误: {raw_data} -> {e}“) return b’’ def CanOBDdatarefresh(self): index = 0 for indexitem in CanOBDItemList: itemTime = QTableWidgetItem(str(indexitem[0])) itemCanID = QTableWidgetItem(str(indexitem[1])) itemType = QTableWidgetItem(str(indexitem[2])) itemLen = QTableWidgetItem(str(indexitem[3])) itemData = QTableWidgetItem(str(indexitem[4])) self.tableWidget.setItem(index,0,itemTime) self.tableWidget.setItem(index, 1, itemCanID) self.tableWidget.setItem(index, 2, itemType) self.tableWidget.setItem(index, 3, itemLen) self.tableWidget.setItem(index, 4, itemData) # 新增:更新 J1939 显示 # try: # # 确保 CAN ID 是整数 # can_id = int(indexitem[1], 16) if isinstance(indexitem[1], str) else indexitem[1] # data = self.parse_can_data(item[4]) # 使用安全解析方法 # # self.update_j1939_widgets(can_id, data) # except Exception as e: # print(f"Error updating J1939 widgets: {e}”) index += 1 #CanOBDItemList.clear() self.tableWidget.show() class Worker(QThread): update_signal = pyqtSignal(int) # 定义一个信号,用于传递更新信息到主线程 def run(self): 模拟耗时操作 while True: time.sleep(0.001) self.update_signal.emit(1) # 发射信号,传递更新信息 这是canobd文件 import serial import time import struct from datetime import datetime CanOBDItemList = [[0, 0, 0, 0, 0]] 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.now().strftime(“%Y-%m-%d %H:%M:%S.%f”)[:-3], self.CanID = CanID, self.CanFrame = CanFramType, self.CanDataLen = Len, self.CanData = CanDataInf 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, timeout=0.1 # 添加串口读取超时 ) 添加超时检测机制 self.last_receive_time = time.time() # 记录最后接收时间 self.timeout_threshold = 1.5 # 超时阈值(秒) self.data_active = True # 初始状态为活跃 self.timeout_counter = 0 # 超时计数器 self.max_timeout_count = 3 # 连续超时3次后判定为停止 self.frame_count = 0 # 帧计数器 self.last_frame_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(): try: # 读取所有可用数据,最多1024字节 data = self.ser.read(min(1024, self.ser.in_waiting)) return data except serial.SerialException as e: print(f"接收数据错误: {e}”) return b’’ def del(self): self.close() def SerialIsOpen(self): return 1 if self.ser.isOpen() else 0 def Frame_check_sum(self, data, length): “”“计算校验和”“” checksum = 0 for i in range(length): checksum += data[i] return checksum % 0x100 # 检查数据超时状态 def check_data_timeout(self): “”“检查数据接收是否超时并更新状态”“” current_time = time.time() elapsed = current_time - self.last_receive_time # 检查帧计数是否变化 if self.frame_count == self.last_frame_count: # 帧计数未变化,更新超时计数器 if elapsed > self.timeout_threshold: self.timeout_counter += 1 else: # 有新帧,重置计数器和时间 self.timeout_counter = 0 self.last_frame_count = self.frame_count self.last_receive_time = current_time # 连续超时达到阈值 if self.timeout_counter >= self.max_timeout_count: if self.data_active: print(f"数据接收超时({elapsed:.2f}s),停止界面更新") self.data_active = False return True # 数据活跃状态 if not self.data_active and self.timeout_counter == 0: print(“检测到数据流,开始更新界面”) self.data_active = True return False # 发送数据并接收响应 def Com_read_frame(self): global frame_buffer global Frame_Data_Len # 检查超时状态 if self.check_data_timeout(): # 超时状态下不处理数据 return byte_data = self.recv() if byte_data: # 更新最后接收时间 self.last_receive_time = time.time() frame_buffer.extend(byte_data) # 添加缓冲区大小限制(1MB) if len(frame_buffer) > 1024 * 1024: # 保留最后512KB frame_buffer = frame_buffer[-512 * 1024:] # 如果没有足够数据,提前返回 if len(frame_buffer) < Frame_data_style_len: return # 查找帧起始和结束位置 start_index = frame_buffer.find(Frame_start) end_index = frame_buffer.find(Frame_end) # 如果找不到起始帧或结束帧 if start_index == -1 or end_index == -1: # 清除无效数据(保留最后几个字节) if len(frame_buffer) > 10: frame_buffer = frame_buffer[-10:] return # 处理帧起始和结束位置关系 if start_index + 1 == end_index: # 计算帧长度 if start_index + 3 >= len(frame_buffer): return # 数据不足 Frame_Data_Len = (frame_buffer[start_index + 3] << 8) | frame_buffer[start_index + 2] # 调整缓冲区,使起始位置在0 frame_buffer = frame_buffer[start_index:] start_index = 0 elif end_index < start_index: # 结束标志在起始标志前,调整缓冲区 frame_buffer = frame_buffer[start_index:] start_index = 0 # 重新查找结束位置 end_index = frame_buffer.find(Frame_end) # 检查是否有完整帧 if len(frame_buffer) >= Frame_Data_Len + 5: # 计算校验和 data_start = start_index + 2 data_end = data_start + Frame_Data_Len + 2 if data_end > len(frame_buffer): return # 数据不足 checksum = self.Frame_check_sum(frame_buffer[data_start:data_end], Frame_Data_Len + 2) expected_checksum = frame_buffer[start_index + Frame_Data_Len + 4] if checksum == expected_checksum: # 提取完整帧 frame_end_pos = start_index + Frame_Data_Len + 5 frame_procee = frame_buffer[start_index:frame_end_pos] # 处理帧数据 self.Frame_analoy_process(frame_procee) self.frame_count += 1 # 增加帧计数 # 更新缓冲区 if len(frame_buffer) == frame_end_pos: frame_buffer.clear() else: frame_buffer = frame_buffer[frame_end_pos:] Frame_Data_Len = 0 else: # 校验失败,跳过这一帧 skip_pos = start_index + Frame_Data_Len + 6 if skip_pos < len(frame_buffer): frame_buffer = frame_buffer[skip_pos:] else: frame_buffer.clear() Frame_Data_Len = 0 # 报文解析 def Frame_analoy_process(self, Framedata): # 检查数据活跃状态 if not self.data_active: return # 数据超时状态下不更新界面 # 检查帧类型 (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 for index in range(FrameNum): # 时间戳 Cantime = datetime.now().strftime(“%Y-%m-%d %H:%M:%S.%f”)[:-3] # 提取ID字节 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) CanFramType = “Cycle” Len = 8 # 提取数据部分 try: CanDataSpace = ’ '.join( format(Framedata[12 * index + 12 + posindex], ‘02X’) for posindex in range(8) ) except IndexError: continue CanItemData = [Cantime, CanID, CanFramType, Len, CanDataSpace] # 更新列表 if not CanOBDItemList or CanOBDItemList[0][0] == 0: if CanOBDItemList and CanOBDItemList[0][0] == 0: CanOBDItemList.pop(0) CanOBDItemList.insert(0, CanItemData) else: Listpos = self.find_in_2d_list(CanOBDItemList, CanID) if Listpos is not None: CanOBDItemList[Listpos[0]] = CanItemData else: CanOBDItemList.append(CanItemData) def find_in_2d_list(self, matrix, target): “”“在二维列表中查找目标”“” for i, row in enumerate(matrix): if target in row: return (i, row.index(target)) return None 这是serialpro文件,现在程序存在一定问题,就是当can数据停止发送后,tab1界面仍然有较长时间(时间大概跟发送can数据的时间有正关系)在刷新数据,要求在can数据停止发送后,tab界面能够较快地(1s内)停止刷新,现在有一个思路是清除缓冲区文件,你认为应该怎么做?
07-10
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值