From 320d715f83f0dbaec6b8f8ae0b6ba19e61d1f09d Mon Sep 17 00:00:00 2001 From: zhu-mengmeng <15588200382@163.com> Date: Thu, 26 Jun 2025 18:26:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E4=BF=AE=E6=AD=A3=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/app_config.json | 5 +- dao/electricity_dao.py | 124 ++++++++++++++++++ db/jtDB.db | Bin 86016 -> 102400 bytes db/schema.sql | 24 ++-- from pymodbus.py | 4 +- main.py | 15 +++ ui/electricity_settings_ui.py | 231 ++++++++++++++++++++++++++++++++++ utils/electricity_monitor.py | 193 ++++++++++++++++++++++++++++ utils/init_db.py | 11 ++ utils/modbus_monitor.py | 2 +- widgets/login_widget.py | 2 +- widgets/main_window.py | 32 +++-- widgets/settings_window.py | 6 + 13 files changed, 619 insertions(+), 30 deletions(-) create mode 100644 dao/electricity_dao.py create mode 100644 ui/electricity_settings_ui.py create mode 100644 utils/electricity_monitor.py diff --git a/config/app_config.json b/config/app_config.json index e630079..fe7ce47 100644 --- a/config/app_config.json +++ b/config/app_config.json @@ -8,7 +8,7 @@ "enable_camera": false }, "base_url": "http://localhost:8084", - "mode": "api" + "mode": "standalone" }, "apis": { "get_tray_info": "/apjt/xcsc/tpda/getByTp_note/", @@ -79,5 +79,8 @@ "stop_bits": 1, "timeout": 1 } + }, + "electricity": { + "auto_start": true } } \ No newline at end of file diff --git a/dao/electricity_dao.py b/dao/electricity_dao.py new file mode 100644 index 0000000..e005d08 --- /dev/null +++ b/dao/electricity_dao.py @@ -0,0 +1,124 @@ +import logging +from datetime import datetime +from utils.sql_utils import SQLUtils + +class ElectricityDAO: + """电力消耗数据访问对象""" + + def __init__(self): + """初始化数据访问对象""" + self.db = SQLUtils('sqlite', database='db/jtDB.db') + + def __del__(self): + """析构函数,确保数据库连接关闭""" + if hasattr(self, 'db'): + self.db.close() + + def create_table_if_not_exists(self): + """创建电力消耗表(如果不存在)""" + try: + sql = """ + CREATE TABLE IF NOT EXISTS wsbz_electricity_consumption ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + sync_time TIMESTAMP, + electricity_number REAL + ) + """ + self.db.execute_query(sql) + logging.info("电力消耗表检查/创建成功") + return True + except Exception as e: + logging.error(f"创建电力消耗表失败: {str(e)}") + return False + + def save_electricity_data(self, electricity_number): + """保存电力消耗数据 + + Args: + electricity_number: 电力消耗数值 + + Returns: + bool: 保存是否成功 + """ + try: + current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + + sql = """ + INSERT INTO wsbz_electricity_consumption ( + sync_time, electricity_number + ) VALUES (?, ?) + """ + params = (current_time, electricity_number) + + self.db.execute_update(sql, params) + logging.info(f"保存电力消耗数据成功: {electricity_number}") + return True + except Exception as e: + logging.error(f"保存电力消耗数据失败: {str(e)}") + return False + + def get_latest_electricity_data(self): + """获取最新的电力消耗数据 + + Returns: + dict: 最新的电力消耗数据,未找到则返回None + """ + try: + sql = """ + SELECT id, sync_time, electricity_number + FROM wsbz_electricity_consumption + ORDER BY id DESC + LIMIT 1 + """ + + self.db.cursor.execute(sql) + row = self.db.cursor.fetchone() + + if row: + data = { + 'id': row[0], + 'sync_time': row[1], + 'electricity_number': row[2] + } + return data + else: + return None + except Exception as e: + logging.error(f"获取最新电力消耗数据失败: {str(e)}") + return None + + def get_electricity_data_by_date_range(self, start_date, end_date): + """根据日期范围获取电力消耗数据 + + Args: + start_date: 开始日期(YYYY-MM-DD格式) + end_date: 结束日期(YYYY-MM-DD格式) + + Returns: + list: 电力消耗数据列表 + """ + try: + sql = """ + SELECT id, sync_time, electricity_number + FROM wsbz_electricity_consumption + WHERE sync_time BETWEEN ? AND ? + ORDER BY sync_time + """ + params = (f"{start_date} 00:00:00", f"{end_date} 23:59:59") + + self.db.cursor.execute(sql, params) + results = self.db.cursor.fetchall() + + data_list = [] + for row in results: + data = { + 'id': row[0], + 'sync_time': row[1], + 'electricity_number': row[2] + } + data_list.append(data) + + return data_list + except Exception as e: + logging.error(f"获取电力消耗数据失败: {str(e)}") + return [] \ No newline at end of file diff --git a/db/jtDB.db b/db/jtDB.db index 06d3ca51e17ff936fb42ea050e0447fd2eab10a1..388c3a4deaf66aae23d9bafe4c71e5a5a3e439f7 100644 GIT binary patch literal 102400 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 delta 694 zcmZozz}B#Ub%K--=S~I&22mh}f$)hsMogSLHzv&I=a%FJiZb$_W#B)%Sy16HKaU|J z7lWjvu_OZnhv4L!@nZeK#SQJ%O&`k zO&O;jWMdRzla}OVHf5STflY*oNoSJ-i^Fmj14eRjOgSsRqqp_rM zvbezHxZTqAmR2T~Rt83TW|kI~hNfO2@s2LOo_;VBj7$^^v8(V3aSjLxFfuhTGcYkT zWd;UkI|BnJgQKXUCnY(wVbb zv?f1XBt3cK{{Ub-gG^)K%j2`=eaX3&Gl4~d!I9%3+q{W&>RTNdfAMa%<~zVBuVHAQ zsmaT0$H2~H!@#|cyOlkPU7W>>$%c0m0|S!@&zeJeB^XoLw-@j+PGDpKT3!NlQNeaUF-AY8WeSW20K!D2 ArT_o{ diff --git a/db/schema.sql b/db/schema.sql index a20f36d..5f27921 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -55,25 +55,23 @@ SET enum_values = '["A区", "B区", "C区", "D区"]' WHERE name = 'fzd' AND is_deleted = FALSE; -- 包装记录表 -drop table if exists wsbz_inspection_pack_data; -create table if not exists wsbz_inspection_pack_data +DROP TABLE IF EXISTS wsbz_inspection_pack_data; +CREATE TABLE IF NOT EXISTS wsbz_inspection_pack_data ( - --订单号 - order_id VARCHAR(50), - --材质 - material VARCHAR(50), - --规格 - spec VARCHAR(50), - --包装号 - tray_id VARCHAR(50), - --轴包装号 + id INTEGER PRIMARY KEY AUTOINCREMENT, + order_id VARCHAR(50) NOT NULL, + tray_id VARCHAR(50) NOT NULL, + gc_note VARCHAR(50), axis_package_id VARCHAR(50), + weight REAL, + net_weight REAL, + pack_time TIMESTAMP, create_time TIMESTAMP NOT NULL, create_by VARCHAR(50) NOT NULL, update_time TIMESTAMP, update_by VARCHAR(50), - is_deleted BOOLEAN -); + is_deleted BOOLEAN DEFAULT FALSE +) -- 创建托盘类型配置表 CREATE TABLE IF NOT EXISTS pallet_types ( diff --git a/from pymodbus.py b/from pymodbus.py index 7a6dc49..1c44735 100644 --- a/from pymodbus.py +++ b/from pymodbus.py @@ -2,14 +2,14 @@ from pymodbus.client import ModbusTcpClient import time client = ModbusTcpClient('localhost', port=5020) client.connect() -client.write_registers(address=11, values=[2242]) +client.write_registers(address=11, values=[2248]) client.write_registers(address=13, values=[0]) client.write_registers(address=21, values=[0]) time.sleep(2) # client.write_registers(address=21, values=[1]) -client.write_registers(address=20, values=[0]) +# client.write_registers(address=30, values=[25]) # client.write_registers(address=5, values=[16]) # 贴标完成 # client.write_registers(address=24, values=[1])s diff --git a/main.py b/main.py index 7d3ee9f..8aaaae3 100644 --- a/main.py +++ b/main.py @@ -134,6 +134,12 @@ def main(): # 键盘监听器配置信息 enable_keyboard_listener = config.get_value('serial.keyboard.enabled', False) logging.info(f"配置信息 - 启用串口: {enable_serial_ports}, 启用键盘监听: {enable_keyboard_listener}") + + # 初始化电力监控器 + from utils.electricity_monitor import ElectricityMonitor + electricity_monitor = ElectricityMonitor.get_instance() + electricity_monitor.start() + logging.info("电力监控器已启动") # 设置中文翻译器 translator = QTranslator(app) @@ -200,6 +206,15 @@ def main(): from utils.sql_utils import SQLUtils SQLUtils.close_all_connections() + # 停止电力监控器 + try: + from utils.electricity_monitor import ElectricityMonitor + electricity_monitor = ElectricityMonitor.get_instance() + electricity_monitor.stop() + logging.info("电力监控器已停止") + except Exception as e: + logging.error(f"停止电力监控器时发生错误: {e}") + sys.exit(exit_code) except Exception as e: diff --git a/ui/electricity_settings_ui.py b/ui/electricity_settings_ui.py new file mode 100644 index 0000000..26d340f --- /dev/null +++ b/ui/electricity_settings_ui.py @@ -0,0 +1,231 @@ +from PySide6.QtWidgets import ( + QWidget, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, + QFormLayout, QSpinBox, QGroupBox, QFrame +) +from PySide6.QtCore import Qt, Signal, QTimer +from PySide6.QtGui import QFont +from datetime import datetime, timedelta +from utils.electricity_monitor import ElectricityMonitor +from dao.electricity_dao import ElectricityDAO +import logging + +class ElectricitySettingsUI(QWidget): + """电力监控设置UI组件""" + + # 定义信号 + settings_changed = Signal() + + def __init__(self, parent=None): + super().__init__(parent) + self.parent = parent + self.electricity_monitor = ElectricityMonitor.get_instance() + self.electricity_dao = ElectricityDAO() + + # 确保电力消耗表已创建 + self.electricity_dao.create_table_if_not_exists() + + self.init_ui() + + # 创建定时器,用于更新UI + self.update_timer = QTimer(self) + self.update_timer.timeout.connect(self.update_status) + self.update_timer.start(1000) # 每秒更新一次 + + # 初始更新状态 + self.update_status() + + def init_ui(self): + """初始化UI""" + # 设置字体 + self.title_font = QFont("微软雅黑", 14, QFont.Bold) + self.normal_font = QFont("微软雅黑", 10) + self.small_font = QFont("微软雅黑", 9) + + # 创建主布局 + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(20, 20, 20, 20) + main_layout.setSpacing(15) + + # 创建标题 + title_label = QLabel("电力监控设置") + title_label.setFont(self.title_font) + title_label.setAlignment(Qt.AlignCenter) + title_label.setStyleSheet("color: #1a237e; margin-bottom: 10px;") + main_layout.addWidget(title_label) + + # 创建状态组 + status_group = QGroupBox("监控状态") + status_group.setFont(self.normal_font) + status_layout = QFormLayout() + + # 监控状态 + self.status_label = QLabel("未知") + self.status_label.setFont(self.normal_font) + status_layout.addRow("监控状态:", self.status_label) + + # 当前电量 + self.electricity_label = QLabel("--") + self.electricity_label.setFont(self.normal_font) + status_layout.addRow("当前电量:", self.electricity_label) + + # 上次监听时间 + self.last_time_label = QLabel("--") + self.last_time_label.setFont(self.normal_font) + status_layout.addRow("上次监听时间:", self.last_time_label) + + # 下次监听时间 + self.next_time_label = QLabel("--") + self.next_time_label.setFont(self.normal_font) + status_layout.addRow("下次监听时间:", self.next_time_label) + + status_group.setLayout(status_layout) + main_layout.addWidget(status_group) + + # 创建设置组 + settings_group = QGroupBox("监控设置") + settings_group.setFont(self.normal_font) + settings_layout = QFormLayout() + + # 监听间隔 + self.interval_spinbox = QSpinBox() + self.interval_spinbox.setFont(self.normal_font) + self.interval_spinbox.setRange(1, 60) # 1-60分钟 + self.interval_spinbox.setValue(self.electricity_monitor.interval_minutes) # 使用当前设置的值 + self.interval_spinbox.setSuffix(" 分钟") + self.interval_spinbox.valueChanged.connect(self.on_interval_changed) + settings_layout.addRow("监听间隔:", self.interval_spinbox) + + settings_group.setLayout(settings_layout) + main_layout.addWidget(settings_group) + + # 创建按钮组 + button_layout = QHBoxLayout() + button_layout.setSpacing(20) + + # 开始监听按钮 + self.start_button = QPushButton("开始监听") + self.start_button.setFont(self.normal_font) + self.start_button.setMinimumHeight(40) + self.start_button.setStyleSheet("background-color: #4CAF50; color: white;") + self.start_button.clicked.connect(self.start_monitoring) + button_layout.addWidget(self.start_button) + + # 暂停监听按钮 + self.stop_button = QPushButton("暂停监听") + self.stop_button.setFont(self.normal_font) + self.stop_button.setMinimumHeight(40) + self.stop_button.setStyleSheet("background-color: #F44336; color: white;") + self.stop_button.clicked.connect(self.stop_monitoring) + button_layout.addWidget(self.stop_button) + + main_layout.addLayout(button_layout) + + # 添加分隔线 + separator = QFrame() + separator.setFrameShape(QFrame.HLine) + separator.setFrameShadow(QFrame.Sunken) + main_layout.addWidget(separator) + + # 添加历史数据标签 + history_label = QLabel("历史数据") + history_label.setFont(self.title_font) + history_label.setAlignment(Qt.AlignCenter) + history_label.setStyleSheet("color: #1a237e; margin: 10px 0;") + main_layout.addWidget(history_label) + + # 占位符,用于未来扩展 + placeholder = QLabel("历史数据功能将在未来版本中提供") + placeholder.setFont(self.normal_font) + placeholder.setAlignment(Qt.AlignCenter) + placeholder.setStyleSheet("color: #888888; padding: 20px;") + main_layout.addWidget(placeholder) + + # 添加弹性空间 + main_layout.addStretch(1) + + def update_status(self): + """更新状态显示""" + try: + # 检查监控器状态 + is_monitoring = self.electricity_monitor.is_monitoring() + + # 更新状态标签 + if is_monitoring: + self.status_label.setText("正在监听") + self.status_label.setStyleSheet("color: green; font-weight: bold;") + self.start_button.setEnabled(False) + self.stop_button.setEnabled(True) + else: + self.status_label.setText("已暂停") + self.status_label.setStyleSheet("color: red; font-weight: bold;") + self.start_button.setEnabled(True) + self.stop_button.setEnabled(False) + + # 获取最新电力数据 + latest_data = self.electricity_dao.get_latest_electricity_data() + if latest_data: + self.electricity_label.setText(str(latest_data['electricity_number'])) + self.last_time_label.setText(latest_data['sync_time']) + + # 计算下次监听时间 + if is_monitoring: + next_time = self.electricity_monitor.get_next_read_time() + if next_time: + self.next_time_label.setText(next_time.strftime('%Y-%m-%d %H:%M:%S')) + else: + self.next_time_label.setText("计算中...") + else: + self.next_time_label.setText("监听已暂停") + else: + self.electricity_label.setText("--") + self.last_time_label.setText("--") + if is_monitoring: + self.next_time_label.setText("等待首次读取...") + else: + self.next_time_label.setText("监听已暂停") + + except Exception as e: + logging.error(f"更新电力监控状态时发生错误: {str(e)}") + + def on_interval_changed(self, value): + """监听间隔变更处理 + + Args: + value: 新的间隔值(分钟) + """ + try: + # 更新监控器间隔 + self.electricity_monitor.set_interval(value) + + # 发出设置变更信号 + self.settings_changed.emit() + except Exception as e: + logging.error(f"设置电力监控间隔时发生错误: {str(e)}") + + def start_monitoring(self): + """开始监听""" + try: + # 启动电力监控器 + self.electricity_monitor.start() + + # 更新UI + self.update_status() + + # 发出设置变更信号 + self.settings_changed.emit() + except Exception as e: + logging.error(f"启动电力监控时发生错误: {str(e)}") + + def stop_monitoring(self): + """暂停监听""" + try: + # 停止电力监控器 + self.electricity_monitor.stop() + + # 更新UI + self.update_status() + + # 发出设置变更信号 + self.settings_changed.emit() + except Exception as e: + logging.error(f"停止电力监控时发生错误: {str(e)}") \ No newline at end of file diff --git a/utils/electricity_monitor.py b/utils/electricity_monitor.py new file mode 100644 index 0000000..dba3bc5 --- /dev/null +++ b/utils/electricity_monitor.py @@ -0,0 +1,193 @@ +import logging +import time +from threading import Thread, Event +from PySide6.QtCore import QTimer +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): + """电力消耗寄存器处理器""" + + def __init__(self): + """初始化处理器""" + self.dao = ElectricityDAO() + # 确保表已创建 + self.dao.create_table_if_not_exists() + + def handle_change(self, value): + """处理寄存器值变化 + + Args: + value: 寄存器值 + """ + try: + # 保存电力消耗数据 + self.dao.save_electricity_data(value) + logging.info(f"已记录电力消耗数据: {value}") + except Exception as e: + logging.error(f"处理电力消耗数据时发生错误: {str(e)}") + + +class ElectricityMonitor: + """电力消耗监控器""" + + _instance = None + + @classmethod + def get_instance(cls): + """获取单例实例""" + if cls._instance is None: + cls._instance = ElectricityMonitor() + return cls._instance + + def __init__(self): + """初始化电力监控器""" + if ElectricityMonitor._instance: + raise Exception("ElectricityMonitor is a singleton class.") + + self.modbus = ModbusUtils() + self.dao = ElectricityDAO() + self.config = ConfigLoader.get_instance() + + # 确保表已创建 + self.dao.create_table_if_not_exists() + + # 创建定时器 + self.timer = None + self.client = None + self.stop_event = Event() + self.monitor_thread = None + + # 电力寄存器地址 + self.electricity_register = 30 + + # 从配置中读取监听间隔(分钟) + self.interval_minutes = self.config.get_value('electricity.interval_minutes', 1) + + # 从配置中读取是否自动启动 + self.auto_start = self.config.get_value('electricity.auto_start', True) + + # 上次读取时间 + self.last_read_time = None + + # 如果配置为自动启动,则启动监控 + if self.auto_start: + self.start() + + def start(self): + """启动监控""" + if self.timer and self.timer.isActive(): + logging.warning("电力监控器已经在运行中") + return + + # 创建并启动定时器,每分钟读取一次 + self.timer = QTimer() + self.timer.timeout.connect(self._read_electricity_data) + self.timer.start(self.interval_minutes * 60000) # 转换为毫秒 + + # 立即执行一次读取 + self._read_electricity_data() + + # 更新配置 + self.config.set_value('electricity.auto_start', True) + + logging.info(f"电力监控器已启动,每{self.interval_minutes}分钟读取一次寄存器30的数据") + + def stop(self): + """停止监控""" + if self.timer: + self.timer.stop() + + if self.client: + self.modbus.close_client(self.client) + self.client = None + + # 更新配置 + self.config.set_value('electricity.auto_start', False) + + logging.info("电力监控器已停止") + + def set_interval(self, minutes): + """设置监听间隔 + + Args: + minutes: 间隔分钟数 + """ + if minutes < 1: + minutes = 1 + + self.interval_minutes = minutes + + # 更新配置 + self.config.set_value('electricity.interval_minutes', minutes) + + # 如果定时器正在运行,则更新间隔 + if self.timer and self.timer.isActive(): + self.timer.setInterval(minutes * 60000) # 转换为毫秒 + logging.info(f"电力监控间隔已更新为{minutes}分钟") + + def is_monitoring(self): + """检查是否正在监控 + + Returns: + bool: 是否正在监控 + """ + return self.timer is not None and self.timer.isActive() + + def get_last_read_time(self): + """获取上次读取时间 + + Returns: + datetime: 上次读取时间,如果未读取过则返回None + """ + return self.last_read_time + + def get_next_read_time(self): + """获取下次读取时间 + + Returns: + datetime: 下次读取时间,如果未在监控则返回None + """ + if not self.is_monitoring() or not self.last_read_time: + return None + + from datetime import datetime, timedelta + return self.last_read_time + timedelta(minutes=self.interval_minutes) + + def _read_electricity_data(self): + """读取电力消耗数据""" + try: + # 如果客户端未连接,则创建连接 + if not self.client: + self.client = self.modbus.get_client() + if not self.client: + logging.error("无法连接到Modbus服务器,电力数据读取失败") + return + + # 读取寄存器30的值 + result = self.modbus.read_holding_register(self.client, self.electricity_register) + if result is None or len(result) == 0: + logging.warning(f"读取寄存器D{self.electricity_register}失败") + return + + # 获取电力消耗值 + electricity_value = result[0] + + # 保存到数据库 + success = self.dao.save_electricity_data(electricity_value) + if success: + logging.info(f"已记录电力消耗数据: {electricity_value}") + # 更新上次读取时间 + from datetime import datetime + self.last_read_time = datetime.now() + else: + logging.error("保存电力消耗数据失败") + + except Exception as e: + logging.error(f"读取或保存电力消耗数据时发生错误: {str(e)}") + # 关闭连接,下次重新尝试 + if self.client: + self.modbus.close_client(self.client) + self.client = None \ No newline at end of file diff --git a/utils/init_db.py b/utils/init_db.py index 79892ea..9dbcc90 100644 --- a/utils/init_db.py +++ b/utils/init_db.py @@ -35,6 +35,17 @@ def init_database(): """ db.execute_query(create_user_table_sql) + # 创建电力消耗表 + create_electricity_table_sql = """ + CREATE TABLE IF NOT EXISTS wsbz_electricity_consumption ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + sync_time TIMESTAMP, + electricity_number REAL + ); + """ + db.execute_query(create_electricity_table_sql) + logging.info("已创建电力消耗表") + # 获取当前时间 current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') diff --git a/utils/modbus_monitor.py b/utils/modbus_monitor.py index 32f7bb1..9281f78 100644 --- a/utils/modbus_monitor.py +++ b/utils/modbus_monitor.py @@ -77,7 +77,7 @@ class ModbusMonitor(QObject): def _initialize_registers(self): """初始化要监控的寄存器列表""" # 默认监控的寄存器地址 - register_addresses = [5, 6, 11, 13, 20, 21, 22, 23, 24] + register_addresses = [5, 6, 11, 13, 20, 21, 22, 23, 24, 30] # 添加寄存器30用于电力监控 for address in register_addresses: self.registers[address] = RegisterValue(address) diff --git a/widgets/login_widget.py b/widgets/login_widget.py index adef91f..1b57f24 100644 --- a/widgets/login_widget.py +++ b/widgets/login_widget.py @@ -38,7 +38,7 @@ def get_user_info(user_id): return result # 始终使用SQLite数据源获取用户信息 db = SQLUtils(source_name='sqlite') - db.execute_query("SELECT username, corp_id as corp_name, corp_id FROM wsbz_user WHERE username = ?", (user_id,)) + db.execute_query("SELECT userid, username, corp_id as corp_name, corp_id FROM wsbz_user WHERE username = ?", (user_id,)) result = db.fetchone() db.close() if result: diff --git a/widgets/main_window.py b/widgets/main_window.py index 193f126..790e7db 100644 --- a/widgets/main_window.py +++ b/widgets/main_window.py @@ -21,6 +21,7 @@ from utils.register_handlers import ( UnloadingLevelHandler, UnloadingPositionHandler ) +from utils.electricity_monitor import ElectricityHandler # 导入PySide6 from PySide6.QtWidgets import ( QWidget, QMessageBox, QTableWidgetItem, QStackedWidget, QLabel, QMainWindow, @@ -1562,26 +1563,33 @@ class MainWindow(MainWindowUI): def _register_modbus_handlers(self): """注册寄存器处理器""" - + # 获取Modbus监控器实例 + monitor = get_modbus_monitor() + # 注册D6处理器,处理NG信号 - self.modbus_monitor.register_handler(6, NGHandler(self.machine_handlers.handle_ng)) + monitor.register_handler(6, NGHandler(self.machine_handlers.handle_ng)) # 注册D11处理器,处理称重数据 - self.modbus_monitor.register_handler(11, WeightDataHandler(self.machine_handlers.handle_weight_data)) + monitor.register_handler(11, WeightDataHandler(self.machine_handlers.handle_weight_data)) # 注册D13处理器,处理贴标信号 - self.modbus_monitor.register_handler(13, LabelSignalHandler(self.machine_handlers.handle_label_signal)) + monitor.register_handler(13, LabelSignalHandler(self.machine_handlers.handle_label_signal)) # 注册D20-D24处理器,处理各种状态信息 - self.modbus_monitor.register_handler(20, LoadingFeedbackHandler(self.handle_loading_feedback)) - self.modbus_monitor.register_handler(21, UnloadingFeedbackHandler(self.handle_unloading_feedback)) - self.modbus_monitor.register_handler(22, Error1Handler(self.machine_handlers.handle_error_1)) - self.modbus_monitor.register_handler(23, Error2Handler(self.machine_handlers.handle_error_2)) - self.modbus_monitor.register_handler(24, Error3Handler(self.machine_handlers.handle_error_3)) + monitor.register_handler(20, LoadingFeedbackHandler(self.handle_loading_feedback)) + monitor.register_handler(21, UnloadingFeedbackHandler(self.handle_unloading_feedback)) + monitor.register_handler(22, Error1Handler(self.machine_handlers.handle_error_1)) + monitor.register_handler(23, Error2Handler(self.machine_handlers.handle_error_2)) + monitor.register_handler(24, Error3Handler(self.machine_handlers.handle_error_3)) # 注册下料层数和位置处理器 - self.modbus_monitor.register_handler(4, UnloadingLevelHandler(self.handle_unloading_level)) - self.modbus_monitor.register_handler(5, UnloadingPositionHandler(self.handle_unloading_position)) + monitor.register_handler(4, UnloadingLevelHandler(self.handle_unloading_level)) + monitor.register_handler(5, UnloadingPositionHandler(self.handle_unloading_position)) + + # 注册电力消耗处理器 + monitor.register_handler(30, ElectricityHandler()) + + logging.info("已注册所有Modbus寄存器处理器") def _connect_modbus_signals(self): """连接Modbus信号槽""" @@ -1804,7 +1812,7 @@ class MainWindow(MainWindowUI): QMessageBox.warning(self, f"提示", response.get("message",{})) # 保存贴标数据到数据库 - self.save_inspection_data(self._current_order_code, gc_note, tray_id, 11, 11, str(axios_num), "pass") + self.save_inspection_data(self._current_order_code, gc_note, tray_id, 11, 11, str(axios_num), "pass") # 重新连接信号 self.process_table.cellChanged.connect(self.handle_inspection_cell_changed) diff --git a/widgets/settings_window.py b/widgets/settings_window.py index f8ad4ca..a63f0a0 100644 --- a/widgets/settings_window.py +++ b/widgets/settings_window.py @@ -3,6 +3,7 @@ from PySide6.QtCore import Signal from PySide6.QtWidgets import QDialog, QVBoxLayout, QDialogButtonBox from widgets.serial_settings_widget import SerialSettingsWidget from widgets.settings_widget import SettingsWidget +from ui.electricity_settings_ui import ElectricitySettingsUI class SettingsWindow(QDialog): """设置窗口,直接使用SettingsWidget中的标签页""" @@ -31,6 +32,10 @@ class SettingsWindow(QDialog): self.serial_settings = SerialSettingsWidget(self) self.settings_widget.tab_widget.addTab(self.serial_settings, "串口设置") + # 添加电力监控设置到标签页 + self.electricity_settings = ElectricitySettingsUI(self) + self.settings_widget.tab_widget.addTab(self.electricity_settings, "电力监控") + # 添加按钮 self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_box.accepted.connect(self.accept) @@ -39,6 +44,7 @@ class SettingsWindow(QDialog): # 连接信号 self.serial_settings.settings_changed.connect(self.settings_changed.emit) + self.electricity_settings.settings_changed.connect(self.settings_changed.emit) logging.info("SettingsWindow初始化完成")