._.

 多好的一顆苗子啊,終因無人栽培,活活的死了.
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, QPainter, QBrush, QPen, QColor import os # ---------- 配置文件路径 ---------- CONFIG_FILE = "data.ini" # ---------- Indicator 类 ---------- class Indicator(QWidget): def __init__(self, color="gray"): super().__init__() self.color = color self.init_ui() def init_ui(self): self.setFixedSize(20, 20) # 设置指示灯大小 def paintEvent(self, event): painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.setBrush(QBrush(QColor(self.color))) painter.setPen(QPen(Qt.black, 1)) painter.drawEllipse(2, 2, 16, 16) # 绘制圆形指示灯 def set_color(self, color): self.color = color self.update() # 重新绘制 # ---------- RelayControl 类 ---------- 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(self): self._ensure_socket() self.client_socket.sendall(bytes.fromhex('00000000000601052040FF00')) time.sleep(0.5) self.client_socket.sendall(bytes.fromhex('000100000006010520400000')) time.sleep(0.5) def power_down(self): self._ensure_socket() self.client_socket.sendall(bytes.fromhex('00020000000601052041FF00')) time.sleep(0.5) self.client_socket.sendall(bytes.fromhex('000300000006010520410000')) time.sleep(0.5) # ---------- 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.start_btn.clicked.connect(lambda: self.on_ctrl(up=True)) self.stop_btn.clicked.connect(lambda: self.on_ctrl(up=False)) self.shutdown_btn.clicked.connect(self.on_shutdown) # 替换为 Indicator 类 self.indicator = Indicator() # 使用 Indicator 类创建指示灯 self.indicator.setFixedSize(20, 20) # 设置指示灯大小 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() 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 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() 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.set_color(color) # 使用 Indicator 类更新颜色 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}') 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) # -------------------- main -------------------- if __name__ == '__main__': app = QApplication(sys.argv) w = MainWindow() w.show() sys.exit(app.exec()) 优化代码 删除没有存在的定义应用
最新发布
11-30
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值