将下面代码进行分解 分解成不同功能的。py文件
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.gray, 0))
painter.setPen(Qt.NoPen) # 设置边框为透明
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)
# def chek(self):
# self._ensure_socket()
# self.client_socket.sendall(bytes.fromhex('00020000000601052041FF00'))
# time.sleep(0.5)
# response = self.client_socket.recv(1024) # 接收返回数据
# print(f"Received response for power_down: {response.hex()}") # 打印返回数据
# IndicatorWorker 类
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 self._check_ssh_connection():
self.status_changed.emit(True, "仿真机已开机")
self._wait_for_on = False
elif self._wait_for_off:
if not self._check_ssh_connection():
self.status_changed.emit(False, "仿真机已关机")
self._wait_for_off = False
time.sleep(1)
def _check_ssh_connection(self):
try:
with socket.create_connection((self.target_ip, self.port), timeout=5):
return True
except (socket.timeout, socket.error):
return False
# DetectWorker 类
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 = ip
self.port = port
self.ssh_ip = ssh_ip
self.ssh_port = ssh_port
def run(self):
ok, msg = self._check_host_reachable()
ssh_ok = self._check_ssh_connection()
self.ssh_status_first.emit(ssh_ok, "仿真机已开机" if ssh_ok else "仿真机未连接")
self.done.emit(ok, msg)
def _check_host_reachable(self):
try:
with socket.create_connection((self.ip, self.port), timeout=5):
return True, f"TCP {self.port} 端口开放"
except (socket.timeout, socket.error):
return False, f"TCP {self.port} 端口无法连接"
def _check_ssh_connection(self):
try:
with socket.create_connection((self.ssh_ip, self.ssh_port), timeout=5):
return True
except (socket.timeout, socket.error):
return False
# PeriodicSSHCheckWorker 类
class PeriodicSSHCheckWorker(QObject):
status_changed = Signal(bool, str)
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 = self._check_ssh_connection()
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
time.sleep(30)
def _check_ssh_connection(self):
try:
with socket.create_connection((self.ssh_ip, self.port), timeout=5):
return True
except (socket.timeout, socket.error):
return False
# Worker 类
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()
else:
self.relay.power_down()
self.finished.emit()
except Exception as e:
self.error.emit(str(e))
# ShutdownWorker 类
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:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(self.ssh_ip, username=self.ssh_user, password=self.ssh_pwd)
stdin, stdout, stderr = ssh.exec_command('sudo shutdown -h now')
stdout.channel.recv_exit_status()
ssh.close()
self.finished.emit()
except Exception as e:
self.error.emit(str(e))
# MainWindow 类
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)
#pyinstaller -F --clean --onefile --windowed --name HIL远程开关机 --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 HIL远程开关机.py
# 主程序入口
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec())