feat(ui): 升级界面布局与波特率输入优化

- 窗口标题去除"(串口/Cat1 TCP)"后缀
- 浏览按钮固定窄宽(70px), 文件地址框随布局加宽
- 波特率下拉改为可编辑(setEditable), 候选项与主界面串口连接区一致(115200~1000000)
- 文件编码修正: 修复编辑过程产生的GBK污染, 统一UTF-8
This commit is contained in:
2026-10-10 15:18:44 +08:00
parent d325910bfe
commit bae784f849
+278 -64
View File
@@ -130,7 +130,9 @@ class SerialThread(QThread):
class TcpServerThread(QThread): class TcpServerThread(QThread):
"""TCP服务线程: 监听端口, 等待网关Cat1模组(AT+LIPOPEN)主动连入后透传升级帧。""" """TCP服务线程: 监听端口, 等待网关Cat1模组(AT+LIPOPEN)主动连入后透传升级帧。"""
client_connected = pyqtSignal(str) client_connected = pyqtSignal(str)
client_disconnected = pyqtSignal() client_disconnected = pyqtSignal(str, bool) # (client_ip, was_upgrade_session)
session_resumed = pyqtSignal(str) # (client_ip) upgrade session (re)locked
ip_list_changed = pyqtSignal(list) # 当前所有已连接客户端IP
data_received_raw = pyqtSignal(bytes) data_received_raw = pyqtSignal(bytes)
listen_error = pyqtSignal(str) listen_error = pyqtSignal(str)
@@ -138,83 +140,157 @@ class TcpServerThread(QThread):
super().__init__() super().__init__()
self.port = port self.port = port
self.running = True self.running = True
self._hexbuf = b"" # 设备上行ASCII hex文本的奇数尾巴缓冲 self._hexbufs = {} # per-conn hex tail buffer (device uplink is ASCII hex text)
self.sock = None # 监听socket self.sock = None # listen socket
self.conn = None # 已连接的客户端(网关模组) self.conn = None # upgrade session client (first valid ACK), None = not locked
self.conn_lock = threading.Lock() self.conn_lock = threading.Lock()
self._conns = {} # {sock: ip} all connected clients
self.expect_mac = None # target DevMac for ACK sniff (set by upgrade dialog); None = accept first valid ACK
self.target_ip = None # 握手成功的目标IP(升级包只发它)
self._errsince = {} # conn -> first OSError time (5s grace before declaring dead)
def run(self): def run(self):
try: try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind(('0.0.0.0', self.port)) self.sock.bind(('0.0.0.0', self.port))
self.sock.listen(1) self.sock.listen(5)
self.sock.settimeout(0.5) self.sock.settimeout(0.1)
except Exception as e: except Exception as e:
self.listen_error.emit(str(e)) self.listen_error.emit(str(e))
return return
while self.running: while self.running:
# 1) accept new clients (non-blocking poll)
try: try:
conn, addr = self.sock.accept() conn, addr = self.sock.accept()
conn.settimeout(0.05)
try:
conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
except Exception:
pass
with self.conn_lock:
self._conns[conn] = addr[0]
self.client_connected.emit(addr[0])
self._push_ip_list()
except socket.timeout: except socket.timeout:
continue pass
except OSError: except OSError:
break break
conn.settimeout(0.5) # 2) poll every client for data
try:
conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
except Exception:
pass
with self.conn_lock: with self.conn_lock:
self.conn = conn items = list(self._conns.items())
self.client_connected.emit(addr[0]) dead = []
while self.running: for conn, ip in items:
try: try:
data = conn.recv(4096) data = conn.recv(4096)
self._errsince.pop(conn, None)
except socket.timeout: except socket.timeout:
continue continue
except OSError: except OSError:
break t0 = self._errsince.get(conn)
if t0 is None:
self._errsince[conn] = time.monotonic()
continue
if time.monotonic() - t0 < 5.0:
continue
dead.append((conn, self.target_ip == ip))
continue
if not data: if not data:
break dead.append((conn, self.target_ip == ip))
self.data_received_raw.emit(self._decode_hex(data)) continue
with self.conn_lock: if self.target_ip is not None and ip != self.target_ip:
continue # session locked: only locked client's data is processed
self._sniff_ack_lock(conn, ip, data)
self.data_received_raw.emit(self._decode_hex_conn(conn, data))
# 3) clean up disconnected clients
for conn, was_session in dead:
with self.conn_lock:
ip = self._conns.pop(conn, None)
if self.conn is conn:
self.conn = None
self._hexbufs.pop(id(conn), None)
try: try:
conn.close() conn.close()
except Exception: except Exception:
pass pass
self.conn = None self.client_disconnected.emit(ip if ip else '', was_session)
self._hexbuf = b"" if was_session:
self.client_disconnected.emit() self.target_ip = None
self._push_ip_list()
try: try:
self.sock.close() self.sock.close()
except Exception: except Exception:
pass pass
def _decode_hex(self, data): def _sniff_ack_lock(self, conn, ip, data):
"""设备上行是ASCII hex文本(LIPSEND hex-ascii), 流式解码为二进制(保留奇数尾巴)。""" """握手阶段(目标未定): 嗅探首个合法 ACK(0x01,len3,总14) 或 REQ(0x02,len4,总15) 帧(0x7D+CRC),
buf = self._hexbuf + data 且帧内MAC与目标设备MAC一致时, 选定该IP为升级目标。"""
if self.target_ip is not None:
return
for i in range(len(data) - 13):
if data[i] != 0x7D:
continue
cmd = data[i + 7]
plen = data[i + 8]
is_ack = (cmd == 0x01 and plen == 0x03)
is_req = (cmd == 0x02 and plen == 0x04)
if not (is_ack or is_req):
continue
flen = 14 if is_ack else 15
if i + flen > len(data):
break
frame = data[i:i + flen]
if crc16_modbus(frame[:-2]) != (frame[-2] | (frame[-1] << 8)):
continue
if self.expect_mac is not None and bytes(frame[1:7]) != bytes(self.expect_mac):
continue # 非目标设备的握手应答(旧固件无MAC过滤): 不选定
with self.conn_lock:
if self.target_ip is None:
self.target_ip = ip
self.session_resumed.emit(ip)
return
def _decode_hex_conn(self, conn, data):
"""Device uplink is ASCII hex text: stream-decode to binary (keep odd tail), per connection."""
key = id(conn)
buf = self._hexbufs.get(key, b"") + data
n = len(buf) - (len(buf) % 2) n = len(buf) - (len(buf) % 2)
self._hexbuf = buf[n:] self._hexbufs[key] = buf[n:]
try: try:
return bytes.fromhex(buf[:n].decode('ascii')) return bytes.fromhex(buf[:n].decode('ascii'))
except ValueError: except ValueError:
self._hexbuf = b"" # 非法字符: 丢弃重同步 self._hexbufs[key] = b"" # invalid chars: drop and resync
return b"" return b""
def send_data(self, data): def send_data(self, data):
"""握手阶段(目标未定)广播所有连接; 目标选定后只发目标IP的连接。"""
with self.conn_lock: with self.conn_lock:
if not self.conn: if self.target_ip is not None:
return False targets = [c for c, ip in self._conns.items() if ip == self.target_ip]
else:
targets = list(self._conns.keys())
ok = False
for conn in targets:
try: try:
self.conn.sendall(data) conn.sendall(data)
return True ok = True
except Exception: except Exception:
return False pass
return ok
def _push_ip_list(self):
with self.conn_lock:
ips = sorted(set(self._conns.values()))
self.ip_list_changed.emit(ips)
def release_session(self):
"""升级结束(完成/取消/关闭): 清除目标IP, 恢复广播模式。"""
with self.conn_lock:
self.target_ip = None
def is_client_connected(self): def is_client_connected(self):
with self.conn_lock: with self.conn_lock:
return self.conn is not None return len(self._conns) > 0
def stop(self): def stop(self):
self.running = False self.running = False
@@ -706,7 +782,7 @@ class UpgradeDialog(QDialog):
def __init__(self, main_window): def __init__(self, main_window):
super().__init__(main_window) super().__init__(main_window)
self.main = main_window self.main = main_window
self.setWindowTitle("设备升级 - 通用升级协议V1.1 (串口/Cat1 TCP)") self.setWindowTitle("设备升级 - 通用升级协议V1.1")
self.setModal(False) self.setModal(False)
self.resize(720, 520) self.resize(720, 520)
@@ -740,6 +816,7 @@ class UpgradeDialog(QDialog):
self.fw_edit.setPlaceholderText("选择固件文件...") self.fw_edit.setPlaceholderText("选择固件文件...")
cfg_layout.addWidget(self.fw_edit, 0, 1) cfg_layout.addWidget(self.fw_edit, 0, 1)
btn_browse = QPushButton("浏览") btn_browse = QPushButton("浏览")
btn_browse.setFixedWidth(70)
btn_browse.clicked.connect(self._choose_file) btn_browse.clicked.connect(self._choose_file)
cfg_layout.addWidget(btn_browse, 0, 2) cfg_layout.addWidget(btn_browse, 0, 2)
@@ -768,19 +845,45 @@ class UpgradeDialog(QDialog):
self.btn_tcp_listen.setFixedWidth(90) self.btn_tcp_listen.setFixedWidth(90)
self.btn_tcp_listen.clicked.connect(self._toggle_tcp_listen) self.btn_tcp_listen.clicked.connect(self._toggle_tcp_listen)
tcp_row.addWidget(self.btn_tcp_listen) tcp_row.addWidget(self.btn_tcp_listen)
self.tcp_status_lbl = QLabel("未监听") self.ip_lbl = QLabel("客户端:")
self.tcp_status_lbl.setStyleSheet("color:#909090;") tcp_row.addWidget(self.ip_lbl)
tcp_row.addWidget(self.tcp_status_lbl) self.ip_cmb = QComboBox()
self.ip_cmb.setFixedWidth(140)
tcp_row.addWidget(self.ip_cmb)
tcp_row.addStretch() tcp_row.addStretch()
cfg_layout.addLayout(tcp_row, 2, 2) self.tcp_row_w = QWidget()
self.tcp_row_w.setLayout(tcp_row)
cfg_layout.addWidget(self.tcp_row_w, 2, 2)
# 串口参数行(串口通道): 串口选择/波特率/打开
serial_row = QHBoxLayout()
self.serial_cmb = QComboBox()
self.serial_cmb.setFixedWidth(150)
serial_row.addWidget(self.serial_cmb)
self.baud_cmb = QComboBox()
self.baud_cmb.setEditable(True)
self.baud_cmb.addItems(["115200", "230400", "460800", "500000", "921600", "1000000"])
self.baud_cmb.setCurrentText("500000")
self.baud_cmb.setFixedWidth(90)
serial_row.addWidget(self.baud_cmb)
self.btn_serial_open = QPushButton("打开串口")
self.btn_serial_open.setFixedWidth(90)
self.btn_serial_open.clicked.connect(self._toggle_serial)
serial_row.addWidget(self.btn_serial_open)
self.serial_status_lbl = QLabel("未打开")
self.serial_status_lbl.setStyleSheet("color:#909090;")
serial_row.addWidget(self.serial_status_lbl)
serial_row.addStretch()
self.serial_row_w = QWidget()
self.serial_row_w.setLayout(serial_row)
cfg_layout.addWidget(self.serial_row_w, 2, 2)
self.tcp_row_w.setVisible(False)
self._refresh_serial_ports()
btn_row = QHBoxLayout() btn_row = QHBoxLayout()
self.start_btn = QPushButton("开始升级") self.start_btn = QPushButton("开始升级")
self.start_btn.clicked.connect(self._start) self.start_btn.clicked.connect(self._on_start_btn)
btn_row.addWidget(self.start_btn) btn_row.addWidget(self.start_btn)
btn_close = QPushButton("关闭")
btn_close.clicked.connect(self.close)
btn_row.addWidget(btn_close)
btn_row.addStretch() btn_row.addStretch()
cfg_layout.addLayout(btn_row, 3, 0, 1, 3) cfg_layout.addLayout(btn_row, 3, 0, 1, 3)
@@ -823,6 +926,20 @@ class UpgradeDialog(QDialog):
def isUpgrading(self): def isUpgrading(self):
return self.running return self.running
def _release_session(self):
if self.tcp_thread:
self.tcp_thread.release_session()
def _cancel_upgrade(self):
"""升级中取消: 停止收发并释放会话锁定。"""
self.running = False
self._timer.stop()
self._log("升级已取消", "err")
self.status_lbl.setText("已取消")
self._release_session()
self.start_btn.setText("开始升级")
self.start_btn.setEnabled(True)
def _reset_ui(self): def _reset_ui(self):
"""重置弹窗 UI 到初始状态,避免关闭后重新打开时界面卡住。""" """重置弹窗 UI 到初始状态,避免关闭后重新打开时界面卡住。"""
self.running = False self.running = False
@@ -836,23 +953,68 @@ class UpgradeDialog(QDialog):
self._retry_cnt = 0 self._retry_cnt = 0
self._resume_base = 0 self._resume_base = 0
self.start_btn.setEnabled(True) self.start_btn.setEnabled(True)
self._release_session()
self.start_btn.setText("开始升级")
self.progress.setValue(0) self.progress.setValue(0)
self.status_lbl.setText("就绪") self.status_lbl.setText("就绪")
def _on_channel_changed(self, idx): def _on_channel_changed(self, idx):
self.channel = 'serial' if idx == 0 else 'tcp' self.channel = 'serial' if idx == 0 else 'tcp'
tcp_on = (self.channel == 'tcp') tcp_on = (self.channel == 'tcp')
self.tcp_lbl.setEnabled(tcp_on) self.tcp_row_w.setVisible(tcp_on)
self.tcp_port_edit.setEnabled(tcp_on) self.serial_row_w.setVisible(not tcp_on)
self.btn_tcp_listen.setEnabled(tcp_on)
def _refresh_serial_ports(self):
try:
ports = [pt.device for pt in serial.tools.list_ports.comports()]
except Exception:
ports = []
self.serial_cmb.clear()
if ports:
self.serial_cmb.addItems(ports)
else:
self.serial_cmb.addItem("无可用串口")
def _toggle_serial(self):
try:
if self.ser and self.ser.is_open:
if self._serial_rx:
self._serial_rx.stop()
self._serial_rx = None
self.ser.close()
self.ser = None
self.btn_serial_open.setText("打开串口")
self.serial_status_lbl.setText("未打开")
self.serial_status_lbl.setStyleSheet("color:#909090;")
return
port = self.serial_cmb.currentText()
if not port or port == "无可用串口":
QMessageBox.warning(self, "提示", "未扫描到串口, 请重新打开升级页面")
return
self.ser = serial.Serial(port, int(self.baud_cmb.currentText()), timeout=0.02)
self._serial_rx = SerialThread(self.ser)
self._serial_rx.data_received_raw.connect(self.feed_data)
self._serial_rx.start()
self.btn_serial_open.setText("关闭串口")
self.serial_status_lbl.setText("已打开")
self.serial_status_lbl.setStyleSheet("color:#2E7D32;")
except Exception as e:
self._log(f"串口操作异常: {e}", "err")
QMessageBox.warning(self, "提示", f"串口操作异常: {e}")
try:
if self.ser and self.ser.is_open:
self.ser.close()
except Exception:
pass
self.ser = None
self.btn_serial_open.setText("打开串口")
self.serial_status_lbl.setText("未打开")
def _toggle_tcp_listen(self): def _toggle_tcp_listen(self):
if self.tcp_thread and self.tcp_thread.isRunning(): if self.tcp_thread and self.tcp_thread.isRunning():
self.tcp_thread.stop() self.tcp_thread.stop()
self.tcp_thread = None self.tcp_thread = None
self.btn_tcp_listen.setText("开始监听") self.btn_tcp_listen.setText("开始监听")
self.tcp_status_lbl.setText("未监听")
self.tcp_status_lbl.setStyleSheet("color:#909090;")
return return
try: try:
port = int(self.tcp_port_edit.text().strip()) port = int(self.tcp_port_edit.text().strip())
@@ -863,27 +1025,35 @@ class UpgradeDialog(QDialog):
self.tcp_thread = TcpServerThread(port) self.tcp_thread = TcpServerThread(port)
self.tcp_thread.client_connected.connect(self._on_tcp_client) self.tcp_thread.client_connected.connect(self._on_tcp_client)
self.tcp_thread.client_disconnected.connect(self._on_tcp_disconnect) self.tcp_thread.client_disconnected.connect(self._on_tcp_disconnect)
self.tcp_thread.session_resumed.connect(self._on_session_resumed)
self.tcp_thread.ip_list_changed.connect(self._on_ip_list)
self.tcp_thread.data_received_raw.connect(self.feed_data) self.tcp_thread.data_received_raw.connect(self.feed_data)
self.tcp_thread.listen_error.connect(self._on_tcp_error) self.tcp_thread.listen_error.connect(self._on_tcp_error)
self.tcp_thread.start() self.tcp_thread.start()
self.btn_tcp_listen.setText("停止监听") self.btn_tcp_listen.setText("停止监听")
self.tcp_status_lbl.setText(f"监听中(0.0.0.0:{port})")
self.tcp_status_lbl.setStyleSheet("color:#1565C0;") def _on_ip_list(self, ips):
self.ip_cmb.clear()
if ips:
self.ip_cmb.addItems(ips)
else:
self.ip_cmb.addItem("无客户端")
def _on_tcp_client(self, ip): def _on_tcp_client(self, ip):
self.tcp_status_lbl.setText(f"已连接: {ip}") pass
self.tcp_status_lbl.setStyleSheet("color:#2E7D32;")
def _on_tcp_disconnect(self): def _on_session_resumed(self, ip):
if self.tcp_thread and self.tcp_thread.isRunning():
self.tcp_status_lbl.setText("客户端断开,等待重连...")
self.tcp_status_lbl.setStyleSheet("color:#D32F2F;")
if self.running and self.channel == 'tcp': if self.running and self.channel == 'tcp':
self._log("TCP连接断开,等待网关模组重连...", "err") self._log(f"握手成功, 升级目标: {ip}", "ok")
idx = self.ip_cmb.findText(ip)
if idx >= 0:
self.ip_cmb.setCurrentIndex(idx)
def _on_tcp_disconnect(self, ip, was_session):
if was_session and self.running and self.channel == 'tcp':
self._log(f"升级会话TCP断开(IP={ip}),等待网关模组重连后自动恢复...", "err")
def _on_tcp_error(self, msg): def _on_tcp_error(self, msg):
self.tcp_status_lbl.setText("监听失败")
self.tcp_status_lbl.setStyleSheet("color:#D32F2F;")
self._log(f"TCP监听失败: {msg}", "err") self._log(f"TCP监听失败: {msg}", "err")
if self.tcp_thread: if self.tcp_thread:
self.tcp_thread.stop() self.tcp_thread.stop()
@@ -897,7 +1067,15 @@ class UpgradeDialog(QDialog):
self._log("TCP未连接,发送失败", "err") self._log("TCP未连接,发送失败", "err")
return False return False
return self.tcp_thread.send_data(frame.hex().upper().encode('ascii')) return self.tcp_thread.send_data(frame.hex().upper().encode('ascii'))
return self.main.send_raw(frame) if not (self.ser and self.ser.is_open):
self._log("串口未打开,发送失败", "err")
return False
try:
self.ser.write(frame)
return True
except Exception:
self._log("串口发送失败", "err")
return False
def _choose_file(self): def _choose_file(self):
path, _ = QFileDialog.getOpenFileName( path, _ = QFileDialog.getOpenFileName(
@@ -918,8 +1096,16 @@ class UpgradeDialog(QDialog):
if text: if text:
self._log(f"[设备] {text}") self._log(f"[设备] {text}")
def _on_start_btn(self):
"""升级中=取消; 空闲=开始升级。"""
if self.running:
self._cancel_upgrade()
else:
self._start()
def _start(self): def _start(self):
if self.running: if self.running:
self._cancel_upgrade()
return return
path = self.fw_edit.text().strip() path = self.fw_edit.text().strip()
mac = parse_mac(self.mac_edit.text()) mac = parse_mac(self.mac_edit.text())
@@ -933,7 +1119,7 @@ class UpgradeDialog(QDialog):
if not (self.tcp_thread and self.tcp_thread.is_client_connected()): if not (self.tcp_thread and self.tcp_thread.is_client_connected()):
QMessageBox.warning(self, "提示", "TCP未监听或网关未连接, 请先监听并等待网关接入") QMessageBox.warning(self, "提示", "TCP未监听或网关未连接, 请先监听并等待网关接入")
return return
elif not self.main.serial_port or not self.main.serial_port.is_open: elif not self.ser and self.ser.is_open:
QMessageBox.warning(self, "提示", "请先打开串口") QMessageBox.warning(self, "提示", "请先打开串口")
return return
try: try:
@@ -950,6 +1136,9 @@ class UpgradeDialog(QDialog):
return return
self._mac = mac self._mac = mac
if getattr(self, 'tcp_thread', None) is not None:
self.tcp_thread.expect_mac = mac
self.tcp_thread.release_session()
self._fw_data = fw self._fw_data = fw
self._got_ack = False self._got_ack = False
self._announce_frame = None self._announce_frame = None
@@ -957,7 +1146,7 @@ class UpgradeDialog(QDialog):
self._sent.clear() self._sent.clear()
self._buf = b"" self._buf = b""
self.running = True self.running = True
self.start_btn.setEnabled(False) self.start_btn.setText("取消")
self.progress.setValue(0) self.progress.setValue(0)
self._window_start = 0 self._window_start = 0
self._retry_cnt = 0 self._retry_cnt = 0
@@ -974,8 +1163,7 @@ class UpgradeDialog(QDialog):
self._timer.start(10000 if self.channel == 'tcp' else 5000) self._timer.start(10000 if self.channel == 'tcp' else 5000)
def feed_data(self, data): def feed_data(self, data):
if not self.running: # keep parsing after finish: device sends REQ(0) to restart download on CRC fail, gate moved to _handle
return
self._buf += data self._buf += data
while True: while True:
frame = self._try_next_frame() frame = self._try_next_frame()
@@ -1009,6 +1197,10 @@ class UpgradeDialog(QDialog):
return None return None
def _handle(self, cmd, payload): def _handle(self, cmd, payload):
if not self.running and cmd != CMD_REQ_PKG:
return # finished: only REQ(0) restart passes, other frames dropped
if not self.running:
return
self._timer.start(3000 if self.channel == 'tcp' else 5000) self._timer.start(3000 if self.channel == 'tcp' else 5000)
self._retry_cnt = 0 self._retry_cnt = 0
if cmd == CMD_ACK: if cmd == CMD_ACK:
@@ -1022,6 +1214,10 @@ class UpgradeDialog(QDialog):
else: else:
self._resume_base = 0 self._resume_base = 0
self._log("收到从机应答(0x01), 全新升级", "ok") self._log("收到从机应答(0x01), 全新升级", "ok")
if self.channel == 'tcp':
self._window_start = 0
self._send_window(0, self._window)
return
else: else:
self._resume_base = 0 self._resume_base = 0
self._log("收到从机应答(0x01)", "ok") self._log("收到从机应答(0x01)", "ok")
@@ -1032,6 +1228,13 @@ class UpgradeDialog(QDialog):
self._log("从机请求包帧格式错误", "err") self._log("从机请求包帧格式错误", "err")
return return
idx, total = struct.unpack('<HH', payload[0:4]) idx, total = struct.unpack('<HH', payload[0:4])
if idx == 0 and not self.running:
# device CRC failed, full re-download: reset progress and resend from pkt 0
self.running = True
self._sent.clear()
self.progress.setValue(0)
self.start_btn.setEnabled(False)
self._log("设备CRC校验失败, 已重置进度重新下载", "warn")
if idx >= self._pkg_num: if idx >= self._pkg_num:
self._log(f"从机请求包序号越界({idx}),视为完成", "ok") self._log(f"从机请求包序号越界({idx}),视为完成", "ok")
self._finish() self._finish()
@@ -1055,6 +1258,8 @@ class UpgradeDialog(QDialog):
self._log("发送失败, 通道异常", "err") self._log("发送失败, 通道异常", "err")
return return
self._sent.add(idx) self._sent.add(idx)
if k < batch - 1:
time.sleep(0.005) # Cat1模组URC输出限流: 防LIPURC截断丢包
done = max(self._sent) + 1 done = max(self._sent) + 1
self.progress.setValue(min(int(done * 100.0 / self._pkg_num), 100)) self.progress.setValue(min(int(done * 100.0 / self._pkg_num), 100))
self.status_lbl.setText(f"{done}/{self._pkg_num} 包") self.status_lbl.setText(f"{done}/{self._pkg_num} 包")
@@ -1072,6 +1277,8 @@ class UpgradeDialog(QDialog):
else: else:
self._log(f"升级未完成,已发送 {len(self._sent)}/{self._pkg_num} 包", "err") self._log(f"升级未完成,已发送 {len(self._sent)}/{self._pkg_num} 包", "err")
self._release_session()
self.start_btn.setText("开始升级")
def _on_timeout(self): def _on_timeout(self):
if not self.running: if not self.running:
return return
@@ -1095,11 +1302,18 @@ class UpgradeDialog(QDialog):
self._log("升级超时(无响应)", "err") self._log("升级超时(无响应)", "err")
self.status_lbl.setText("超时") self.status_lbl.setText("超时")
self._release_session()
self.start_btn.setText("开始升级")
def closeEvent(self, event): def closeEvent(self, event):
if self.running:
self._cancel_upgrade()
else:
self._release_session()
if self.ser and self.ser.is_open:
self._toggle_serial()
self._reset_ui() self._reset_ui()
event.accept() event.accept()
class DeviceDialog(QDialog): class DeviceDialog(QDialog):
"""设备管理对话框:通过 culs 查询已注册通讯单元,cudel 删除设备。""" """设备管理对话框:通过 culs 查询已注册通讯单元,cudel 删除设备。"""