From 397c7398c8a34ac2151569a79d8d66197c94c455 Mon Sep 17 00:00:00 2001 From: zhu-mengmeng <15588200382@163.com> Date: Tue, 15 Jul 2025 17:01:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0README=E6=96=87?= =?UTF-8?q?=E6=A1=A3=EF=BC=8C=E5=A2=9E=E5=8A=A0=E7=B3=BB=E7=BB=9F=E6=9E=B6?= =?UTF-8?q?=E6=9E=84=E5=92=8C=E5=8A=9F=E8=83=BD=E6=8F=8F=E8=BF=B0=EF=BC=9B?= =?UTF-8?q?=E5=9C=A8=E7=94=B5=E5=8A=9B=E7=9B=91=E6=8E=A7=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E4=B8=AD=E6=96=B0=E5=A2=9E=E7=94=B5=E5=8A=9B=E6=B6=88=E8=80=97?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=B9=B6=E5=9C=A8?= =?UTF-8?q?=E4=B8=BB=E7=AA=97=E5=8F=A3=E4=B8=AD=E5=AE=9E=E7=8E=B0=E7=94=B5?= =?UTF-8?q?=E5=8A=9B=E6=95=B0=E6=8D=AE=E7=9A=84=E5=AE=9E=E6=97=B6=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=EF=BC=9B=E4=BF=AE=E6=94=B9=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=BB=A5=E7=A6=81=E7=94=A8=E7=94=B5=E5=8A=9B=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=90=AF=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 252 +++++++++++++++++++++-------------- config/app_config.json | 2 +- dao/electricity_dao.py | 54 +++++++- db/jtDB.db | Bin 159744 -> 159744 bytes utils/electricity_monitor.py | 11 +- widgets/main_window.py | 81 ++++++++--- 6 files changed, 282 insertions(+), 118 deletions(-) diff --git a/README.md b/README.md index c08fd83..a96e7ba 100644 --- a/README.md +++ b/README.md @@ -4,118 +4,136 @@ 腾智微丝产线包装系统是一个基于PySide6(Qt for Python)开发的工业自动化控制系统,用于管理和监控微丝产线的包装过程。该系统集成了相机监控、称重、条码扫描、PLC通信等功能,为微丝产品的质量控制与包装提供全面的解决方案。 -## 项目架构 +## 系统架构 -系统采用典型的MVC(模型-视图-控制器)架构: +系统采用MVC(模型-视图-控制器)架构,并集成了多种工业通信协议: 1. **模型层(Model)**: - 采用DAO(数据访问对象)模式访问数据库 - 支持多种数据库(SQLite、PostgreSQL、MySQL) - - 主要数据表包括用户表、检验配置表、检验数据表、电力消耗表等 + - 核心数据表包括用户表、检验配置表、检验数据表、电力消耗表、托盘类型表等 2. **视图层(View)**: - 基于PySide6构建UI界面 - - 主要界面包括登录界面、主窗口、设置界面等 - - 采用分离的UI类设计,如LoginUI、MainWindowUI等 + - 界面包括登录界面、主窗口、设置界面、相机配置界面、检验设置等 + - 支持多语言界面(中文简体为主) 3. **控制器层(Controller)**: - - 主要控制逻辑在widgets目录下的类中实现 - - 使用信号槽机制进行组件间通信 - - 采用单例模式管理全局资源 + - 主要控制逻辑在widgets目录下实现 + - 使用Qt信号槽机制进行组件间通信 + - 采用单例模式管理全局资源和服务 4. **工具层(Utils)**: - - 提供各种工具类,如配置加载器、Modbus通信、串口管理等 - - 采用单例模式确保资源共享 + - 配置加载器(config_loader.py) + - 本地图像播放器(local_image_player.py) + - 串口管理器(serial_manager.py) + - Modbus通信工具(modbus_utils.py和modbus_monitor.py) + - 电力监控(electricity_monitor.py) + - 键盘监听器(keyboard_listener.py) + - 数据库工具(sql_utils.py) + - 检验配置管理器(inspection_config_manager.py) -5. **硬件集成层**: - - **相机子系统**:基于海康威视SDK进行集成 - - **串口通信**:与称重设备、扫描器等外设通信 +5. **硬件集成**: + - **相机子系统**:基于海康威视SDK集成(camera目录) + - **串口通信**:支持多种设备(称重设备、扫描器等) - **Modbus通信**:与PLC设备通信 - - **本地图像模式**:支持离线使用本地图像序列进行模拟测试 + - **本地图像模式**:支持离线使用本地图像序列 ## 技术栈 1. **前端技术**: - PySide6(Qt for Python)用于GUI开发 - - 使用Qt的信号槽机制实现组件间通信 + - Qt样式表(QSS)用于界面美化 2. **后端技术**: - - Python 3.7+作为主要开发语言 - - 支持SQLite、PostgreSQL和MySQL数据库 - - Modbus协议用于与PLC设备通信 - - 多线程处理耗时操作 + - Python 3.7+ + - 支持多种数据库(SQLite、PostgreSQL、MySQL) + - 多线程处理模型 + - 进程间通信 3. **通信技术**: - - Modbus TCP用于与PLC设备通信 - - 串口通信用于与称重设备、条码扫描器等外设通信 - - 支持键盘监听用于扫码枪等输入设备 + - Modbus TCP协议(与PLC设备通信) + - 串口通信(RS-232/485协议) + - HTTP RESTful API(API模式) 4. **设计模式**: - - 单例模式(配置加载器、电力监控器、相机管理器等) - - DAO模式(数据访问) - - 观察者模式(信号槽) + - 单例模式(ConfigLoader、ElectricityMonitor等) + - DAO模式(数据访问层) + - 观察者模式(基于Qt信号槽) - 工厂模式(数据库连接) + - 策略模式(不同通信协议的实现) ## 代码结构 1. **主要目录**: - - `widgets/`:包含所有窗口控制器类 - - `ui/`:包含所有UI定义类 - - `utils/`:包含工具类 - - `dao/`:包含数据访问对象 - - `db/`:包含数据库文件 - - `config/`:包含配置文件 - - `logs/`:包含日志文件 - - `camera/`:包含相机模块和SDK接口类 - - `apis/`:API接口(用于接口模式) + - `main.py`:程序入口,初始化应用 + - `widgets/`:窗口控制器类(包含主窗口、设置窗口、相机窗口等) + - `ui/`:UI定义类(使用PySide6的界面描述) + - `utils/`:工具类(配置、串口、Modbus、电力监控等) + - `dao/`:数据访问对象(用户、检验、电力数据等) + - `db/`:数据库文件和工具 + - `config/`:配置文件目录 + - `logs/`:日志文件存储目录 + - `camera/`:相机模块和SDK集成 + - `apis/`:API接口定义(用于接口模式) + - `inspection/`:检验相关业务逻辑 -2. **核心文件**: - - `main.py`:程序入口点 - - `modbus_server.py`:Modbus服务器模拟 - - `utils/config_loader.py`:配置加载器 - - `utils/local_image_player.py`:本地图像序列播放器 - - `widgets/camera_manager.py`:相机管理器 - - `dao/login_dao.py`:用户认证数据访问 +2. **关键组件**: + - **主窗口**(`widgets/main_window.py`):系统核心界面,集成所有功能 + - **相机管理器**(`widgets/camera_manager.py`):管理相机设备连接与图像获取 + - **串口管理器**(`utils/serial_manager.py`):管理多个串口设备连接 + - **Modbus工具**(`utils/modbus_utils.py`):Modbus协议通信 + - **电力监控**(`utils/electricity_monitor.py`):监控电力消耗 + - **检验DAO**(`dao/inspection_dao.py`):处理检验数据存储与检索 + - **本地图像播放器**(`utils/local_image_player.py`):模拟相机图像源 ## 系统特点与功能 -1. **两种运行模式**: - - 单机模式(standalone):完整的独立系统 - - 接口模式(api):作为其他系统的接口组件运行 +1. **运行模式**: + - **单机模式**(standalone):完整独立系统 + - **API模式**(api):作为其他系统的接口组件运行 + - **开发/测试模式**:支持模拟器和测试工具 2. **本地图像模式**: - - 支持本地图像序列播放,模拟相机实时画面 - - 适用于开发测试和演示场景,无需连接实际相机设备 - - 可调整播放帧率、设置循环播放等参数 + - 支持从本地图像序列模拟相机画面 + - 可配置播放速率、循环模式 + - 支持多种图像格式(JPG、PNG、BMP等) 3. **相机管理**: - - 支持海康威视相机设备管理 - - 自动发现和枚举可用相机设备 - - 相机参数设置和保存(曝光、增益、帧率等) + - 支持海康威视相机设备 + - 相机参数配置(曝光、增益、帧率等) + - 实时图像显示和处理 -4. **Modbus通信**: - - 与PLC设备进行Modbus TCP通信 - - 支持自动模式和手动模式切换 - - 内置Modbus服务器模拟功能,便于开发测试 +4. **通信功能**: + - **Modbus通信**:支持读写PLC寄存器 + - **串口通信**:支持多种设备协议(称重设备、扫描器等) + - **键盘监听**:支持扫码枪等输入设备 -5. **电力监控**: - - 实时监控和记录电力消耗数据 - - 数据可视化和统计分析 +5. **检验管理**: + - 检验配置管理 + - 检验数据记录和分析 + - 检验报告生成 -6. **数据管理**: - - 支持用户认证和权限控制 - - 检验数据的采集、存储和分析 - - 托盘类型管理 +6. **电力监控**: + - 定时记录电力消耗数据 + - 电力数据分析和报表 -7. **日志系统**: - - 详细的日志记录 +7. **安全与用户管理**: + - 用户认证系统 + - 操作日志记录 + - 系统异常处理和恢复 + +8. **日志系统**: + - 多级日志记录 - 自动日志轮换 - - 支持多级日志级别 + - 异常信息捕获与记录 ## 安装与运行 ### 系统要求 - Python 3.7+ +- PySide6 6.2.0+ +- 相关依赖库(见requirements.txt) - 支持Windows、macOS、Linux操作系统 ### 安装步骤 @@ -128,7 +146,7 @@ 2. 配置应用: - 配置文件位于`config/app_config.json` - 默认使用SQLite数据库,位于`db/jtDB.db` - - 可配置数据库连接、相机参数、Modbus设置等 + - 可配置数据库连接、相机参数、串口设置等 3. 运行应用: ``` @@ -137,7 +155,21 @@ ### 配置说明 -1. **数据库配置**: +1. **应用配置**: + ```json + "app": { + "name": "腾智微丝产线包装系统", + "version": "1.0.0", + "features": { + "enable_serial_ports": true, + "enable_keyboard_listener": false, + "enable_camera": false + }, + "mode": "api" // 或 "standalone" + } + ``` + +2. **数据库配置**: ```json "database": { "default": "sqlite", @@ -157,43 +189,64 @@ } ``` -2. **本地图像模式配置**: +3. **相机配置**: ```json - "local_image_mode": { - "enabled": true, - "folder_path": "/path/to/images", - "framerate": 15, - "loop": true + "camera": { + "enabled": false, + "default_exposure": 20000, + "default_gain": 10, + "default_framerate": 30, + "local_mode": { + "enabled": true, + "folder_path": "/path/to/images", + "framerate": 15, + "loop": true + } } ``` -3. **应用模式配置**: +4. **Modbus配置**: ```json - "app": { - "mode": "standalone", // 或 "api" - "features": { - "enable_serial_ports": false, - "enable_keyboard_listener": false, - "enable_camera": false - } + "modbus": { + "host": "localhost", + "port": "5020" + } + ``` + +5. **串口配置**: + ```json + "serial": { + "cz": { // 称重设备 + "code": "cz", + "ser": "COM2", + "port": "9600", + "data_bits": 8, + "stop_bits": 1, + "parity": "N", + "timeout": 1 + }, + // 其他串口设备配置... } ``` ## 开发与扩展 -系统采用模块化设计,可以方便地进行功能扩展: +系统设计采用模块化和松耦合原则,便于扩展: -1. **添加新的数据源**:扩展DAO层,实现对应的数据访问对象 +1. **添加新的数据源**: + - 在`dao/`目录下创建新的DAO类 + - 遵循现有DAO模式和接口约定 2. **添加新的硬件支持**: - - 参考`widgets/camera_manager.py`添加新的硬件管理器 - - 参考`utils/local_image_player.py`添加新的设备模拟器 + - 在`utils/`目录下创建新的硬件管理类 + - 实现相应的信号机制与主程序通信 3. **扩展UI界面**: - - 在`ui/`目录下创建新的UI类 - - 在`widgets/`目录下创建对应的控制器类 + - 在`ui/`目录下创建新的UI定义类 + - 在`widgets/`目录下创建对应控制器类 -4. **添加新的通信协议**:参考`modbus_server.py`实现新的通信接口 +4. **添加新的通信协议**: + - 参考`utils/modbus_utils.py`和`utils/serial_manager.py`实现 ## 注意事项 @@ -201,24 +254,29 @@ 2. 日志文件位于`logs/`目录,按日期自动轮换 -3. 如果使用相机功能,需确保已安装海康威视SDK +3. 使用相机功能前,确保已安装相应的SDK -4. 在测试环境中,可使用本地图像模式替代实际相机 +4. 在开发/测试环境中,可使用本地图像模式和Modbus服务器模拟 + +5. 系统支持热插拔设备,但建议在操作前确认设备状态 ## 故障排除 1. **日志查看**: - - 检查`logs/`目录下的日志文件 - - 日志格式为`app_YYYY-MM-DD.log` + - 检查`logs/`目录下的日志文件(`app_YYYY-MM-DD.log`格式) + - 查看控制台输出信息 2. **数据库问题**: - 确认数据库配置正确 - - 查看数据库连接错误日志 + - 检查数据库文件权限 + - 可使用SQLite浏览工具查看数据库内容 -3. **相机连接问题**: - - 确认相机驱动已正确安装 - - 尝试启用本地图像模式进行测试 +3. **设备连接问题**: + - 检查设备是否正确连接 + - 确认设备驱动已安装 + - 验证设备配置参数(串口、波特率等) 4. **Modbus通信问题**: - - 检查PLC设备IP和端口配置 - - 使用`modbus_server.py`进行通信测试 \ No newline at end of file + - 使用`modbus_server.py`进行通信测试 + - 检查网络连接和防火墙设置 + - 验证Modbus寄存器地址配置 \ No newline at end of file diff --git a/config/app_config.json b/config/app_config.json index 921f301..27e282e 100644 --- a/config/app_config.json +++ b/config/app_config.json @@ -116,7 +116,7 @@ } }, "electricity": { - "auto_start": true, + "auto_start": false, "interval_minutes": 30 } } \ No newline at end of file diff --git a/dao/electricity_dao.py b/dao/electricity_dao.py index e005d08..97de7f6 100644 --- a/dao/electricity_dao.py +++ b/dao/electricity_dao.py @@ -121,4 +121,56 @@ class ElectricityDAO: return data_list except Exception as e: logging.error(f"获取电力消耗数据失败: {str(e)}") - return [] \ No newline at end of file + return [] + + def get_electricity_statistics(self): + """获取电力消耗统计数据(日/月/年/累计) + + Returns: + dict: 包含日、月、年、累计用电量的字典 + """ + try: + # 使用提供的SQL查询 + sql = """ + SELECT CASE + WHEN sync_time >= DATE('now') AND sync_time < DATE('now', '+1 day') + THEN MAX(electricity_number) - MIN(electricity_number) + ELSE 0 END AS electricity_number_day, + CASE + WHEN sync_time >= DATE('now', 'start of month') AND sync_time < DATE('now', 'start of month', '+1 month') + THEN MAX(electricity_number) - MIN(electricity_number) + ELSE 0 END AS electricity_number_month, + CASE + WHEN sync_time >= DATE('now', 'start of year') AND sync_time < DATE('now', 'start of year', '+1 year') + THEN MAX(electricity_number) - MIN(electricity_number) + ELSE 0 END AS electricity_number_year, + MAX(electricity_number) - MIN(electricity_number) AS electricity_number_all + FROM wsbz_electricity_consumption + """ + + self.db.cursor.execute(sql) + row = self.db.cursor.fetchone() + + if row: + data = { + 'day': row[0] if row[0] is not None else 0, + 'month': row[1] if row[1] is not None else 0, + 'year': row[2] if row[2] is not None else 0, + 'all': row[3] if row[3] is not None else 0 + } + return data + else: + return { + 'day': 0, + 'month': 0, + 'year': 0, + 'all': 0 + } + except Exception as e: + logging.error(f"获取电力消耗统计数据失败: {str(e)}") + return { + 'day': 0, + 'month': 0, + 'year': 0, + 'all': 0 + } \ No newline at end of file diff --git a/db/jtDB.db b/db/jtDB.db index 109d6258be3ff813641462a13c4073da983e1c95..056fe3b8684e5f34da132a5a5800aca03096e68e 100644 GIT binary patch delta 237 zcmZp8z}fJCbAmLZ^+XwGM(d3Uf9)8Po8#=;7BMb2fNmykJ&BP}pqRW_BnHpIc1N9_emoXBb l5RXL$XqOStxHwEH6ALQ?bMXnWSY(W?j4Zbc JLU~N@7y<3K5taY| diff --git a/utils/electricity_monitor.py b/utils/electricity_monitor.py index dba3bc5..d9a6a44 100644 --- a/utils/electricity_monitor.py +++ b/utils/electricity_monitor.py @@ -1,17 +1,21 @@ import logging import time from threading import Thread, Event -from PySide6.QtCore import QTimer +from PySide6.QtCore import QTimer, QObject, Signal from utils.modbus_utils import ModbusUtils from utils.modbus_monitor import RegisterHandler from dao.electricity_dao import ElectricityDAO from utils.config_loader import ConfigLoader -class ElectricityHandler(RegisterHandler): +class ElectricityHandler(RegisterHandler, QObject): """电力消耗寄存器处理器""" + # 定义电力数据变化信号 + electricity_data_changed = Signal(float) + def __init__(self): """初始化处理器""" + QObject.__init__(self) self.dao = ElectricityDAO() # 确保表已创建 self.dao.create_table_if_not_exists() @@ -26,6 +30,9 @@ class ElectricityHandler(RegisterHandler): # 保存电力消耗数据 self.dao.save_electricity_data(value) logging.info(f"已记录电力消耗数据: {value}") + + # 发射电力数据变化信号 + self.electricity_data_changed.emit(value) except Exception as e: logging.error(f"处理电力消耗数据时发生错误: {str(e)}") diff --git a/widgets/main_window.py b/widgets/main_window.py index 1816688..fbf9092 100644 --- a/widgets/main_window.py +++ b/widgets/main_window.py @@ -87,7 +87,7 @@ class MainWindow(MainWindowUI): # 初始化拆垛和下料相关的属性 self._current_stow_num = 0 # 当前拆垛层数 self._current_unload_num = 0 # 当前下料层数 - self._total_unload_num = 0 # 总下料层数 + self._total_unload_num = 0 self._current_unload_info = None # 存储当前下料信息 self._loading_info = None # 存储上料对话框的信息 self._is_loading_active = False # 标识上料任务是否正在进行 @@ -209,6 +209,10 @@ class MainWindow(MainWindowUI): self.restore_input_button_style() if hasattr(self, 'restore_output_button_style'): self.restore_output_button_style() + + # 启动Modbus监控,确保电力消耗数据在应用启动时就能显示 + self.setup_modbus_monitor() + logging.info("主窗口初始化时已启动Modbus监控系统") def get_axios_num(self,tray_id): """获取托盘号对应的轴号""" @@ -1636,31 +1640,74 @@ class MainWindow(MainWindowUI): monitor.register_handler(4, UnloadingLevelHandler(self.handle_unloading_level)) monitor.register_handler(5, UnloadingPositionHandler(self.handle_unloading_position)) - # 注册电力消耗处理器 - monitor.register_handler(30, ElectricityHandler()) + # 注册电力消耗处理器并保存引用以便连接信号 + self.electricity_handler = ElectricityHandler() + monitor.register_handler(30, self.electricity_handler) logging.info("已注册所有Modbus寄存器处理器") def _connect_modbus_signals(self): - """连接Modbus信号槽""" - # 连接监控器状态信号 + """连接Modbus信号""" + # 连接Modbus状态变化信号 self.modbus_monitor.monitor_status_changed.connect(self.handle_modbus_status_change) - self.modbus_monitor.register_error.connect(self.handle_register_error) - self.machine_handlers.ng_changed.connect(self.handle_ng) - # 直接连接寄存器变化信号 + + # 连接寄存器变化信号 self.modbus_monitor.register_changed.connect(self.handle_register_change) - # 连接机器状态信号 - self.machine_handlers.loading_feedback_changed.connect(self.handle_loading_feedback) - self.machine_handlers.unloading_feedback_changed.connect(self.handle_unloading_feedback) - self.machine_handlers.error_1_changed.connect(self.handle_error_1) - self.machine_handlers.error_2_changed.connect(self.handle_error_2) - self.machine_handlers.error_3_changed.connect(self.handle_error_3) + # 连接寄存器错误信号 + self.modbus_monitor.register_error.connect(self.handle_register_error) - # 连接称重数据和贴标信号 - self.machine_handlers.weight_changed.connect(self.handle_weight_data) - self.machine_handlers.label_signal_changed.connect(self.handle_label_signal) + # 连接电力数据变化信号 + self.electricity_handler.electricity_data_changed.connect(self.update_electricity_statistics) + # 立即更新一次用电量数据 + self.update_electricity_statistics() + + logging.info("已连接所有Modbus信号") + + def update_electricity_statistics(self, value=None): + """更新电力消耗统计数据到项目表格 + + Args: + value: 可选的当前电力消耗值,用于信号触发时 + """ + try: + from dao.electricity_dao import ElectricityDAO + electricity_dao = ElectricityDAO() + + # 获取电力消耗统计数据 + statistics = electricity_dao.get_electricity_statistics() + + # 设置表格项(日、月、年、累计用电量) + # 当日用电量 + day_item = QTableWidgetItem(str(round(statistics['day'], 2))) + day_item.setTextAlignment(Qt.AlignCenter) + self.project_table.setItem(0, 0, day_item) + + # 当月用电量 + month_item = QTableWidgetItem(str(round(statistics['month'], 2))) + month_item.setTextAlignment(Qt.AlignCenter) + self.project_table.setItem(1, 0, month_item) + + # 当年用电量 + year_item = QTableWidgetItem(str(round(statistics['year'], 2))) + year_item.setTextAlignment(Qt.AlignCenter) + self.project_table.setItem(2, 0, year_item) + + # 累计用电量 + all_item = QTableWidgetItem(str(round(statistics['all'], 2))) + all_item.setTextAlignment(Qt.AlignCenter) + self.project_table.setItem(3, 0, all_item) + + # 只在调试级别输出详细信息,减少日志量 + if value is not None: + logging.info(f"电力数据变化触发UI更新:当前值={value}") + else: + logging.debug(f"已更新电力消耗统计数据: 日={statistics['day']}, 月={statistics['month']}, 年={statistics['year']}, 累计={statistics['all']}") + + except Exception as e: + logging.error(f"更新电力消耗统计数据失败: {str(e)}") + def _convert_to_kg(self, weight_in_g): """ 将克转换为千克