From 5d2f0099ac4ecb423a1b74b235c6803c6673dff4 Mon Sep 17 00:00:00 2001 From: zhu-mengmeng <15588200382@163.com> Date: Wed, 23 Jul 2025 13:22:53 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9Modbus=E5=86=99?= =?UTF-8?q?=E5=85=A5=E5=80=BC=EF=BC=8C=E6=96=B0=E5=A2=9E=E7=BA=BF=E5=BE=84?= =?UTF-8?q?=E5=92=8C=E9=87=8D=E9=87=8F=E8=AD=A6=E5=91=8A=E4=BF=A1=E5=8F=B7?= =?UTF-8?q?=E5=8F=8A=E5=BC=B9=E6=A1=86=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E8=AD=A6=E5=91=8A=E6=8F=90=E7=A4=BA=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/jtDB.db | Bin 245760 -> 262144 bytes from pymodbus.py | 2 +- widgets/main_window.py | 328 +++++++++++++++++++++++------------------ 3 files changed, 188 insertions(+), 142 deletions(-) diff --git a/db/jtDB.db b/db/jtDB.db index 4433962eef7fb30a3f351f29562cf08e9a45a4b7..388d6a8ca728923d9dca0a0f51ffd954b7f54f01 100644 GIT binary patch delta 7670 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 delta 1007 zcmY*Xe{54#6u$4Cd*5r{kKWg?uIuL5n{E)c(7bMM(=22wIAcOG2@Da2x{V+z!pg)< zAW@5k4H}6pRW8<*lxd7-nO>BiA0u}s1W z0*xZjUT^!w98!<|?Jy^wOh?l*sh(C)4{5rN7GU*RP(~KlAS1nFAwNPh|5?92xU@CH7BI zub$>j@u(gj>N7QOm*Gb?Dv`P>Rv;%qGr&GA#8$~-PA&&92{+_l)ZI;HkmBi7s zWtO>R2k9sno$+Q#g1p)xwi0yoA`{Mmv4(XpsMv!nN>L zc?BOngjczX4&uW~`R*60?^|IQMpZ?i)(8dX1wsq??^)bi)nwaYyU}*+aD#pA%${qX zW!AN?ZgA{JB6hpoyv*%AjF0rLJdY|-r(~S=W!a$7q5r7AsLg77w1E1d8u6a>cF6Vg zxTjkwDE;!R=Q3?07sz^;L7nglzAKGL3yWU;@9<9vY$v!9^@T5)bXumbFhTqU2 z$gu)zAeLCc`=6as`b6}S z5PgjQaRblit`F87G=V^dp67}QU-0WD47h!M=)z)AS}-4<2fBMP01YZ+;I^w+&{W)) zQ<&$ffw@Wz+|eLI9In{V?T*;+fFy{c*mWG3)O3kW6#aRaO|YnIM&WJ7hr7Wpj?&C0 zqhOn=c&?Jf%1yEy+xB17a{*C$92VR!G1K?6?-=jv0f&2fplWfAd`ljeGx8R>RW|7r z`Z*n>YiS*sBj1o5d6~46QkaG@7! max_weight: # 写入寄存器D10 给值 2 表示重量超出范围 - modbus = ModbusUtils() - client = modbus.get_client() - modbus.write_register_until_success(client, 10, 2) - modbus.close_client(client) - # 显示自动关闭的警告提示框 - self.warning_msg = QMessageBox(self) - self.warning_msg.setIcon(QMessageBox.Warning) - self.warning_msg.setWindowTitle('重量超出范围') - self.warning_msg.setText(f"称重值 {net_weight_kg:.2f}kg 不在轴重要求范围内 ({min_weight:.1f} - {max_weight:.1f}kg)") - self.warning_msg.setStandardButtons(QMessageBox.Ok) # 添加确定按钮 - self.warning_msg.setModal(False) # 确保非模态 - self.warning_msg.setWindowFlags(self.warning_msg.windowFlags() | Qt.WindowStaysOnTopHint) # 置顶显示 + try: + modbus = ModbusUtils() + client = modbus.get_client() + modbus.write_register_until_success(client, 10, 2) + modbus.close_client(client) + except Exception as e: + logging.error(f"写入Modbus寄存器失败: {str(e)}") - # 连接按钮点击信号 - self.warning_msg.buttonClicked.connect(lambda btn: self.warning_msg.close()) - - # 使用 show 而非 exec_ 保持非阻塞 - self.warning_msg.show() - - # 强制关闭方法1:使用多种定时器策略 - def force_close_weight_warning(): - try: - if hasattr(self, 'warning_msg') and self.warning_msg: - if self.warning_msg.isVisible(): - self.warning_msg.close() - self.warning_msg.deleteLater() - logging.debug("重量警告弹框已强制关闭") - else: - logging.debug("重量警告弹框已不可见,跳过关闭") - except Exception as e: - logging.warning(f"强制关闭重量警告弹框时出错: {str(e)}") - finally: - # 清理引用 - if hasattr(self, 'warning_msg'): - delattr(self, 'warning_msg') - - # 使用多个定时器确保关闭 - QTimer.singleShot(5000, force_close_weight_warning) # 5秒后关闭 - QTimer.singleShot(6000, force_close_weight_warning) # 6秒后再次尝试 + # 使用信号触发弹框显示 - 避免主线程阻塞 + logging.warning(f"称重值 {net_weight_kg:.2f}kg 超出轴重要求范围 ({min_weight:.1f} - {max_weight:.1f}kg),发送信号显示警告") + try: + self.weight_alert_signal.emit(net_weight_kg, min_weight, max_weight) + except Exception as e: + logging.error(f"发送重量警告信号失败: {str(e)}", exc_info=True) # 阻止继续执行,等待用户处理 - logging.warning(f"称重值 {weight_kg:.3f}kg 超出轴重要求范围,已阻止保存,等待用户处理") + logging.warning(f"称重值 {weight_kg:.3f}kg 超出轴重要求范围,已阻止保存") return else: logging.info(f"称重值 {weight_kg:.3f}kg 在轴重要求范围内 ({min_weight:.1f} - {max_weight:.1f}kg)") @@ -3030,8 +3007,6 @@ class MainWindow(MainWindowUI): if self._loading_info and self._current_stow_num > 0: self.show_operation_status("拆垛层数", "input", str(self._current_stow_num)) else: - # 上料任务完成,清除状态显示 - self.clear_operation_status("input") # 上料任务完成,恢复上料按钮样式 self.restore_input_button_style() logging.info("上料任务完成,恢复上料按钮样式") @@ -3551,52 +3526,20 @@ class MainWindow(MainWindowUI): # 检查线径值是否在范围内 if final_value < min_diameter or final_value > max_diameter: - # 写入寄存器D10 给值 3 表示线径超出范围 - modbus = ModbusUtils() - modbus_client = modbus.get_client() - modbus.write_register_until_success(modbus_client, 10, 3) - - # 显示自动关闭的警告提示框 - self.diameter_warning_msg = QMessageBox(self) - self.diameter_warning_msg.setIcon(QMessageBox.Warning) - self.diameter_warning_msg.setWindowTitle('线径超出范围') - self.diameter_warning_msg.setText(f"线径值 {final_value:.3f}mm 不在线径公差范围内 ({min_diameter:.3f} - {max_diameter:.3f}mm)") - self.diameter_warning_msg.setStandardButtons(QMessageBox.Ok) # 添加确定按钮 - self.diameter_warning_msg.setModal(False) # 确保非模态 - self.diameter_warning_msg.setWindowFlags(self.diameter_warning_msg.windowFlags() | Qt.WindowStaysOnTopHint) # 置顶显示 - - # 连接按钮点击信号 - self.diameter_warning_msg.buttonClicked.connect(lambda btn: self.diameter_warning_msg.close()) - # 使用 show 而非 exec_ 保持非阻塞 - self.diameter_warning_msg.show() - # 强制关闭方法:使用多种定时器策略 - def force_close_diameter_warning(): - try: - if hasattr(self, 'diameter_warning_msg') and self.diameter_warning_msg: - if self.diameter_warning_msg.isVisible(): - self.diameter_warning_msg.close() - self.diameter_warning_msg.deleteLater() - logging.debug("线径警告弹框已强制关闭") - else: - logging.debug("线径警告弹框已不可见,跳过关闭") - except Exception as e: - logging.warning(f"强制关闭线径警告弹框时出错: {str(e)}") - finally: - # 清理引用 - if hasattr(self, 'diameter_warning_msg'): - delattr(self, 'diameter_warning_msg') - - # 使用多个定时器确保关闭 - QTimer.singleShot(2000, force_close_diameter_warning) # 2秒后关闭 - QTimer.singleShot(3000, force_close_diameter_warning) # 3秒后再次尝试 + # 使用信号触发弹框显示 - 避免主线程阻塞 + logging.warning(f"线径值 {final_value:.3f}mm 超出线径公差范围 ({min_diameter:.3f} - {max_diameter:.3f}mm),发送信号显示警告") + try: + self.diameter_alert_signal.emit(final_value, min_diameter, max_diameter) + except Exception as e: + logging.error(f"发送线径警告信号失败: {str(e)}", exc_info=True) # 重置测量列表,防止重复触发 self._diameter_measurements = [] # 阻止继续执行,等待用户处理 - logging.warning(f"线径值 {final_value:.3f}mm 超出线径公差范围,已阻止保存,等待用户处理") + logging.warning(f"线径值 {final_value:.3f}mm 超出线径公差范围,已阻止保存") return else: logging.info(f"线径值 {final_value:.3f}mm 在线径公差范围内 ({min_diameter:.3f} - {max_diameter:.3f}mm)") @@ -3678,56 +3621,11 @@ class MainWindow(MainWindowUI): @Slot(float, str, str) def show_diameter_warning(self, final_value, bccd, tccd): - """显示线径超出范围警告(在主线程中执行)""" - try: - # 显示自动关闭的警告提示框 - self.diameter_warning_msg = QMessageBox(self) - self.diameter_warning_msg.setIcon(QMessageBox.Warning) - self.diameter_warning_msg.setWindowTitle('线径超出范围') - self.diameter_warning_msg.setText(f"线径 {final_value:.3f} 不在公差范围内 ({bccd} - {tccd})") - self.diameter_warning_msg.setStandardButtons(QMessageBox.Ok) # 添加确定按钮 - self.diameter_warning_msg.setModal(False) # 确保非模态 - self.diameter_warning_msg.setWindowFlags(self.diameter_warning_msg.windowFlags() | Qt.WindowStaysOnTopHint) # 置顶显示 - - # 连接按钮点击信号 - self.diameter_warning_msg.buttonClicked.connect(lambda btn: self.diameter_warning_msg.close()) - - # 显示提示框(非模态) - self.diameter_warning_msg.show() - - # Windows平台强制关闭方法 - def force_close_diameter_warning(): - try: - if hasattr(self, 'diameter_warning_msg') and self.diameter_warning_msg: - if self.diameter_warning_msg.isVisible(): - # 方法1:正常关闭 - self.diameter_warning_msg.close() - # 方法2:强制删除 - self.diameter_warning_msg.deleteLater() - # 方法3:隐藏窗口 - self.diameter_warning_msg.hide() - # 方法4:设置为不可见 - self.diameter_warning_msg.setVisible(False) - logging.debug("线径警告弹框已强制关闭") - else: - logging.debug("线径警告弹框已不可见,跳过关闭") - except Exception as e: - logging.warning(f"强制关闭线径警告弹框时出错: {str(e)}") - finally: - # 清理引用 - try: - if hasattr(self, 'diameter_warning_msg'): - delattr(self, 'diameter_warning_msg') - except: - pass - - # 使用多个定时器确保关闭(Windows平台优化) - QTimer.singleShot(2000, force_close_diameter_warning) # 1秒后关闭 - QTimer.singleShot(3000, force_close_diameter_warning) # 2秒后最后尝试 - - logging.info(f"线径警告弹框已显示,将在1-3秒后自动关闭") - except Exception as e: - logging.error(f"显示线径警告弹框时出错: {str(e)}") + """显示线径超出范围警告(在主线程中执行)- 为避免冲突,此方法已弃用,仅保留接口""" + # 这个方法已经被新的线径处理逻辑取代,为避免冲突,此处不再显示弹框 + # 仅记录日志,保持接口兼容性 + logging.info(f"线径警告信号已接收,但弹框显示已被新逻辑取代: 线径值 {final_value:.3f}mm, 范围 {bccd} - {tccd}") + return def on_scanner_data_received(self, port_name, data): """扫码器数据接收回调函数 @@ -5111,6 +5009,154 @@ class MainWindow(MainWindowUI): logging.warning(f"线径数据处理超时,强制释放锁。处理时间: {processing_time:.2f}秒") self._processing_diameter_lock = False + @Slot(float, float, float) + def show_diameter_alert(self, value, min_value, max_value): + """显示线径超出范围警告 - 通过信号触发,使用exec_强制显示""" + try: + # 使用更强制的方式显示警告对话框 + from PySide6.QtWidgets import QApplication + + # 记录当前时间,用于跟踪弹框显示时间 + start_time = time.time() + logging.info(f"开始创建线径警告弹框,时间: {start_time}") + + # 创建一个模态对话框 - 使用exec_方式显示 + msg = QMessageBox() + msg.setIcon(QMessageBox.Critical) # 使用Critical图标更明显 + msg.setWindowTitle('警告:线径超出范围!') + msg.setText(f"线径值 {value:.3f}mm 超出范围!

允许范围: {min_value:.3f} - {max_value:.3f}mm") + msg.setStandardButtons(QMessageBox.Ok) + + # 设置样式,使其更显眼 + msg.setStyleSheet(""" + QMessageBox { + background-color: #ffeeee; + border: 3px solid #ff0000; + font-size: 14px; + } + QLabel { + color: #ff0000; + font-size: 16px; + font-weight: bold; + min-width: 250px; + min-height: 80px; + } + QPushButton { + background-color: #ff6666; + color: white; + font-weight: bold; + min-width: 80px; + min-height: 30px; + } + """) + + # 确保对话框显示在所有窗口之上 + msg.setWindowFlags(Qt.WindowStaysOnTopHint) + + # 强制处理事件,确保UI更新 + QApplication.processEvents() + + # 使用定时器在2秒后自动点击确定按钮 + def auto_close(): + try: + # 查找确定按钮并点击 + for button in msg.buttons(): + if msg.buttonRole(button) == QMessageBox.AcceptRole: + button.click() + logging.info(f"已自动点击确定按钮,弹框显示时长: {time.time() - start_time:.2f}秒") + return + # 如果没有找到确定按钮,直接关闭 + msg.done(QMessageBox.Ok) + logging.info(f"已自动关闭弹框,弹框显示时长: {time.time() - start_time:.2f}秒") + except Exception as e: + logging.error(f"自动关闭弹框失败: {str(e)}") + + # 设置自动关闭定时器 + QTimer.singleShot(2000, auto_close) + + # 显示弹框并阻塞直到用户关闭或自动关闭 + logging.info(f"即将显示线径警告弹框...") + msg.exec_() # 使用exec_而不是show,确保弹框显示 + + # 记录日志 + logging.info(f"线径警告弹框已关闭,总显示时长: {time.time() - start_time:.2f}秒") + except Exception as e: + logging.error(f"显示线径警告弹框失败: {str(e)}", exc_info=True) + + @Slot(float, float, float) + def show_weight_alert(self, value, min_value, max_value): + """显示重量超出范围警告 - 通过信号触发,使用exec_强制显示""" + try: + # 使用更强制的方式显示警告对话框 + from PySide6.QtWidgets import QApplication + + # 记录当前时间,用于跟踪弹框显示时间 + start_time = time.time() + logging.info(f"开始创建重量警告弹框,时间: {start_time}") + + # 创建一个模态对话框 - 使用exec_方式显示 + msg = QMessageBox() + msg.setIcon(QMessageBox.Critical) # 使用Critical图标更明显 + msg.setWindowTitle('警告:重量超出范围!') + msg.setText(f"称重值 {value:.2f}kg 超出范围!

允许范围: {min_value:.1f} - {max_value:.1f}kg") + msg.setStandardButtons(QMessageBox.Ok) + + # 设置样式,使其更显眼 + msg.setStyleSheet(""" + QMessageBox { + background-color: #ffeeee; + border: 3px solid #ff0000; + font-size: 14px; + } + QLabel { + color: #ff0000; + font-size: 16px; + font-weight: bold; + min-width: 250px; + min-height: 80px; + } + QPushButton { + background-color: #ff6666; + color: white; + font-weight: bold; + min-width: 80px; + min-height: 30px; + } + """) + + # 确保对话框显示在所有窗口之上 + msg.setWindowFlags(Qt.WindowStaysOnTopHint) + + # 强制处理事件,确保UI更新 + QApplication.processEvents() + + # 使用定时器在2秒后自动点击确定按钮 + def auto_close(): + try: + # 查找确定按钮并点击 + for button in msg.buttons(): + if msg.buttonRole(button) == QMessageBox.AcceptRole: + button.click() + logging.info(f"已自动点击确定按钮,弹框显示时长: {time.time() - start_time:.2f}秒") + return + # 如果没有找到确定按钮,直接关闭 + msg.done(QMessageBox.Ok) + logging.info(f"已自动关闭弹框,弹框显示时长: {time.time() - start_time:.2f}秒") + except Exception as e: + logging.error(f"自动关闭弹框失败: {str(e)}") + + # 设置自动关闭定时器 + QTimer.singleShot(2000, auto_close) + + # 显示弹框并阻塞直到用户关闭或自动关闭 + logging.info(f"即将显示重量警告弹框...") + msg.exec_() # 使用exec_而不是show,确保弹框显示 + + # 记录日志 + logging.info(f"重量警告弹框已关闭,总显示时长: {time.time() - start_time:.2f}秒") + except Exception as e: + logging.error(f"显示重量警告弹框失败: {str(e)}", exc_info=True) + def safe_str(val):