feat: 更新README文档,增加系统架构和功能描述;在电力监控模块中新增电力消耗统计功能,并在主窗口中实现电力数据的实时更新;修改配置文件以禁用电力自动启动

This commit is contained in:
zhu-mengmeng 2025-07-15 17:01:09 +08:00
parent 94f8ae054c
commit 397c7398c8
6 changed files with 282 additions and 118 deletions

240
README.md
View File

@ -4,118 +4,136 @@
腾智微丝产线包装系统是一个基于PySide6Qt 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. **前端技术**
- PySide6Qt 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 APIAPI模式
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": {
"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`进行通信测试
- 检查网络连接和防火墙设置
- 验证Modbus寄存器地址配置

View File

@ -116,7 +116,7 @@
}
},
"electricity": {
"auto_start": true,
"auto_start": false,
"interval_minutes": 30
}
}

View File

@ -122,3 +122,55 @@ class ElectricityDAO:
except Exception as e:
logging.error(f"获取电力消耗数据失败: {str(e)}")
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
}

Binary file not shown.

View File

@ -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)}")

View File

@ -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 # 标识上料任务是否正在进行
@ -210,6 +210,10 @@ class MainWindow(MainWindowUI):
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):
"""获取托盘号对应的轴号"""
from dao.inspection_dao import InspectionDAO
@ -1636,30 +1640,73 @@ 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):
"""