remote Combo强制读取数据的方法

当一个 combo 的 remote 为 true,时,通常只在单击combo小箭头的时候读取一次数据,再次点击小箭头后就不会再读取了。但有些时候需要实时地获取combo的数据,这时候只需要在这个combo的 beforequery 事件方法中加上 store.reload() 就可以了~

 

	beforequery: function(queryEvent){
		s.reload();		//强制每次点击combo的时候都进行查询
	}
 
下面的代码 开关设备和关闭linux 按钮失效 无法发出控制信号 import sys import time import socket import paramiko from threading import Thread from configparser import ConfigParser from pathlib import Path from PySide6.QtWidgets import (QApplication, QWidget, QLineEdit, QPushButton, QHBoxLayout, QVBoxLayout, QMessageBox, QLabel, QComboBox) from PySide6.QtCore import QObject, Signal, Qt from PySide6.QtGui import QIcon import os # ---------- 配置文件路径 ---------- CONFIG_FILE = "data.ini" # ---------- 工具函数 ---------- def tcp_port_alive(ip: str, port: int, timeout: int = 2) -> bool: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.settimeout(timeout) return s.connect_ex((ip, port)) == 0 def host_reachable(ip: str, port: int = 502) -> tuple[bool, str]: if tcp_port_alive(ip, port): return True, f'TCP {port} 端口开放' return False, '502 端口无法连接' def shutdown_remote_linux(remote_host: str, ssh_user: str, ssh_pass: str, port: int = 22) -> None: ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: ssh.connect(remote_host, port=port, username=ssh_user, password=ssh_pass) stdin, stdout, stderr = ssh.exec_command('sudo shutdown -h now') stdout.channel.recv_exit_status() except Exception as e: raise RuntimeError(f"SSH 关机失败: {e}") from e finally: ssh.close() def verify_shutdown(remote_host: str, port: int = 22, timeout: int = 3) -> bool: try: with socket.create_connection((remote_host, port), timeout=timeout): return False except (socket.timeout, socket.error): return True def wait_shutdown_complete(remote_host: str, port: int = 22, max_wait: int = 60) -> None: for _ in range(max_wait): if verify_shutdown(remote_host, port): return time.sleep(1) raise RuntimeError("等待关机超时") def openlinux(remote_host: str, port: int = 22) -> bool: while True: try: with socket.create_connection((remote_host, port), timeout=5): return True except (socket.timeout, socket.error): continue # ---------- 周期性SSH检测线程 ---------- class PeriodicSSHCheckWorker(QObject): status_changed = Signal(bool, str) # True/"下位机已开机" or False/"下位机未连接" def __init__(self, ssh_ip: str, port: int = 22): super().__init__() self.ssh_ip = ssh_ip self.port = port self._running = True def stop(self): self._running = False def run(self): last_status = None while self._running: try: current_ok = tcp_port_alive(self.ssh_ip, self.port, timeout=3) current_status = "下位机已开机" if current_ok else "下位机未连接" if current_ok != last_status: self.status_changed.emit(current_ok, current_status) last_status = current_ok except Exception: if last_status is not None: self.status_changed.emit(False, "下位机未连接") last_status = None # 每隔30秒检测一次 for _ in range(30): if not self._running: break time.sleep(1) # ---------- 探测线程 ---------- class DetectWorker(QObject): done = Signal(bool, str) ssh_status_first = Signal(bool, str) def __init__(self, ip: str, port: int, ssh_ip: str, ssh_port: int = 22): super().__init__() self.ip, self.port = ip, port self.ssh_ip, self._ssh_port = ssh_ip, ssh_port def run(self): ok, msg = host_reachable(self.ip, self.port) ssh_ok = tcp_port_alive(self.ssh_ip, self._ssh_port, timeout=2) if ssh_ok: self.ssh_status_first.emit(True, "下位机已开机") else: self.ssh_status_first.emit(False, "下位机未连接") self.done.emit(ok, msg) # ---------- 继电器 ---------- class RelayControl: def __init__(self): self.client_socket = None self._ip = None self._port = None def tcp_connect(self, ip_addr: str, ip_port: int): self._ip, self._port = ip_addr, ip_port self._reconnect() def _reconnect(self): try: if self.client_socket: self.client_socket.close() except Exception: pass self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.client_socket.settimeout(3) self.client_socket.connect((self._ip, self._port)) def _ensure_socket(self): try: self.client_socket.getpeername() except (OSError, AttributeError): self._reconnect() def tcp_disconnect(self): try: self.client_socket.close() except Exception: pass self.client_socket = None def power_up_1(self): self._ensure_socket() self.client_socket.sendall(bytes.fromhex('00000000000601052040FF00')) def power_up_2(self): self._ensure_socket() self.client_socket.sendall(bytes.fromhex('000100000006010520400000')) def power_down_1(self): self._ensure_socket() self.client_socket.sendall(bytes.fromhex('00020000000601052041FF00')) def power_down_2(self): self._ensure_socket() self.client_socket.sendall(bytes.fromhex('000300000006010520410000')) # ---------- 启动/停止线程(仅 PLC) ---------- class Worker(QObject): finished = Signal() error = Signal(str) def __init__(self, relay: RelayControl, up: bool): super().__init__() self.relay = relay self.up = up def run(self): try: if self.up: self.relay.power_up_1() time.sleep(1) self.relay.power_up_2() else: self.relay.power_down_1() time.sleep(1) self.relay.power_down_2() self.finished.emit() except Exception as e: self.error.emit(str(e)) # ---------- 独立关机线程(仅 SSH) ---------- class ShutdownWorker(QObject): finished = Signal() error = Signal(str) def __init__(self, ssh_ip: str, ssh_user: str, ssh_pwd: str): super().__init__() self.ssh_ip = ssh_ip self.ssh_user = ssh_user self.ssh_pwd = ssh_pwd def run(self): try: shutdown_remote_linux(self.ssh_ip, self.ssh_user, self.ssh_pwd) wait_shutdown_complete(self.ssh_ip) self.finished.emit() except Exception as e: self.error.emit(str(e)) # ---------- 指示灯线程 ---------- class IndicatorWorker(QObject): status_changed = Signal(bool, str) def __init__(self, target_ip: str, port: int = 22): super().__init__() self.target_ip = target_ip self.port = port self._running = True self._wait_for_on = False self._wait_for_off = False def wait_for_on(self): self._wait_for_on = True self._wait_for_off = False def wait_for_off(self): self._wait_for_off = True self._wait_for_on = False def stop(self): self._running = False def run(self): while self._running: if self._wait_for_on: if openlinux(self.target_ip, self.port): self.status_changed.emit(True, "下位机已开机") self._wait_for_on = False elif self._wait_for_off: if verify_shutdown(self.target_ip, self.port): self.status_changed.emit(False, "下位机已关机") self._wait_for_off = False else: time.sleep(0.2) # ---------- GUI 主窗口 ---------- class MainWindow(QWidget): def __init__(self): super().__init__() base_path = os.path.dirname(os.path.abspath(__file__)) icon_path = os.path.join(base_path, "logo.ico") # 动态路径 # 设置窗口图标 self.setWindowIcon(QIcon(icon_path)) # ════════════ 可调常量 ════════════ WIN_WIDTH = 480 WIN_HEIGHT = 150 IP_WIDTH = 110 PORT_WIDTH = 80 USER_WIDTH = 80 BTN_WIDTH = 80 FONT_SIZE = 13 BG_COLOR = "#f5f5f5" BTN_COLOR = "#e5e5e5" BTN_HOVER = "#d0d0d0" BTN_PRESSED = "#c0c0c0" # ═════════════════════════════════ self.setFixedSize(WIN_WIDTH, WIN_HEIGHT) self.setWindowTitle('HIL 开关机控制') self.setStyleSheet(f""" QWidget{{ background-color:{BG_COLOR}; font:{FONT_SIZE}px "Microsoft YaHei"; }} QLineEdit{{ height:24px; border:1px solid #ccc; border-radius:4px; padding:0 4px; background:white; }} QPushButton{{ height:26px; border:1px solid #bbb; border-radius:4px; background:{BTN_COLOR}; min-width:{BTN_WIDTH}px; }} QPushButton:hover{{ background:{BTN_HOVER}; }} QPushButton:pressed{{ background:{BTN_PRESSED}; }} QLabel{{ color:#333; }} QComboBox{{ height:24px; border:1px solid #ccc; border-radius:4px; padding:0 4px; }} """) # ---------------- 初始化配置 ---------------- self.config = ConfigParser() self.load_config() # ---------------- 创建控件 ---------------- self.profile_combo = QComboBox() self.profile_combo.setFixedWidth(120) self.update_profile_list() self.ip_edit = QLineEdit('') self.port_edit = QLineEdit('') self.port_edit.setInputMask('00000') self.ssh_ip_edit = QLineEdit('') self.ssh_user_edit = QLineEdit('') self.ssh_pwd_edit = QLineEdit('') self.ssh_pwd_edit.setEchoMode(QLineEdit.Password) self.ssh_pwd_edit.setMaxLength(32) self.ssh_pwd_edit.setFixedWidth(110) # 统一宽度 self.ip_edit.setFixedWidth(IP_WIDTH) self.ssh_ip_edit.setFixedWidth(IP_WIDTH) self.port_edit.setFixedWidth(PORT_WIDTH) self.ssh_user_edit.setFixedWidth(USER_WIDTH) self.profile_combo.setFixedWidth(IP_WIDTH) self.conn_btn = QPushButton('连接') self.conn_btn.setCheckable(True) self.conn_btn.setFixedWidth(BTN_WIDTH) self.conn_btn.clicked.connect(self.on_toggle_connect) self._set_conn_style(False) self.start_btn = QPushButton('启动') self.stop_btn = QPushButton('停止') self.shutdown_btn = QPushButton('关闭Linux') self.start_btn.setFixedWidth(BTN_WIDTH) self.indicator = QLabel('█') # 使用方块符号替代圆形 self.indicator.setFixedSize(18, 18) # 设置为正方形 self.indicator.setAlignment(Qt.AlignCenter) # 文字居中 self.indicator.setStyleSheet(""" QLabel { font-size: 24px; /* 调整字体大小 */ color: gray; border: 1px solid #ccc; /* 添加边框,使其更像方块 */ border-radius: 4px; /* 轻微圆角,可选 */ } """) self.indicator_text = QLabel("下位机未启动") # ---------------- 布局 ---------------- row0 = QHBoxLayout() row0.addWidget(QLabel('设备选择:')) row0.addWidget(self.profile_combo) row0.addWidget(self.conn_btn) row0.addStretch() row1 = QHBoxLayout() row1.addWidget(QLabel('继电器IP:')) row1.addWidget(self.ip_edit) row1.addWidget(QLabel('端 口:')) row1.addWidget(self.port_edit) row1.addStretch() row2 = QHBoxLayout() row2.addWidget(QLabel('下位机IP:')) row2.addWidget(self.ssh_ip_edit) row2.addWidget(QLabel('用户名:')) row2.addWidget(self.ssh_user_edit) row2.addWidget(QLabel('密码:')) row2.addWidget(self.ssh_pwd_edit) row2.addStretch() row3 = QHBoxLayout() row3.addWidget(self.start_btn) row3.addWidget(self.stop_btn) row3.addWidget(self.shutdown_btn) # 创建一个水平布局用于居中显示指示灯和文字 indicator_layout = QHBoxLayout() indicator_layout.addWidget(QLabel('下位机状态:')) indicator_layout.addWidget(self.indicator) indicator_layout.addWidget(self.indicator_text) indicator_layout.addStretch() # 添加弹性空间以居中 row3.addLayout(indicator_layout) # 将指示灯布局添加到主布局 row3.addStretch() main = QVBoxLayout(self) main.addLayout(row0) main.addLayout(row1) main.addLayout(row2) main.addLayout(row3) main.addStretch() # 业务对象 self.relay = RelayControl() self._thread = None self.indicator_thread = None self.indicator_worker = None self.periodic_thread = None # 周期检测线程 self.periodic_worker = None # 周期检测工作对象 self._ssh_ever_connected = False self._set_ctrl_enabled(False) # 信号绑定 self.profile_combo.currentTextChanged.connect(self.on_profile_selected) # 默认加载第一个配置 if self.profile_combo.count() > 0: self.on_profile_selected(self.profile_combo.currentText()) # 注意:这里不再启动周期检测!只等“连接”后再启动 def load_config(self): if not Path(CONFIG_FILE).exists(): # 创建默认配置 self.config['设备1'] = { 'relay_ip': '192.168.1.3', 'relay_port': '502', 'ssh_ip': '192.168.1.119', 'ssh_user': 'root', 'ssh_password': 'aertp2020' } self.config['设备2'] = { 'relay_ip': '192.168.0.3', 'relay_port': '502', 'ssh_ip': '192.168.2.119', 'ssh_user': 'root', 'ssh_password': '111111' } with open(CONFIG_FILE, 'w', encoding='utf-8') as f: self.config.write(f) else: self.config.read(CONFIG_FILE, encoding='utf-8') def update_profile_list(self): self.profile_combo.clear() self.profile_combo.addItems(self.config.sections()) def on_profile_selected(self, name): if not name or not self.config.has_section(name): return sec = self.config[name] self.ip_edit.setText(sec.get('relay_ip', '')) self.port_edit.setText(sec.get('relay_port', '')) self.ssh_ip_edit.setText(sec.get('ssh_ip', '')) self.ssh_user_edit.setText(sec.get('ssh_user', '')) self.ssh_pwd_edit.setText(sec.get('ssh_password', '')) # ---------------- 连接/断开 ---------------- def on_toggle_connect(self): if self.conn_btn.isChecked(): ip = self.ip_edit.text().strip() if not ip: QMessageBox.warning(self, '提示', 'IP 地址不能为空') self.conn_btn.setChecked(False) return try: port = int(self.port_edit.text()) except ValueError: QMessageBox.warning(self, '提示', '端口必须是数字') self.conn_btn.setChecked(False) return # === 禁止切换配置 === self.profile_combo.setEnabled(False) self.conn_btn.setEnabled(False) self.conn_btn.setText('连接中…') self.detector = DetectWorker(ip, port, self.ssh_ip_edit.text().strip()) self.detector.ssh_status_first.connect(self._set_indicator) self.detector.done.connect(self._on_detect_done) Thread(target=self.detector.run, daemon=True).start() else: # 断开时关闭周期检测 self._stop_periodic_ssh_check() try: self.relay.tcp_disconnect() except Exception: pass self._set_conn_style(False) self._set_ctrl_enabled(False) self._stop_indicator() self._set_indicator(False, "下位机未连接") # === 断开后恢复选择 === self.profile_combo.setEnabled(True) def _on_detect_done(self, ok: bool, msg: str): self.conn_btn.setEnabled(True) if not ok: QMessageBox.warning(self, '不可达', f'{self.ip_edit.text().strip()} 不可达({msg})') self.conn_btn.setChecked(False) self._set_conn_style(False) # === 连接失败,恢复选择 === self.profile_combo.setEnabled(True) return try: self.relay.tcp_connect(self.ip_edit.text().strip(), int(self.port_edit.text())) self._set_conn_style(True) self._set_ctrl_enabled(True) self._start_indicator() # === 成功连接后启动周期性SSH检测 === self._start_periodic_ssh_check() except Exception as e: QMessageBox.critical(self, '错误', f'连接失败:\n{e}') self.conn_btn.setChecked(False) self._set_conn_style(False) # === 连接失败,恢复选择 === self.profile_combo.setEnabled(True) # === 启动周期性检测(仅在连接成功后调用)=== def _start_periodic_ssh_check(self): ssh_ip = self.ssh_ip_edit.text().strip() if not ssh_ip or self.periodic_worker is not None: return self.periodic_worker = PeriodicSSHCheckWorker(ssh_ip) self.periodic_worker.status_changed.connect(self._set_indicator) self.periodic_thread = Thread(target=self.periodic_worker.run, daemon=True) self.periodic_thread.start() def _stop_periodic_ssh_check(self): if self.periodic_worker: self.periodic_worker.stop() self.periodic_worker = None # ---------------- 启动/停止(仅PLC) ---------------- def on_ctrl(self, up: bool): self.start_btn.setEnabled(False) self.stop_btn.setEnabled(False) self.shutdown_btn.setEnabled(False) if up: self.indicator_worker.wait_for_on() self.worker = Worker(self.relay, up=up) self.worker.finished.connect(self._done) self.worker.error.connect(self._error) self._thread = Thread(target=self.worker.run, daemon=True) self._thread.start() # ---------------- 独立关闭Linux(仅SSH) ---------------- def on_shutdown(self): self.start_btn.setEnabled(False) self.stop_btn.setEnabled(False) self.shutdown_btn.setEnabled(False) self.indicator_worker.wait_for_off() self.shutdown_worker = ShutdownWorker( self.ssh_ip_edit.text().strip(), self.ssh_user_edit.text().strip(), self.ssh_pwd_edit.text().strip()) self.shutdown_worker.finished.connect(self._shutdown_done) self.shutdown_worker.error.connect(self._shutdown_error) Thread(target=self.shutdown_worker.run, daemon=True).start() # ---------------- 指示灯 ---------------- def _start_indicator(self): self._stop_indicator() self.indicator_worker = IndicatorWorker(self.ssh_ip_edit.text().strip()) self.indicator_worker.status_changed.connect(self._set_indicator) self.indicator_thread = Thread(target=self.indicator_worker.run, daemon=True) self.indicator_thread.start() def _stop_indicator(self): if self.indicator_worker: self.indicator_worker.stop() self.indicator_worker = None def _set_indicator(self, on: bool, text: str = ""): color = "green" if on else "gray" self.indicator.setStyleSheet(f""" QLabel {{ font-size: 24px; color: {color}; border: 1px solid #ccc; border-radius: 4px; }} """) if text: self.indicator_text.setText(text) if on and not self._ssh_ever_connected: self._ssh_ever_connected = True self.shutdown_btn.setEnabled(True) # ---------------- 回调 ---------------- def _done(self): self.start_btn.setEnabled(True) self.stop_btn.setEnabled(True) self.shutdown_btn.setEnabled(True) QMessageBox.information(self, '成功', '指令执行完成') def _error(self, msg): self.start_btn.setEnabled(True) self.stop_btn.setEnabled(True) self.shutdown_btn.setEnabled(True) QMessageBox.critical(self, '错误', f'指令执行失败:\n{msg}') def _shutdown_done(self): self.start_btn.setEnabled(True) self.stop_btn.setEnabled(True) self.shutdown_btn.setEnabled(True) QMessageBox.information(self, '成功', '下位机已关闭') def _shutdown_error(self, msg): self.start_btn.setEnabled(True) self.stop_btn.setEnabled(True) self.shutdown_btn.setEnabled(True) QMessageBox.critical(self, '错误', f'关闭下位机失败:\n{msg}') # ---------------- UI 辅助 ---------------- def _set_conn_style(self, connected: bool): if connected: self.conn_btn.setText('已连接') self.conn_btn.setStyleSheet('background-color:#4CAF50;color:white;') else: self.conn_btn.setText('连接') self.conn_btn.setStyleSheet('background-color:#e5e5e5;color:black;') def _set_ctrl_enabled(self, on: bool): self.start_btn.setEnabled(on) self.stop_btn.setEnabled(on) self.shutdown_btn.setEnabled(on and self._ssh_ever_connected) #pyinstaller -F --clean --onefile --windowed --name UI2 --icon=logo.ico --add-data="logo.ico;." --exclude-module=matplotlib --exclude-module=tkinter --exclude-module=PyQt5 --exclude-module=PyQt6 --exclude-module=PySide2 --exclude-module=PySide6.QtWebEngine --exclude-module=PySide6.QtWebEngineCore --exclude-module=PySide6.QtWebEngineWidgets --exclude-module=PySide6.QtSql --exclude-module=PySide6.QtXml --exclude-module=PySide6.QtMultimedia --exclude-module=PySide6.QtMultimediaWidgets --exclude-module=PySide6.QtOpenGL --exclude-module=PySide6.QtOpenGLWidgets --exclude-module=PySide6.QtPrintSupport --exclude-module=PySide6.QtTest --exclude-module=PySide6.QtHelp --exclude-module=PySide6.QtDesigner --exclude-module=PySide6.QtUiTools --exclude-module=PySide6.QtQml --exclude-module=PySide6.QtQuick --exclude-module=PySide6.QtQuickWidgets --exclude-module=PySide6.QtRemoteObjects --exclude-module=PySide6.QtScxml --exclude-module=PySide6.QtSensors --exclude-module=PySide6.QtSpatialAudio --exclude-module=PySide6.QtStateMachine --exclude-module=PySide6.QtSvg --exclude-module=PySide6.QtSvgWidgets --exclude-module=PySide6.QtTextToSpeech --exclude-module=PySide6.QtVirtualKeyboard --exclude-module=PySide6.Qt3DCore --exclude-module=PySide6.Qt3DRender --exclude-module=PySide6.Qt3DInput --exclude-module=PySide6.Qt3DLogic --exclude-module=PySide6.Qt3DExtras --exclude-module=PySide6.Qt3DAnimation --exclude-module=PySide6.Qt3DPhysics UI2.py # # -------------------- main -------------------- if __name__ == '__main__': app = QApplication(sys.argv) w = MainWindow() w.show() sys.exit(app.exec())
11-29
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值