标题栏增加版本标识:网关配置工具 V1.2(窄带)
This commit is contained in:
+146
-146
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
网关配置工具
|
网关配置工具
|
||||||
串口配置:波特率500000(可修改),数据位8,停止位1,校验位None
|
串口配置:波特率500000(可修改),数据位8,停止位1,校验位None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@@ -30,18 +30,18 @@ def resource_path(relative_path):
|
|||||||
return os.path.join(os.path.abspath(os.path.dirname(__file__)), relative_path)
|
return os.path.join(os.path.abspath(os.path.dirname(__file__)), relative_path)
|
||||||
|
|
||||||
|
|
||||||
# ── 传感器通用串口升级协议 V1.1 ─────────────────────────────
|
# ── 传感器通用串口升级协议 V1.1 ─────────────────────────────
|
||||||
FRAME_HEADER = 0x7D
|
FRAME_HEADER = 0x7D
|
||||||
UPG_PACKET_DATA = 200 # 每包数据长度上限
|
UPG_PACKET_DATA = 200 # 每包数据长度上限
|
||||||
CMD_ANNOUNCE = 0x81 # 主机->从机:下发APP请求
|
CMD_ANNOUNCE = 0x81 # 主机->从机:下发APP请求
|
||||||
CMD_ACK = 0x01 # 从机->主机:应答(Response=0x14)
|
CMD_ACK = 0x01 # 从机->主机:应答(Response=0x14)
|
||||||
CMD_REQ_PKG = 0x02 # 从机->主机:请求包序号
|
CMD_REQ_PKG = 0x02 # 从机->主机:请求包序号
|
||||||
CMD_SEND_PKG = 0x82 # 主机->从机:下发包
|
CMD_SEND_PKG = 0x82 # 主机->从机:下发包
|
||||||
PKG_DATALEN_MAX = 200
|
PKG_DATALEN_MAX = 200
|
||||||
|
|
||||||
|
|
||||||
def crc16_modbus(data):
|
def crc16_modbus(data):
|
||||||
"""MODBUS CRC16,多项式0x8005(反射),返回16位整数。"""
|
"""MODBUS CRC16,多项式0x8005(反射),返回16位整数。"""
|
||||||
crc = 0xFFFF
|
crc = 0xFFFF
|
||||||
for b in data:
|
for b in data:
|
||||||
crc ^= b
|
crc ^= b
|
||||||
@@ -54,14 +54,14 @@ def crc16_modbus(data):
|
|||||||
|
|
||||||
|
|
||||||
def build_frame(dev_mac, cmd, payload):
|
def build_frame(dev_mac, cmd, payload):
|
||||||
"""0x7D + DevMac(6) + Cmd + PayloadLen + Payload + CRC16(小端)。"""
|
"""0x7D + DevMac(6) + Cmd + PayloadLen + Payload + CRC16(小端)。"""
|
||||||
body = bytes([FRAME_HEADER]) + bytes(dev_mac) + bytes([cmd, len(payload)]) + bytes(payload)
|
body = bytes([FRAME_HEADER]) + bytes(dev_mac) + bytes([cmd, len(payload)]) + bytes(payload)
|
||||||
crc = crc16_modbus(body)
|
crc = crc16_modbus(body)
|
||||||
return body + struct.pack('<H', crc)
|
return body + struct.pack('<H', crc)
|
||||||
|
|
||||||
|
|
||||||
def parse_mac(s):
|
def parse_mac(s):
|
||||||
"""'81-00-00-06-00-00' -> bytes(6),无效返回 None。"""
|
"""'81-00-00-06-00-00' -> bytes(6),无效返回 None。"""
|
||||||
if not s:
|
if not s:
|
||||||
return None
|
return None
|
||||||
parts = s.strip().replace('-', ':').split(':')
|
parts = s.strip().replace('-', ':').split(':')
|
||||||
@@ -93,7 +93,7 @@ class SerialThread(QThread):
|
|||||||
self.data_received_raw.emit(data)
|
self.data_received_raw.emit(data)
|
||||||
buf = self._buffer + data
|
buf = self._buffer + data
|
||||||
text = None
|
text = None
|
||||||
# 优先 UTF-8;若结尾是不完整字符序列则保留尾部待下次拼接,避免乱码缺字
|
# 优先 UTF-8;若结尾是不完整字符序列则保留尾部待下次拼接,避免乱码缺字
|
||||||
for enc in ('utf-8', 'gbk'):
|
for enc in ('utf-8', 'gbk'):
|
||||||
try:
|
try:
|
||||||
text = buf.decode(enc)
|
text = buf.decode(enc)
|
||||||
@@ -133,7 +133,7 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
self.serial_thread = None
|
self.serial_thread = None
|
||||||
self._recv_buffer = ""
|
self._recv_buffer = ""
|
||||||
self._para_mode = False
|
self._para_mode = False
|
||||||
# para 大数据量返回时,用定时器延迟解析,避免每段数据全量解析阻塞UI导致串口缓冲溢出丢包
|
# para 大数据量返回时,用定时器延迟解析,避免每段数据全量解析阻塞UI导致串口缓冲溢出丢包
|
||||||
self._para_timer = QTimer(self)
|
self._para_timer = QTimer(self)
|
||||||
self._para_timer.setSingleShot(True)
|
self._para_timer.setSingleShot(True)
|
||||||
self._para_timer.setInterval(250)
|
self._para_timer.setInterval(250)
|
||||||
@@ -144,8 +144,8 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
self.refresh_ports()
|
self.refresh_ports()
|
||||||
|
|
||||||
def init_ui(self):
|
def init_ui(self):
|
||||||
self.setWindowTitle("网关配置工具")
|
self.setWindowTitle("网关配置工具 V1.2(窄带)")
|
||||||
self.setWindowIcon(QIcon(resource_path("物联网网关.png")))
|
self.setWindowIcon(QIcon(resource_path("物联网网关.png")))
|
||||||
self.setMinimumSize(940, 640)
|
self.setMinimumSize(940, 640)
|
||||||
self.resize(1000, 700)
|
self.resize(1000, 700)
|
||||||
|
|
||||||
@@ -155,35 +155,35 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
main_layout.setSpacing(4)
|
main_layout.setSpacing(4)
|
||||||
main_layout.setContentsMargins(4, 4, 4, 4)
|
main_layout.setContentsMargins(4, 4, 4, 4)
|
||||||
|
|
||||||
# === 串口连接区 ===
|
# === 串口连接区 ===
|
||||||
conn_group = QGroupBox("串口连接")
|
conn_group = QGroupBox("串口连接")
|
||||||
conn_layout = QHBoxLayout(conn_group)
|
conn_layout = QHBoxLayout(conn_group)
|
||||||
|
|
||||||
conn_layout.addWidget(QLabel("串口:"))
|
conn_layout.addWidget(QLabel("串口:"))
|
||||||
self.combo_port = QComboBox()
|
self.combo_port = QComboBox()
|
||||||
self.combo_port.setMinimumWidth(120)
|
self.combo_port.setMinimumWidth(120)
|
||||||
conn_layout.addWidget(self.combo_port)
|
conn_layout.addWidget(self.combo_port)
|
||||||
|
|
||||||
self.btn_refresh = QPushButton("刷新")
|
self.btn_refresh = QPushButton("刷新")
|
||||||
self.btn_refresh.setFixedWidth(60)
|
self.btn_refresh.setFixedWidth(60)
|
||||||
self.btn_refresh.clicked.connect(self.refresh_ports)
|
self.btn_refresh.clicked.connect(self.refresh_ports)
|
||||||
conn_layout.addWidget(self.btn_refresh)
|
conn_layout.addWidget(self.btn_refresh)
|
||||||
|
|
||||||
conn_layout.addSpacing(20)
|
conn_layout.addSpacing(20)
|
||||||
conn_layout.addWidget(QLabel("波特率:"))
|
conn_layout.addWidget(QLabel("波特率:"))
|
||||||
self.combo_baud = QComboBox()
|
self.combo_baud = QComboBox()
|
||||||
self.combo_baud.setEditable(True)
|
self.combo_baud.setEditable(True)
|
||||||
self.combo_baud.addItems(["115200", "230400", "460800", "500000", "921600", "1000000"])
|
self.combo_baud.addItems(["115200", "230400", "460800", "500000", "921600", "1000000"])
|
||||||
self.combo_baud.setCurrentText("500000")
|
self.combo_baud.setCurrentText("500000")
|
||||||
self.combo_baud.setFixedWidth(90)
|
self.combo_baud.setFixedWidth(90)
|
||||||
conn_layout.addWidget(self.combo_baud)
|
conn_layout.addWidget(self.combo_baud)
|
||||||
conn_layout.addWidget(QLabel("数据位: 8"))
|
conn_layout.addWidget(QLabel("数据位: 8"))
|
||||||
conn_layout.addWidget(QLabel("停止位: 1"))
|
conn_layout.addWidget(QLabel("停止位: 1"))
|
||||||
conn_layout.addWidget(QLabel("校验: None"))
|
conn_layout.addWidget(QLabel("校验: None"))
|
||||||
|
|
||||||
conn_layout.addStretch()
|
conn_layout.addStretch()
|
||||||
|
|
||||||
self.btn_connect = QPushButton("打开串口")
|
self.btn_connect = QPushButton("打开串口")
|
||||||
self.btn_connect.setFixedWidth(100)
|
self.btn_connect.setFixedWidth(100)
|
||||||
self.btn_connect.setStyleSheet("font-weight:bold;")
|
self.btn_connect.setStyleSheet("font-weight:bold;")
|
||||||
self.btn_connect.clicked.connect(self.toggle_serial)
|
self.btn_connect.clicked.connect(self.toggle_serial)
|
||||||
@@ -191,17 +191,17 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
|
|
||||||
main_layout.addWidget(conn_group)
|
main_layout.addWidget(conn_group)
|
||||||
|
|
||||||
# === 参数配置区 ===
|
# === 参数配置区 ===
|
||||||
config_group = QGroupBox("参数配置")
|
config_group = QGroupBox("参数配置")
|
||||||
config_group_layout = QVBoxLayout(config_group)
|
config_group_layout = QVBoxLayout(config_group)
|
||||||
config_group_layout.setSpacing(4)
|
config_group_layout.setSpacing(4)
|
||||||
|
|
||||||
# Lora 参数(子分组)
|
# Lora 参数(子分组)
|
||||||
lora_group = QGroupBox("Lora 参数")
|
lora_group = QGroupBox("Lora 参数")
|
||||||
lora_layout = QHBoxLayout(lora_group)
|
lora_layout = QHBoxLayout(lora_group)
|
||||||
lora_layout.setSpacing(6)
|
lora_layout.setSpacing(6)
|
||||||
|
|
||||||
lora_layout.addWidget(QLabel("信道频率(fc):"))
|
lora_layout.addWidget(QLabel("信道频率(fc):"))
|
||||||
self.combo_lora_fc = QComboBox()
|
self.combo_lora_fc = QComboBox()
|
||||||
self.combo_lora_fc.setFixedWidth(200)
|
self.combo_lora_fc.setFixedWidth(200)
|
||||||
lora_freqs = []
|
lora_freqs = []
|
||||||
@@ -209,7 +209,7 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
ch_idx = 0
|
ch_idx = 0
|
||||||
while fc_hz <= 492600000:
|
while fc_hz <= 492600000:
|
||||||
mhz = fc_hz / 1000000
|
mhz = fc_hz / 1000000
|
||||||
lora_freqs.append((str(fc_hz), f"信道{ch_idx}: {mhz:.1f}MHz"))
|
lora_freqs.append((str(fc_hz), f"信道{ch_idx}: {mhz:.1f}MHz"))
|
||||||
fc_hz += 500000
|
fc_hz += 500000
|
||||||
ch_idx += 1
|
ch_idx += 1
|
||||||
for freq, desc in lora_freqs:
|
for freq, desc in lora_freqs:
|
||||||
@@ -221,40 +221,40 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
)
|
)
|
||||||
lora_layout.addWidget(self.combo_lora_fc)
|
lora_layout.addWidget(self.combo_lora_fc)
|
||||||
|
|
||||||
self.btn_lora_fc = QPushButton("发送")
|
self.btn_lora_fc = QPushButton("发送")
|
||||||
self.btn_lora_fc.setFixedWidth(60)
|
self.btn_lora_fc.setFixedWidth(60)
|
||||||
self.btn_lora_fc.setStyleSheet("font-weight:bold;")
|
self.btn_lora_fc.setStyleSheet("font-weight:bold;")
|
||||||
self.btn_lora_fc.clicked.connect(self.on_lora_send)
|
self.btn_lora_fc.clicked.connect(self.on_lora_send)
|
||||||
lora_layout.addWidget(self.btn_lora_fc)
|
lora_layout.addWidget(self.btn_lora_fc)
|
||||||
|
|
||||||
lora_hint = QLabel("设置LoRa通信信道频率")
|
lora_hint = QLabel("设置LoRa通信信道频率")
|
||||||
lora_hint.setStyleSheet("color:#909090;")
|
lora_hint.setStyleSheet("color:#909090;")
|
||||||
lora_layout.addWidget(lora_hint)
|
lora_layout.addWidget(lora_hint)
|
||||||
lora_layout.addStretch()
|
lora_layout.addStretch()
|
||||||
|
|
||||||
config_group_layout.addWidget(lora_group)
|
config_group_layout.addWidget(lora_group)
|
||||||
|
|
||||||
# 查看配置按钮 + 提示
|
# 查看配置按钮 + 提示
|
||||||
btn_row = QHBoxLayout()
|
btn_row = QHBoxLayout()
|
||||||
btn_row.setSpacing(6)
|
btn_row.setSpacing(6)
|
||||||
self.btn_para = QPushButton("查看配置(para)")
|
self.btn_para = QPushButton("查看配置(para)")
|
||||||
self.btn_para.setFixedWidth(130)
|
self.btn_para.setFixedWidth(130)
|
||||||
self.btn_para.clicked.connect(self.on_para_clicked)
|
self.btn_para.clicked.connect(self.on_para_clicked)
|
||||||
btn_row.addWidget(self.btn_para)
|
btn_row.addWidget(self.btn_para)
|
||||||
|
|
||||||
self.btn_upgrade = QPushButton("设备升级")
|
self.btn_upgrade = QPushButton("设备升级")
|
||||||
self.btn_upgrade.setFixedWidth(100)
|
self.btn_upgrade.setFixedWidth(100)
|
||||||
self.btn_upgrade.setStyleSheet("background-color:#1976D2; color:white; font-weight:bold;")
|
self.btn_upgrade.setStyleSheet("background-color:#1976D2; color:white; font-weight:bold;")
|
||||||
self.btn_upgrade.clicked.connect(self.on_upgrade_clicked)
|
self.btn_upgrade.clicked.connect(self.on_upgrade_clicked)
|
||||||
btn_row.addWidget(self.btn_upgrade)
|
btn_row.addWidget(self.btn_upgrade)
|
||||||
|
|
||||||
self.btn_dev_mgmt = QPushButton("设备管理")
|
self.btn_dev_mgmt = QPushButton("设备管理")
|
||||||
self.btn_dev_mgmt.setFixedWidth(100)
|
self.btn_dev_mgmt.setFixedWidth(100)
|
||||||
self.btn_dev_mgmt.setStyleSheet("background-color:#388E3C; color:white; font-weight:bold;")
|
self.btn_dev_mgmt.setStyleSheet("background-color:#388E3C; color:white; font-weight:bold;")
|
||||||
self.btn_dev_mgmt.clicked.connect(self.on_dev_mgmt_clicked)
|
self.btn_dev_mgmt.clicked.connect(self.on_dev_mgmt_clicked)
|
||||||
btn_row.addWidget(self.btn_dev_mgmt)
|
btn_row.addWidget(self.btn_dev_mgmt)
|
||||||
|
|
||||||
para_hint = QLabel("配置后可通过para命令验证是否成功")
|
para_hint = QLabel("配置后可通过para命令验证是否成功")
|
||||||
para_hint.setStyleSheet("color:#909090;")
|
para_hint.setStyleSheet("color:#909090;")
|
||||||
btn_row.addWidget(para_hint)
|
btn_row.addWidget(para_hint)
|
||||||
btn_row.addStretch()
|
btn_row.addStretch()
|
||||||
@@ -262,8 +262,8 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
|
|
||||||
main_layout.addWidget(config_group)
|
main_layout.addWidget(config_group)
|
||||||
|
|
||||||
# === 设备参数信息区 ===
|
# === 设备参数信息区 ===
|
||||||
info_group = QGroupBox("设备参数信息")
|
info_group = QGroupBox("设备参数信息")
|
||||||
info_grid = QGridLayout(info_group)
|
info_grid = QGridLayout(info_group)
|
||||||
info_grid.setSpacing(8)
|
info_grid.setSpacing(8)
|
||||||
info_grid.setContentsMargins(12, 8, 12, 8)
|
info_grid.setContentsMargins(12, 8, 12, 8)
|
||||||
@@ -274,34 +274,34 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
return lb
|
return lb
|
||||||
|
|
||||||
row = 0
|
row = 0
|
||||||
info_grid.addWidget(QLabel("软件版本:"), row, 0)
|
info_grid.addWidget(QLabel("软件版本:"), row, 0)
|
||||||
self.label_sw_ver = _val_label()
|
self.label_sw_ver = _val_label()
|
||||||
info_grid.addWidget(self.label_sw_ver, row, 1)
|
info_grid.addWidget(self.label_sw_ver, row, 1)
|
||||||
info_grid.addWidget(QLabel("硬件版本:"), row, 2)
|
info_grid.addWidget(QLabel("硬件版本:"), row, 2)
|
||||||
self.label_hw_ver = _val_label()
|
self.label_hw_ver = _val_label()
|
||||||
info_grid.addWidget(self.label_hw_ver, row, 3)
|
info_grid.addWidget(self.label_hw_ver, row, 3)
|
||||||
|
|
||||||
row = 1
|
row = 1
|
||||||
info_grid.addWidget(QLabel("电池电量:"), row, 0)
|
info_grid.addWidget(QLabel("电池电量:"), row, 0)
|
||||||
self.label_battery = _val_label()
|
self.label_battery = _val_label()
|
||||||
info_grid.addWidget(self.label_battery, row, 1)
|
info_grid.addWidget(self.label_battery, row, 1)
|
||||||
info_grid.addWidget(QLabel("注册状态:"), row, 2)
|
info_grid.addWidget(QLabel("注册状态:"), row, 2)
|
||||||
self.label_reg_status = _val_label()
|
self.label_reg_status = _val_label()
|
||||||
info_grid.addWidget(self.label_reg_status, row, 3)
|
info_grid.addWidget(self.label_reg_status, row, 3)
|
||||||
|
|
||||||
row = 2
|
row = 2
|
||||||
info_grid.addWidget(QLabel("通讯方式:"), row, 0)
|
info_grid.addWidget(QLabel("通讯方式:"), row, 0)
|
||||||
self.label_cur_comm = _val_label()
|
self.label_cur_comm = _val_label()
|
||||||
info_grid.addWidget(self.label_cur_comm, row, 1)
|
info_grid.addWidget(self.label_cur_comm, row, 1)
|
||||||
info_grid.addWidget(QLabel("Lora频率:"), row, 2)
|
info_grid.addWidget(QLabel("Lora频率:"), row, 2)
|
||||||
self.label_cur_lora_fc = _val_label()
|
self.label_cur_lora_fc = _val_label()
|
||||||
info_grid.addWidget(self.label_cur_lora_fc, row, 3)
|
info_grid.addWidget(self.label_cur_lora_fc, row, 3)
|
||||||
|
|
||||||
row = 3
|
row = 3
|
||||||
info_grid.addWidget(QLabel("网关MAC:"), row, 0)
|
info_grid.addWidget(QLabel("网关MAC:"), row, 0)
|
||||||
self.label_gw_mac = _val_label()
|
self.label_gw_mac = _val_label()
|
||||||
info_grid.addWidget(self.label_gw_mac, row, 1)
|
info_grid.addWidget(self.label_gw_mac, row, 1)
|
||||||
info_grid.addWidget(QLabel("通讯单元读取间隔:"), row, 2)
|
info_grid.addWidget(QLabel("通讯单元读取间隔:"), row, 2)
|
||||||
self.label_comm_interval = _val_label()
|
self.label_comm_interval = _val_label()
|
||||||
info_grid.addWidget(self.label_comm_interval, row, 3)
|
info_grid.addWidget(self.label_comm_interval, row, 3)
|
||||||
|
|
||||||
@@ -315,13 +315,13 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
|
|
||||||
main_layout.addWidget(info_group)
|
main_layout.addWidget(info_group)
|
||||||
|
|
||||||
# === 下方:通讯日志 + 自定义指令 ===
|
# === 下方:通讯日志 + 自定义指令 ===
|
||||||
bottom_widget = QWidget()
|
bottom_widget = QWidget()
|
||||||
bottom_layout = QHBoxLayout(bottom_widget)
|
bottom_layout = QHBoxLayout(bottom_widget)
|
||||||
bottom_layout.setContentsMargins(0, 0, 0, 0)
|
bottom_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
bottom_layout.setSpacing(4)
|
bottom_layout.setSpacing(4)
|
||||||
|
|
||||||
log_group = QGroupBox("通讯日志")
|
log_group = QGroupBox("通讯日志")
|
||||||
log_inner = QVBoxLayout(log_group)
|
log_inner = QVBoxLayout(log_group)
|
||||||
|
|
||||||
self.text_log = QTextEdit()
|
self.text_log = QTextEdit()
|
||||||
@@ -333,23 +333,23 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
log_inner.addWidget(self.text_log)
|
log_inner.addWidget(self.text_log)
|
||||||
|
|
||||||
log_btn_layout = QHBoxLayout()
|
log_btn_layout = QHBoxLayout()
|
||||||
self.btn_clear_log = QPushButton("清空日志")
|
self.btn_clear_log = QPushButton("清空日志")
|
||||||
self.btn_clear_log.clicked.connect(self.text_log.clear)
|
self.btn_clear_log.clicked.connect(self.text_log.clear)
|
||||||
log_btn_layout.addWidget(self.btn_clear_log)
|
log_btn_layout.addWidget(self.btn_clear_log)
|
||||||
log_btn_layout.addStretch()
|
log_btn_layout.addStretch()
|
||||||
log_inner.addLayout(log_btn_layout)
|
log_inner.addLayout(log_btn_layout)
|
||||||
|
|
||||||
custom_group = QGroupBox("自定义指令")
|
custom_group = QGroupBox("自定义指令")
|
||||||
custom_inner = QVBoxLayout(custom_group)
|
custom_inner = QVBoxLayout(custom_group)
|
||||||
custom_inner.setSpacing(6)
|
custom_inner.setSpacing(6)
|
||||||
|
|
||||||
custom_inner.addWidget(QLabel("指令:"))
|
custom_inner.addWidget(QLabel("指令:"))
|
||||||
self.edit_custom = QLineEdit()
|
self.edit_custom = QLineEdit()
|
||||||
self.edit_custom.setPlaceholderText("输入指令...")
|
self.edit_custom.setPlaceholderText("输入指令...")
|
||||||
self.edit_custom.returnPressed.connect(self.send_custom_command)
|
self.edit_custom.returnPressed.connect(self.send_custom_command)
|
||||||
custom_inner.addWidget(self.edit_custom)
|
custom_inner.addWidget(self.edit_custom)
|
||||||
|
|
||||||
self.btn_custom_send = QPushButton("发送")
|
self.btn_custom_send = QPushButton("发送")
|
||||||
self.btn_custom_send.setStyleSheet("font-weight:bold;")
|
self.btn_custom_send.setStyleSheet("font-weight:bold;")
|
||||||
self.btn_custom_send.clicked.connect(self.send_custom_command)
|
self.btn_custom_send.clicked.connect(self.send_custom_command)
|
||||||
custom_inner.addWidget(self.btn_custom_send)
|
custom_inner.addWidget(self.btn_custom_send)
|
||||||
@@ -359,20 +359,20 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
bottom_layout.addWidget(custom_group, 1)
|
bottom_layout.addWidget(custom_group, 1)
|
||||||
|
|
||||||
main_layout.addWidget(bottom_widget, 1)
|
main_layout.addWidget(bottom_widget, 1)
|
||||||
self.statusBar().showMessage("就绪 - 请选择串口并连接")
|
self.statusBar().showMessage("就绪 - 请选择串口并连接")
|
||||||
|
|
||||||
def refresh_ports(self):
|
def refresh_ports(self):
|
||||||
self.combo_port.clear()
|
self.combo_port.clear()
|
||||||
ports = serial.tools.list_ports.comports()
|
ports = serial.tools.list_ports.comports()
|
||||||
if not ports:
|
if not ports:
|
||||||
self.combo_port.addItem("无可用串口")
|
self.combo_port.addItem("无可用串口")
|
||||||
return
|
return
|
||||||
for p in ports:
|
for p in ports:
|
||||||
self.combo_port.addItem(f"{p.device} - {p.description}")
|
self.combo_port.addItem(f"{p.device} - {p.description}")
|
||||||
|
|
||||||
def get_port_name(self):
|
def get_port_name(self):
|
||||||
text = self.combo_port.currentText()
|
text = self.combo_port.currentText()
|
||||||
if not text or text.startswith("无"):
|
if not text or text.startswith("无"):
|
||||||
return None
|
return None
|
||||||
return text.split(" - ")[0]
|
return text.split(" - ")[0]
|
||||||
|
|
||||||
@@ -385,12 +385,12 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
def open_serial(self):
|
def open_serial(self):
|
||||||
port_name = self.get_port_name()
|
port_name = self.get_port_name()
|
||||||
if not port_name:
|
if not port_name:
|
||||||
QMessageBox.warning(self, "错误", "请选择有效的串口!")
|
QMessageBox.warning(self, "错误", "请选择有效的串口!")
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
baud = int(self.combo_baud.currentText().strip())
|
baud = int(self.combo_baud.currentText().strip())
|
||||||
except ValueError:
|
except ValueError:
|
||||||
QMessageBox.warning(self, "错误", "波特率无效,请输入数字!")
|
QMessageBox.warning(self, "错误", "波特率无效,请输入数字!")
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
self.serial_port = serial.Serial(
|
self.serial_port = serial.Serial(
|
||||||
@@ -406,12 +406,12 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
self.serial_thread.data_received_raw.connect(self.on_upgrade_data)
|
self.serial_thread.data_received_raw.connect(self.on_upgrade_data)
|
||||||
self.serial_thread.start()
|
self.serial_thread.start()
|
||||||
|
|
||||||
self.btn_connect.setText("关闭串口")
|
self.btn_connect.setText("关闭串口")
|
||||||
self.btn_connect.setStyleSheet("background-color:#f44336; color:white; font-weight:bold;")
|
self.btn_connect.setStyleSheet("background-color:#f44336; color:white; font-weight:bold;")
|
||||||
self.log_send(f"已连接 {port_name} @ {baud}")
|
self.log_send(f"已连接 {port_name} @ {baud}")
|
||||||
self.statusBar().showMessage(f"已连接: {port_name}")
|
self.statusBar().showMessage(f"已连接: {port_name}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
QMessageBox.critical(self, "串口错误", f"无法打开串口:\n{e}")
|
QMessageBox.critical(self, "串口错误", f"无法打开串口:\n{e}")
|
||||||
|
|
||||||
def close_serial(self):
|
def close_serial(self):
|
||||||
if self.serial_thread:
|
if self.serial_thread:
|
||||||
@@ -420,12 +420,12 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
if self.serial_port and self.serial_port.is_open:
|
if self.serial_port and self.serial_port.is_open:
|
||||||
port_name = self.serial_port.port
|
port_name = self.serial_port.port
|
||||||
self.serial_port.close()
|
self.serial_port.close()
|
||||||
self.log_send(f"已断开 {port_name}")
|
self.log_send(f"已断开 {port_name}")
|
||||||
self.serial_port = None
|
self.serial_port = None
|
||||||
|
|
||||||
self.btn_connect.setText("打开串口")
|
self.btn_connect.setText("打开串口")
|
||||||
self.btn_connect.setStyleSheet("font-weight:bold;")
|
self.btn_connect.setStyleSheet("font-weight:bold;")
|
||||||
self.statusBar().showMessage("已断开")
|
self.statusBar().showMessage("已断开")
|
||||||
|
|
||||||
def on_para_clicked(self):
|
def on_para_clicked(self):
|
||||||
self._recv_buffer = ""
|
self._recv_buffer = ""
|
||||||
@@ -446,28 +446,28 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
|
|
||||||
def send_command(self, cmd):
|
def send_command(self, cmd):
|
||||||
if not self.serial_port or not self.serial_port.is_open:
|
if not self.serial_port or not self.serial_port.is_open:
|
||||||
QMessageBox.warning(self, "错误", "请先打开串口!")
|
QMessageBox.warning(self, "错误", "请先打开串口!")
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
data = (cmd + "\r\n").encode('utf-8')
|
data = (cmd + "\r\n").encode('utf-8')
|
||||||
self.serial_port.write(data)
|
self.serial_port.write(data)
|
||||||
self.log_send(f">>> {cmd}")
|
self.log_send(f">>> {cmd}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log_send(f"[发送错误] {e}")
|
self.log_send(f"[发送错误] {e}")
|
||||||
|
|
||||||
def send_raw(self, data):
|
def send_raw(self, data):
|
||||||
if not self.serial_port or not self.serial_port.is_open:
|
if not self.serial_port or not self.serial_port.is_open:
|
||||||
QMessageBox.warning(self, "错误", "请先打开串口!")
|
QMessageBox.warning(self, "错误", "请先打开串口!")
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
self.serial_port.write(data)
|
self.serial_port.write(data)
|
||||||
self.log_send(f">>> {data.hex(' ').upper()}")
|
self.log_send(f">>> {data.hex(' ').upper()}")
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log_send(f"[发送错误] {e}")
|
self.log_send(f"[发送错误] {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# ── 设备升级 ─────────────────────────────────────────
|
# ── 设备升级 ─────────────────────────────────────────
|
||||||
def on_upgrade_clicked(self):
|
def on_upgrade_clicked(self):
|
||||||
if self.upgrade_dialog is None:
|
if self.upgrade_dialog is None:
|
||||||
self.upgrade_dialog = UpgradeDialog(self)
|
self.upgrade_dialog = UpgradeDialog(self)
|
||||||
@@ -480,10 +480,10 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
def upgrade_active(self):
|
def upgrade_active(self):
|
||||||
return self.upgrade_dialog is not None and self.upgrade_dialog.isUpgrading()
|
return self.upgrade_dialog is not None and self.upgrade_dialog.isUpgrading()
|
||||||
|
|
||||||
# ── 设备管理(查询/删除) ────────────────────────────
|
# ── 设备管理(查询/删除) ────────────────────────────
|
||||||
def on_dev_mgmt_clicked(self):
|
def on_dev_mgmt_clicked(self):
|
||||||
if not self.serial_port or not self.serial_port.is_open:
|
if not self.serial_port or not self.serial_port.is_open:
|
||||||
QMessageBox.warning(self, "错误", "请先打开串口!")
|
QMessageBox.warning(self, "错误", "请先打开串口!")
|
||||||
return
|
return
|
||||||
if self.dev_dialog is None:
|
if self.dev_dialog is None:
|
||||||
self.dev_dialog = DeviceDialog(self)
|
self.dev_dialog = DeviceDialog(self)
|
||||||
@@ -517,7 +517,7 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
buf = self._recv_buffer.replace('\r', '')
|
buf = self._recv_buffer.replace('\r', '')
|
||||||
lines_all = [l.strip() for l in buf.split('\n')]
|
lines_all = [l.strip() for l in buf.split('\n')]
|
||||||
|
|
||||||
# 查找 *** 包围的块
|
# 查找 *** 包围的块
|
||||||
star_indices = [i for i, l in enumerate(lines_all) if l and all(c == '*' for c in l)]
|
star_indices = [i for i, l in enumerate(lines_all) if l and all(c == '*' for c in l)]
|
||||||
if len(star_indices) < 2:
|
if len(star_indices) < 2:
|
||||||
return
|
return
|
||||||
@@ -543,7 +543,7 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
_update(self.label_hw_ver, m.group(2))
|
_update(self.label_hw_ver, m.group(2))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Key: Value 行
|
# Key: Value 行
|
||||||
m = re.match(r'([\w\s/()]+?):\s*(.*)', line)
|
m = re.match(r'([\w\s/()]+?):\s*(.*)', line)
|
||||||
if not m:
|
if not m:
|
||||||
continue
|
continue
|
||||||
@@ -563,11 +563,11 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
elif key == 'CommUnitReadInterval':
|
elif key == 'CommUnitReadInterval':
|
||||||
_update(self.label_comm_interval, f"{val} s")
|
_update(self.label_comm_interval, f"{val} s")
|
||||||
elif key == 'MuchReg':
|
elif key == 'MuchReg':
|
||||||
status_map = {"true": "已注册", "false": "未注册", "null": "未注册"}
|
status_map = {"true": "已注册", "false": "未注册", "null": "未注册"}
|
||||||
_update(self.label_reg_status, status_map.get(val, val))
|
_update(self.label_reg_status, status_map.get(val, val))
|
||||||
# --- LTENet (Cat1) section ---
|
# --- LTENet (Cat1) section ---
|
||||||
elif key == 'DestAddr' and 'Cat1' in section:
|
elif key == 'DestAddr' and 'Cat1' in section:
|
||||||
pass # Cat1远端地址暂不单独显示
|
pass # Cat1远端地址暂不单独显示
|
||||||
elif key == 'DestPort' and 'Cat1' in section:
|
elif key == 'DestPort' and 'Cat1' in section:
|
||||||
pass
|
pass
|
||||||
elif key == 'LocalAddr':
|
elif key == 'LocalAddr':
|
||||||
@@ -586,7 +586,7 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
|
|
||||||
self._para_mode = False
|
self._para_mode = False
|
||||||
self._recv_buffer = ""
|
self._recv_buffer = ""
|
||||||
self.statusBar().showMessage("配置读取完成")
|
self.statusBar().showMessage("配置读取完成")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _escape_html(s):
|
def _escape_html(s):
|
||||||
@@ -605,16 +605,16 @@ class GatewayConfigTool(QMainWindow):
|
|||||||
|
|
||||||
|
|
||||||
class UpgradeDialog(QDialog):
|
class UpgradeDialog(QDialog):
|
||||||
"""设备升级对话框:参照通用串口升级协议V1.1。"""
|
"""设备升级对话框:参照通用串口升级协议V1.1。"""
|
||||||
|
|
||||||
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")
|
self.setWindowTitle("设备升级 - 通用串口升级协议V1.1")
|
||||||
self.setModal(False)
|
self.setModal(False)
|
||||||
self.resize(720, 520)
|
self.resize(720, 520)
|
||||||
|
|
||||||
self.running = False # 升级进行中
|
self.running = False # 升级进行中
|
||||||
self._mac = None
|
self._mac = None
|
||||||
self._fw_data = b""
|
self._fw_data = b""
|
||||||
self._pkg_num = 0
|
self._pkg_num = 0
|
||||||
@@ -628,33 +628,33 @@ class UpgradeDialog(QDialog):
|
|||||||
root.setContentsMargins(8, 8, 8, 8)
|
root.setContentsMargins(8, 8, 8, 8)
|
||||||
root.setSpacing(6)
|
root.setSpacing(6)
|
||||||
|
|
||||||
# ── 升级设置 ──
|
# ── 升级设置 ──
|
||||||
cfg = QGroupBox("升级设置")
|
cfg = QGroupBox("升级设置")
|
||||||
cfg_layout = QGridLayout(cfg)
|
cfg_layout = QGridLayout(cfg)
|
||||||
cfg_layout.setSpacing(6)
|
cfg_layout.setSpacing(6)
|
||||||
|
|
||||||
cfg_layout.addWidget(QLabel("固件文件(.bin):"), 0, 0)
|
cfg_layout.addWidget(QLabel("固件文件(.bin):"), 0, 0)
|
||||||
self.fw_edit = QLineEdit()
|
self.fw_edit = QLineEdit()
|
||||||
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.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)
|
||||||
|
|
||||||
cfg_layout.addWidget(QLabel("设备MAC:"), 1, 0)
|
cfg_layout.addWidget(QLabel("设备MAC:"), 1, 0)
|
||||||
default_mac = main_window.label_gw_mac.text()
|
default_mac = main_window.label_gw_mac.text()
|
||||||
self.mac_edit = QLineEdit(default_mac if default_mac != "-" else "")
|
self.mac_edit = QLineEdit(default_mac if default_mac != "-" else "")
|
||||||
self.mac_edit.setPlaceholderText("81-00-00-06-00-00")
|
self.mac_edit.setPlaceholderText("81-00-00-06-00-00")
|
||||||
cfg_layout.addWidget(self.mac_edit, 1, 1)
|
cfg_layout.addWidget(self.mac_edit, 1, 1)
|
||||||
mac_hint = QLabel("6字节从机MAC地址")
|
mac_hint = QLabel("6字节从机MAC地址")
|
||||||
mac_hint.setStyleSheet("color:#909090;")
|
mac_hint.setStyleSheet("color:#909090;")
|
||||||
cfg_layout.addWidget(mac_hint, 1, 2)
|
cfg_layout.addWidget(mac_hint, 1, 2)
|
||||||
|
|
||||||
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._start)
|
||||||
btn_row.addWidget(self.start_btn)
|
btn_row.addWidget(self.start_btn)
|
||||||
btn_close = QPushButton("关闭")
|
btn_close = QPushButton("关闭")
|
||||||
btn_close.clicked.connect(self.close)
|
btn_close.clicked.connect(self.close)
|
||||||
btn_row.addWidget(btn_close)
|
btn_row.addWidget(btn_close)
|
||||||
btn_row.addStretch()
|
btn_row.addStretch()
|
||||||
@@ -663,7 +663,7 @@ class UpgradeDialog(QDialog):
|
|||||||
self.progress = QProgressBar()
|
self.progress = QProgressBar()
|
||||||
self.progress.setRange(0, 100)
|
self.progress.setRange(0, 100)
|
||||||
self.progress.setValue(0)
|
self.progress.setValue(0)
|
||||||
# 固定进度条颜色,避免窗口失活(点击弹窗外)时变色
|
# 固定进度条颜色,避免窗口失活(点击弹窗外)时变色
|
||||||
self.progress.setStyleSheet("""
|
self.progress.setStyleSheet("""
|
||||||
QProgressBar {
|
QProgressBar {
|
||||||
border: 1px solid #B0B0B0;
|
border: 1px solid #B0B0B0;
|
||||||
@@ -676,19 +676,19 @@ class UpgradeDialog(QDialog):
|
|||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
cfg_layout.addWidget(self.progress, 3, 0, 1, 2)
|
cfg_layout.addWidget(self.progress, 3, 0, 1, 2)
|
||||||
self.status_lbl = QLabel("就绪")
|
self.status_lbl = QLabel("就绪")
|
||||||
cfg_layout.addWidget(self.status_lbl, 3, 2)
|
cfg_layout.addWidget(self.status_lbl, 3, 2)
|
||||||
|
|
||||||
root.addWidget(cfg)
|
root.addWidget(cfg)
|
||||||
|
|
||||||
# ── 升级日志 ──
|
# ── 升级日志 ──
|
||||||
log_group = QGroupBox("升级日志")
|
log_group = QGroupBox("升级日志")
|
||||||
log_layout = QVBoxLayout(log_group)
|
log_layout = QVBoxLayout(log_group)
|
||||||
self.log_text = QTextEdit()
|
self.log_text = QTextEdit()
|
||||||
self.log_text.setReadOnly(True)
|
self.log_text.setReadOnly(True)
|
||||||
self.log_text.setFont(QFont("Consolas", 9))
|
self.log_text.setFont(QFont("Consolas", 9))
|
||||||
log_btn_row = QHBoxLayout()
|
log_btn_row = QHBoxLayout()
|
||||||
btn_clear_log = QPushButton("清空日志")
|
btn_clear_log = QPushButton("清空日志")
|
||||||
btn_clear_log.clicked.connect(self.log_text.clear)
|
btn_clear_log.clicked.connect(self.log_text.clear)
|
||||||
log_btn_row.addStretch()
|
log_btn_row.addStretch()
|
||||||
log_btn_row.addWidget(btn_clear_log)
|
log_btn_row.addWidget(btn_clear_log)
|
||||||
@@ -700,7 +700,7 @@ class UpgradeDialog(QDialog):
|
|||||||
return self.running
|
return self.running
|
||||||
|
|
||||||
def _reset_ui(self):
|
def _reset_ui(self):
|
||||||
"""重置弹窗 UI 到初始状态,避免关闭后重新打开时界面卡住。"""
|
"""重置弹窗 UI 到初始状态,避免关闭后重新打开时界面卡住。"""
|
||||||
self.running = False
|
self.running = False
|
||||||
self._timer.stop()
|
self._timer.stop()
|
||||||
self._buf = b""
|
self._buf = b""
|
||||||
@@ -710,11 +710,11 @@ class UpgradeDialog(QDialog):
|
|||||||
self._pkg_num = 0
|
self._pkg_num = 0
|
||||||
self.start_btn.setEnabled(True)
|
self.start_btn.setEnabled(True)
|
||||||
self.progress.setValue(0)
|
self.progress.setValue(0)
|
||||||
self.status_lbl.setText("就绪")
|
self.status_lbl.setText("就绪")
|
||||||
|
|
||||||
def _choose_file(self):
|
def _choose_file(self):
|
||||||
path, _ = QFileDialog.getOpenFileName(
|
path, _ = QFileDialog.getOpenFileName(
|
||||||
self, "选择固件文件", "", "固件文件 (*.bin);;所有文件 (*)"
|
self, "选择固件文件", "", "固件文件 (*.bin);;所有文件 (*)"
|
||||||
)
|
)
|
||||||
if path:
|
if path:
|
||||||
self.fw_edit.setText(path)
|
self.fw_edit.setText(path)
|
||||||
@@ -729,7 +729,7 @@ class UpgradeDialog(QDialog):
|
|||||||
if any(0x20 <= b <= 0x7E for b in data):
|
if any(0x20 <= b <= 0x7E for b in data):
|
||||||
text = data.decode('utf-8', errors='replace').strip('\x00\r\n ')
|
text = data.decode('utf-8', errors='replace').strip('\x00\r\n ')
|
||||||
if text:
|
if text:
|
||||||
self._log(f"[设备] {text}")
|
self._log(f"[设备] {text}")
|
||||||
|
|
||||||
def _start(self):
|
def _start(self):
|
||||||
if self.running:
|
if self.running:
|
||||||
@@ -737,22 +737,22 @@ class UpgradeDialog(QDialog):
|
|||||||
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())
|
||||||
if not path or not os.path.exists(path):
|
if not path or not os.path.exists(path):
|
||||||
QMessageBox.warning(self, "提示", "请先选择固件文件")
|
QMessageBox.warning(self, "提示", "请先选择固件文件")
|
||||||
return
|
return
|
||||||
if mac is None:
|
if mac is None:
|
||||||
QMessageBox.warning(self, "提示", "设备MAC格式错误,应为 81-00-00-06-00-00")
|
QMessageBox.warning(self, "提示", "设备MAC格式错误,应为 81-00-00-06-00-00")
|
||||||
return
|
return
|
||||||
if not self.main.serial_port or not self.main.serial_port.is_open:
|
if not self.main.serial_port or not self.main.serial_port.is_open:
|
||||||
QMessageBox.warning(self, "提示", "请先打开串口")
|
QMessageBox.warning(self, "提示", "请先打开串口")
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
with open(path, 'rb') as f:
|
with open(path, 'rb') as f:
|
||||||
fw = f.read()
|
fw = f.read()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
QMessageBox.critical(self, "错误", f"读取固件失败: {e}")
|
QMessageBox.critical(self, "错误", f"读取固件失败: {e}")
|
||||||
return
|
return
|
||||||
if not fw:
|
if not fw:
|
||||||
QMessageBox.warning(self, "提示", "固件文件为空")
|
QMessageBox.warning(self, "提示", "固件文件为空")
|
||||||
return
|
return
|
||||||
|
|
||||||
self._mac = mac
|
self._mac = mac
|
||||||
@@ -763,14 +763,14 @@ class UpgradeDialog(QDialog):
|
|||||||
self.running = True
|
self.running = True
|
||||||
self.start_btn.setEnabled(False)
|
self.start_btn.setEnabled(False)
|
||||||
self.progress.setValue(0)
|
self.progress.setValue(0)
|
||||||
self.status_lbl.setText("等待握手...")
|
self.status_lbl.setText("等待握手...")
|
||||||
|
|
||||||
app_crc = zlib.crc32(fw) & 0xFFFFFFFF
|
app_crc = zlib.crc32(fw) & 0xFFFFFFFF
|
||||||
payload = struct.pack('<H', self._pkg_num) + struct.pack('<I', len(fw)) + struct.pack('<I', app_crc)
|
payload = struct.pack('<H', self._pkg_num) + struct.pack('<I', len(fw)) + struct.pack('<I', app_crc)
|
||||||
frame = build_frame(mac, CMD_ANNOUNCE, payload)
|
frame = build_frame(mac, CMD_ANNOUNCE, payload)
|
||||||
self._log(f"下发APP信息: 包数={self._pkg_num}, 大小={len(fw)}字节, CRC32=0x{app_crc:08X}")
|
self._log(f"下发APP信息: 包数={self._pkg_num}, 大小={len(fw)}字节, CRC32=0x{app_crc:08X}")
|
||||||
self.main.send_raw(frame)
|
self.main.send_raw(frame)
|
||||||
self._log("等待设备握手后接收数据请求...")
|
self._log("等待设备握手后接收数据请求...")
|
||||||
self._timer.start(5000)
|
self._timer.start(5000)
|
||||||
|
|
||||||
def feed_data(self, data):
|
def feed_data(self, data):
|
||||||
@@ -788,7 +788,7 @@ class UpgradeDialog(QDialog):
|
|||||||
while len(self._buf) >= 11:
|
while len(self._buf) >= 11:
|
||||||
idx = self._buf.find(bytes([FRAME_HEADER]))
|
idx = self._buf.find(bytes([FRAME_HEADER]))
|
||||||
if idx < 0:
|
if idx < 0:
|
||||||
# 无帧头,保留末尾字节以防跨包帧头
|
# 无帧头,保留末尾字节以防跨包帧头
|
||||||
self._buf = self._buf[-1:]
|
self._buf = self._buf[-1:]
|
||||||
return None
|
return None
|
||||||
if idx > 0:
|
if idx > 0:
|
||||||
@@ -804,23 +804,23 @@ class UpgradeDialog(QDialog):
|
|||||||
if crc16_modbus(frame[:-2]) == (frame[-2] | (frame[-1] << 8)):
|
if crc16_modbus(frame[:-2]) == (frame[-2] | (frame[-1] << 8)):
|
||||||
self._buf = self._buf[total:]
|
self._buf = self._buf[total:]
|
||||||
return (cmd, payload)
|
return (cmd, payload)
|
||||||
# 误判的帧头,丢弃继续寻找
|
# 误判的帧头,丢弃继续寻找
|
||||||
self._buf = self._buf[1:]
|
self._buf = self._buf[1:]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _handle(self, cmd, payload):
|
def _handle(self, cmd, payload):
|
||||||
self._timer.start(5000)
|
self._timer.start(5000)
|
||||||
if cmd == CMD_ACK:
|
if cmd == CMD_ACK:
|
||||||
self._log("收到从机应答(0x01)", "ok")
|
self._log("收到从机应答(0x01)", "ok")
|
||||||
self.status_lbl.setText("设备就绪")
|
self.status_lbl.setText("设备就绪")
|
||||||
return
|
return
|
||||||
if cmd == CMD_REQ_PKG:
|
if cmd == CMD_REQ_PKG:
|
||||||
if len(payload) < 4:
|
if len(payload) < 4:
|
||||||
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 >= self._pkg_num:
|
if idx >= self._pkg_num:
|
||||||
self._log(f"从机请求包序号越界({idx}),视为完成", "ok")
|
self._log(f"从机请求包序号越界({idx}),视为完成", "ok")
|
||||||
self._finish()
|
self._finish()
|
||||||
return
|
return
|
||||||
start = idx * UPG_PACKET_DATA
|
start = idx * UPG_PACKET_DATA
|
||||||
@@ -831,22 +831,22 @@ class UpgradeDialog(QDialog):
|
|||||||
self._sent.add(idx)
|
self._sent.add(idx)
|
||||||
pct = len(self._sent) * 100.0 / self._pkg_num
|
pct = len(self._sent) * 100.0 / self._pkg_num
|
||||||
self.progress.setValue(int(pct))
|
self.progress.setValue(int(pct))
|
||||||
self.status_lbl.setText(f"{len(self._sent)}/{self._pkg_num} 包")
|
self.status_lbl.setText(f"{len(self._sent)}/{self._pkg_num} 包")
|
||||||
if len(self._sent) >= self._pkg_num:
|
if len(self._sent) >= self._pkg_num:
|
||||||
self._finish()
|
self._finish()
|
||||||
return
|
return
|
||||||
self._log(f"收到其他帧 Cmd={cmd:#04x}", "err")
|
self._log(f"收到其他帧 Cmd={cmd:#04x}", "err")
|
||||||
|
|
||||||
def _finish(self):
|
def _finish(self):
|
||||||
self.running = False
|
self.running = False
|
||||||
self._timer.stop()
|
self._timer.stop()
|
||||||
self.start_btn.setEnabled(True)
|
self.start_btn.setEnabled(True)
|
||||||
if len(self._sent) >= self._pkg_num:
|
if len(self._sent) >= self._pkg_num:
|
||||||
self._log(f"升级完成 (已发送全部 {len(self._sent)} 包)", "ok")
|
self._log(f"升级完成 (已发送全部 {len(self._sent)} 包)", "ok")
|
||||||
self.progress.setValue(100)
|
self.progress.setValue(100)
|
||||||
self.status_lbl.setText("完成")
|
self.status_lbl.setText("完成")
|
||||||
else:
|
else:
|
||||||
self._log(f"升级未完成,已发送 {len(self._sent)}/{self._pkg_num} 包", "err")
|
self._log(f"升级未完成,已发送 {len(self._sent)}/{self._pkg_num} 包", "err")
|
||||||
|
|
||||||
def _on_timeout(self):
|
def _on_timeout(self):
|
||||||
if not self.running:
|
if not self.running:
|
||||||
@@ -854,8 +854,8 @@ class UpgradeDialog(QDialog):
|
|||||||
self.running = False
|
self.running = False
|
||||||
self._timer.stop()
|
self._timer.stop()
|
||||||
self.start_btn.setEnabled(True)
|
self.start_btn.setEnabled(True)
|
||||||
self._log("升级超时(5秒无响应)", "err")
|
self._log("升级超时(5秒无响应)", "err")
|
||||||
self.status_lbl.setText("超时")
|
self.status_lbl.setText("超时")
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
self._reset_ui()
|
self._reset_ui()
|
||||||
@@ -863,17 +863,17 @@ class UpgradeDialog(QDialog):
|
|||||||
|
|
||||||
|
|
||||||
class DeviceDialog(QDialog):
|
class DeviceDialog(QDialog):
|
||||||
"""设备管理对话框:通过 culs 查询已注册通讯单元,cudel 删除设备。"""
|
"""设备管理对话框:通过 culs 查询已注册通讯单元,cudel 删除设备。"""
|
||||||
|
|
||||||
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("设备管理 - 查询/删除")
|
self.setWindowTitle("设备管理 - 查询/删除")
|
||||||
self.setModal(False)
|
self.setModal(False)
|
||||||
self.resize(860, 520)
|
self.resize(860, 520)
|
||||||
|
|
||||||
self._buf = ""
|
self._buf = ""
|
||||||
self._pending_del = None # 等待删除结果的MAC,"all"表示删除全部
|
self._pending_del = None # 等待删除结果的MAC,"all"表示删除全部
|
||||||
self._quiet = QTimer(self)
|
self._quiet = QTimer(self)
|
||||||
self._quiet.setSingleShot(True)
|
self._quiet.setSingleShot(True)
|
||||||
self._quiet.timeout.connect(self._on_quiet)
|
self._quiet.timeout.connect(self._on_quiet)
|
||||||
@@ -883,25 +883,25 @@ class DeviceDialog(QDialog):
|
|||||||
root.setSpacing(6)
|
root.setSpacing(6)
|
||||||
|
|
||||||
btn_row = QHBoxLayout()
|
btn_row = QHBoxLayout()
|
||||||
self.btn_refresh = QPushButton("刷新查询")
|
self.btn_refresh = QPushButton("刷新查询")
|
||||||
self.btn_refresh.clicked.connect(self.query)
|
self.btn_refresh.clicked.connect(self.query)
|
||||||
btn_row.addWidget(self.btn_refresh)
|
btn_row.addWidget(self.btn_refresh)
|
||||||
self.btn_del_all = QPushButton("删除全部设备")
|
self.btn_del_all = QPushButton("删除全部设备")
|
||||||
self.btn_del_all.setStyleSheet("color:#D32F2F; font-weight:bold;")
|
self.btn_del_all.setStyleSheet("color:#D32F2F; font-weight:bold;")
|
||||||
self.btn_del_all.clicked.connect(self._del_all)
|
self.btn_del_all.clicked.connect(self._del_all)
|
||||||
btn_row.addWidget(self.btn_del_all)
|
btn_row.addWidget(self.btn_del_all)
|
||||||
btn_close = QPushButton("关闭")
|
btn_close = QPushButton("关闭")
|
||||||
btn_close.clicked.connect(self.close)
|
btn_close.clicked.connect(self.close)
|
||||||
btn_row.addWidget(btn_close)
|
btn_row.addWidget(btn_close)
|
||||||
btn_row.addStretch()
|
btn_row.addStretch()
|
||||||
self.status_lbl = QLabel("就绪")
|
self.status_lbl = QLabel("就绪")
|
||||||
btn_row.addWidget(self.status_lbl)
|
btn_row.addWidget(self.status_lbl)
|
||||||
root.addLayout(btn_row)
|
root.addLayout(btn_row)
|
||||||
|
|
||||||
self.table = QTableWidget(0, 11)
|
self.table = QTableWidget(0, 11)
|
||||||
self.table.setHorizontalHeaderLabels(
|
self.table.setHorizontalHeaderLabels(
|
||||||
["DevType", "设备MAC", "传感器类型", "Laser", "电量",
|
["DevType", "设备MAC", "传感器类型", "Laser", "电量",
|
||||||
"RSSI", "SNR", "FPO", "LpCfg", "在线", "操作"])
|
"RSSI", "SNR", "FPO", "LpCfg", "在线", "操作"])
|
||||||
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
||||||
self.table.horizontalHeader().setStretchLastSection(True)
|
self.table.horizontalHeader().setStretchLastSection(True)
|
||||||
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
||||||
@@ -911,18 +911,18 @@ class DeviceDialog(QDialog):
|
|||||||
|
|
||||||
def _serial_ok(self):
|
def _serial_ok(self):
|
||||||
if not self.main.serial_port or not self.main.serial_port.is_open:
|
if not self.main.serial_port or not self.main.serial_port.is_open:
|
||||||
QMessageBox.warning(self, "错误", "请先打开串口!")
|
QMessageBox.warning(self, "错误", "请先打开串口!")
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# ── 查询 ──
|
# ── 查询 ──
|
||||||
def query(self):
|
def query(self):
|
||||||
if not self._serial_ok():
|
if not self._serial_ok():
|
||||||
return
|
return
|
||||||
self._buf = ""
|
self._buf = ""
|
||||||
self._pending_del = None
|
self._pending_del = None
|
||||||
self._quiet.stop()
|
self._quiet.stop()
|
||||||
self.status_lbl.setText("查询中...")
|
self.status_lbl.setText("查询中...")
|
||||||
self.main.send_command("culs")
|
self.main.send_command("culs")
|
||||||
self._quiet.start(800)
|
self._quiet.start(800)
|
||||||
|
|
||||||
@@ -934,13 +934,13 @@ class DeviceDialog(QDialog):
|
|||||||
if self._pending_del is not None:
|
if self._pending_del is not None:
|
||||||
buf = self._buf
|
buf = self._buf
|
||||||
if "deleted" in buf.lower():
|
if "deleted" in buf.lower():
|
||||||
self.status_lbl.setText(f"删除成功 ({self._pending_del})")
|
self.status_lbl.setText(f"删除成功 ({self._pending_del})")
|
||||||
elif "not registered" in buf:
|
elif "not registered" in buf:
|
||||||
self.status_lbl.setText("删除失败:设备未注册")
|
self.status_lbl.setText("删除失败:设备未注册")
|
||||||
elif "Para Error" in buf:
|
elif "Para Error" in buf:
|
||||||
self.status_lbl.setText("删除失败:指令参数错误")
|
self.status_lbl.setText("删除失败:指令参数错误")
|
||||||
else:
|
else:
|
||||||
self.status_lbl.setText("删除失败:设备无响应")
|
self.status_lbl.setText("删除失败:设备无响应")
|
||||||
self._pending_del = None
|
self._pending_del = None
|
||||||
self.query()
|
self.query()
|
||||||
return
|
return
|
||||||
@@ -951,14 +951,14 @@ class DeviceDialog(QDialog):
|
|||||||
cur = None
|
cur = None
|
||||||
for raw in self._buf.replace('\r', '').split('\n'):
|
for raw in self._buf.replace('\r', '').split('\n'):
|
||||||
line = raw.strip()
|
line = raw.strip()
|
||||||
# Lora通讯单元:DevType: XXXX, SensorNum: N
|
# Lora通讯单元:DevType: XXXX, SensorNum: N
|
||||||
m = re.match(r'DevType:\s*([0-9A-Fa-f]{4}),\s*SensorNum:\s*(\d+)', line)
|
m = re.match(r'DevType:\s*([0-9A-Fa-f]{4}),\s*SensorNum:\s*(\d+)', line)
|
||||||
if m:
|
if m:
|
||||||
cur = {"type": m.group(1).upper(), "builtin": False,
|
cur = {"type": m.group(1).upper(), "builtin": False,
|
||||||
"macs": [], "types": [], "bat": "", "rssi": "", "snr": "", "online": ""}
|
"macs": [], "types": [], "bat": "", "rssi": "", "snr": "", "online": ""}
|
||||||
devices.append(cur)
|
devices.append(cur)
|
||||||
continue
|
continue
|
||||||
# RS485内置通讯单元:DevType: XXXX(单独一行)
|
# RS485内置通讯单元:DevType: XXXX(单独一行)
|
||||||
m = re.match(r'DevType:\s*([0-9A-Fa-f]{4})$', line)
|
m = re.match(r'DevType:\s*([0-9A-Fa-f]{4})$', line)
|
||||||
if m:
|
if m:
|
||||||
cur = {"type": m.group(1).upper(), "builtin": True,
|
cur = {"type": m.group(1).upper(), "builtin": True,
|
||||||
@@ -1008,7 +1008,7 @@ class DeviceDialog(QDialog):
|
|||||||
for dev in devices:
|
for dev in devices:
|
||||||
row = self.table.rowCount()
|
row = self.table.rowCount()
|
||||||
self.table.insertRow(row)
|
self.table.insertRow(row)
|
||||||
type_str = dev["type"] + ("(内置)" if dev["builtin"] else "")
|
type_str = dev["type"] + ("(内置)" if dev["builtin"] else "")
|
||||||
macs = "\n".join(f"{role}: {mac}" for role, mac in dev["macs"])
|
macs = "\n".join(f"{role}: {mac}" for role, mac in dev["macs"])
|
||||||
types = " ".join(dev["types"]) if dev["types"] else "-"
|
types = " ".join(dev["types"]) if dev["types"] else "-"
|
||||||
online = dev["online"] or "-"
|
online = dev["online"] or "-"
|
||||||
@@ -1026,38 +1026,38 @@ class DeviceDialog(QDialog):
|
|||||||
self.table.setItem(row, col, item)
|
self.table.setItem(row, col, item)
|
||||||
master = next((mac for role, mac in dev["macs"] if role == "Master"), None)
|
master = next((mac for role, mac in dev["macs"] if role == "Master"), None)
|
||||||
if master and not dev["builtin"]:
|
if master and not dev["builtin"]:
|
||||||
btn = QPushButton("删除")
|
btn = QPushButton("删除")
|
||||||
btn.setStyleSheet("color:#D32F2F;")
|
btn.setStyleSheet("color:#D32F2F;")
|
||||||
btn.clicked.connect(lambda _, m=master: self._del_one(m))
|
btn.clicked.connect(lambda _, m=master: self._del_one(m))
|
||||||
self.table.setCellWidget(row, 10, btn)
|
self.table.setCellWidget(row, 10, btn)
|
||||||
else:
|
else:
|
||||||
self.table.setItem(row, 10, QTableWidgetItem("-" if dev["builtin"] else ""))
|
self.table.setItem(row, 10, QTableWidgetItem("-" if dev["builtin"] else ""))
|
||||||
self.status_lbl.setText(f"共 {len(devices)} 个设备")
|
self.status_lbl.setText(f"共 {len(devices)} 个设备")
|
||||||
|
|
||||||
# ── 删除 ──
|
# ── 删除 ──
|
||||||
def _del_one(self, mac):
|
def _del_one(self, mac):
|
||||||
if not self._serial_ok():
|
if not self._serial_ok():
|
||||||
return
|
return
|
||||||
ret = QMessageBox.question(self, "确认删除", f"确定删除设备 {mac} 吗?",
|
ret = QMessageBox.question(self, "确认删除", f"确定删除设备 {mac} 吗?",
|
||||||
QMessageBox.Yes | QMessageBox.No)
|
QMessageBox.Yes | QMessageBox.No)
|
||||||
if ret != QMessageBox.Yes:
|
if ret != QMessageBox.Yes:
|
||||||
return
|
return
|
||||||
self._buf = ""
|
self._buf = ""
|
||||||
self._pending_del = mac
|
self._pending_del = mac
|
||||||
self.status_lbl.setText(f"正在删除 {mac} ...")
|
self.status_lbl.setText(f"正在删除 {mac} ...")
|
||||||
self.main.send_command(f"cudel {mac}")
|
self.main.send_command(f"cudel {mac}")
|
||||||
self._quiet.start(800)
|
self._quiet.start(800)
|
||||||
|
|
||||||
def _del_all(self):
|
def _del_all(self):
|
||||||
if not self._serial_ok():
|
if not self._serial_ok():
|
||||||
return
|
return
|
||||||
ret = QMessageBox.question(self, "确认删除", "确定删除全部已注册设备吗?",
|
ret = QMessageBox.question(self, "确认删除", "确定删除全部已注册设备吗?",
|
||||||
QMessageBox.Yes | QMessageBox.No)
|
QMessageBox.Yes | QMessageBox.No)
|
||||||
if ret != QMessageBox.Yes:
|
if ret != QMessageBox.Yes:
|
||||||
return
|
return
|
||||||
self._buf = ""
|
self._buf = ""
|
||||||
self._pending_del = "all"
|
self._pending_del = "all"
|
||||||
self.status_lbl.setText("正在删除全部设备 ...")
|
self.status_lbl.setText("正在删除全部设备 ...")
|
||||||
self.main.send_command("cudel all")
|
self.main.send_command("cudel all")
|
||||||
self._quiet.start(800)
|
self._quiet.start(800)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user