feat: 更新图片上位机,修改按钮布局和文字,增加自检回读

图片传输功能由于缺少buffer,会导致缺或多数据,造成图片显示异常;自检回读大概率会卡死导致程序退出
This commit is contained in:
2025-08-28 10:33:19 +08:00
parent b3fa034558
commit 3caea65695
3 changed files with 919 additions and 155 deletions
+217
View File
@@ -0,0 +1,217 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
#poetry.toml
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
#pdm.lock
#pdm.toml
.pdm-python
.pdm-build/
# pixi
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
#pixi.lock
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
# in the .venv directory. It is recommended not to include this directory in version control.
.pixi
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# Redis
*.rdb
*.aof
*.pid
# RabbitMQ
mnesia/
rabbitmq/
rabbitmq-data/
# ActiveMQ
activemq-data/
# SageMath parsed files
*.sage.py
# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/
# Streamlit
.streamlit/secrets.toml
+562
View File
@@ -0,0 +1,562 @@
# from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QLabel, QSlider, QLineEdit, QGridLayout, QPushButton, QCheckBox, QHBoxLayout, QInputDialog)
# from PyQt5.QtCore import pyqtSignal
# from PyQt5.QtGui import QDoubleValidator
import os
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QLabel, QSlider, QLineEdit,
QGridLayout, QPushButton, QCheckBox, QHBoxLayout, QInputDialog, QComboBox, QFileDialog)
from PyQt5.QtCore import pyqtSignal, Qt, QTimer
from PyQt5.QtGui import QDoubleValidator, QPixmap
try:
from i18n import _
import utils_ui
import utils
from widgets import EditRemarDialog
except Exception:
from COMTool.i18n import _
from COMTool import utils_ui
from COMTool import utils
from COMTool.widgets import EditRemarDialog
# from .graph_widget_metasenselite import Graph_MetaSenseLite
from .graph_widgets_base import Graph_Widget_Base
import pyqtgraph as pg
from struct import unpack, pack
import time
class Graph_Image(Graph_Widget_Base):
id = "image"
updateSignal = pyqtSignal(bytes)
startTimeoutSignal = pyqtSignal()
stopTimeoutSignal = pyqtSignal()
def __init__(self, parent=None, hintSignal=lambda type, title, msg: None,
rmCallback=lambda widget: None, send=lambda x: None, config=None):
# 初始化配置,默认缩放倍数为10
default = {"scale_factor": 10}
if config:
default.update(config)
super().__init__(parent, hintSignal, rmCallback, send, default, default)
self.image_data = bytearray()
# 从配置获取缩放倍数
self.scale_factor = self.config.get("scale_factor", 10)
layout = QVBoxLayout()
self.setLayout(layout)
self.imageLabel = QLabel("[ Waiting for image... ]")
self.imageLabel.setAlignment(Qt.AlignCenter)
layout.addWidget(self.imageLabel)
self.clearBtn = QPushButton("Clear Image")
self.clearBtn.clicked.connect(self.clear)
layout.addWidget(self.clearBtn)
self.updateSignal.connect(self.update_image)
self.timeoutTimer = QTimer(self)
self.timeoutTimer.setInterval(2000) # 2 seconds timeout
self.timeoutTimer.timeout.connect(self.clear_buffer)
self.startTimeoutSignal.connect(lambda: self.timeoutTimer.start())
self.stopTimeoutSignal.emit()
def clear(self):
self.image_data.clear()
self.imageLabel.clear()
self.imageLabel.setText("[ Cleared ]")
self.stopTimeoutSignal.emit()
def clear_buffer(self):
self.image_data.clear()
self.imageLabel.setText("[ Timeout: buffer cleared ]")
self.stopTimeoutSignal.emit()
def onData(self, data: bytes):
self.stopTimeoutSignal.emit() # Stop any running timer when data is received
self.image_data.extend(data)
# Check for common image signatures
if self._is_complete_jpeg(self.image_data) or \
self._is_complete_png(self.image_data) or \
self._is_complete_bmp(self.image_data):
self.updateSignal.emit(bytes(self.image_data))
self.image_data.clear()
else:
self.startTimeoutSignal.emit() # Restart timer only if not complete
def _is_complete_jpeg(self, data):
return data[:2] == b'\xFF\xD8' and data[-2:] == b'\xFF\xD9'
def _is_complete_png(self, data):
return data[:8] == b'\x89PNG\r\n\x1a\n' and b'IEND' in data[-20:]
def _is_complete_bmp(self, data):
return data[:2] == b'BM' and len(data) >= int.from_bytes(data[2:6], 'little')
def update_image(self, img_bytes: bytes):
pixmap = QPixmap()
if pixmap.loadFromData(img_bytes):
# 使用配置的缩放倍数进行缩放
scaled_width = pixmap.width() * self.scale_factor
scaled_height = pixmap.height() * self.scale_factor
scaled = pixmap.scaled(
scaled_width,
scaled_height,
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
self.imageLabel.setPixmap(scaled)
else:
self.imageLabel.setText("[ Failed to decode image ]")
class Graph_Plot(Graph_Widget_Base):
updateSignal = pyqtSignal(dict)
id = "plot"
def __init__(self, parent=None, hintSignal=lambda type, title, msg: None, rmCallback=lambda widget: None, send=lambda x: None, config=None):
default = {
"xRange": 10,
"xRangeEnable": True,
"header": "\\xAA\\xCC\\xEE\\xBB",
"binary_protocol": False,
}
super().__init__(parent, hintSignal=hintSignal, rmCallback=rmCallback,
send=send, config=config, defaultConfig=default)
self.headerBytes = utils.str_to_bytes(
self.config["header"], escape=True, encoding="utf-8")
self.layout = QGridLayout()
self.setLayout(self.layout)
self.plotWin = pg.GraphicsLayoutWidget()
self.plotWin.setMinimumHeight(200)
self.default_x = 0;
pg.setConfigOptions(antialias=True)
rmBtn = QPushButton(_("Remove"))
clearBtn = QPushButton(_("Clear"))
rangeLabel = QLabel(_("Range:"))
rangeConf = QLineEdit(str(self.config["xRange"]))
rangeEnable = QCheckBox(_("Enable"))
rangeEnable.setChecked(self.config["xRangeEnable"])
self.binary_protocol = QCheckBox(_("Binary Protocol"))
headerLabel = QLabel(_("Header:"))
headerConf = QLineEdit(self.config["header"])
self.headerBtn = QPushButton(_("Set"))
hint = _("Protocol: header + 1Byte name length + name + 8Bytes x(double) + 8Bytes y(double) + 1Byte sum\n"
"Protocol example code see help")
headerConf.setToolTip(hint)
self.binary_widgets = [headerLabel, headerConf, self.headerBtn]
self.binary_protocol.setToolTip(_('Check this if you want to use binary protocol, or use ASCII protocol(e.g. "$data1,10.0,200.0\\n"), more see help button on the top of window'))
self.show_binary_protocol_widgets(self.config["binary_protocol"])
headerLabel.setToolTip(hint)
self.headerBtn.setToolTip(hint)
validator = QDoubleValidator()
rangeConf.setValidator(validator)
self.layout.addWidget(self.plotWin, 0, 0, 1, 3)
self.layout.addWidget(rmBtn, 1, 0, 1, 1)
self.layout.addWidget(clearBtn, 1, 2, 1, 1)
self.layout.addWidget(rangeLabel, 2, 0, 1, 1)
self.layout.addWidget(rangeConf, 2, 1, 1, 1)
self.layout.addWidget(rangeEnable, 2, 2, 1, 1)
self.layout.addWidget(self.binary_protocol, 3, 0, 1, 1)
self.layout.addWidget(headerLabel, 4, 0, 1, 1)
self.layout.addWidget(headerConf, 4, 1, 1, 1)
self.layout.addWidget(self.headerBtn, 4, 2, 1, 1)
self.resize(600, 400)
self.p = self.plotWin.addPlot(colspan=2)
# self.p.setLabel('bottom', 'x', '')
self.p.addLegend()
self.p.setXRange(0, self.config["xRange"])
self.updateSignal.connect(self.update)
self.curves = {}
self.rawData = b''
self.data = {}
self.builtinColors = [
"#BD4B4B",
"#3BB273",
"#FFFFFA",
"#307473",
"#3C6997",
"#746D75",
"#228CDB",
"#824C71",
"#7768AE",
"#DC6BAD",
"#607d8b",
"#F18701",
"#912F40",
"#414288",
"#ED4D6E",
"#FFD29D",
"#B56576",
"#503B31",
"#93E1D8",
"#596157",
]
self.notUsedColors = self.builtinColors.copy()
self.colors = {
}
rangeConf.textChanged.connect(self.setRange)
rangeEnable.clicked.connect(
lambda: self.setEnableRange(rangeEnable.isChecked()))
self.headerBtn.clicked.connect(
lambda: self.setHeader(headerConf.text()))
rmBtn.clicked.connect(self.remove)
headerConf.textChanged.connect(self.headerChanged)
clearBtn.clicked.connect(self.clear)
self.binary_protocol.clicked.connect(self.en_binary_protocol)
def remove(self):
self.rmCallback(self)
def en_binary_protocol(self):
self.rawData = b''
self.config["binary_protocol"] = self.binary_protocol.isChecked()
self.show_binary_protocol_widgets(self.config["binary_protocol"])
def show_binary_protocol_widgets(self, show=True):
for w in self.binary_widgets:
w.setVisible(show)
def clear(self):
self.data = {}
self.curves = {}
self.notUsedColors = self.builtinColors.copy()
self.colors = {}
self.p.clear()
def setRange(self, text):
if text:
self.config["xRange"] = float(text)
def setEnableRange(self, en):
self.config["xRangeEnable"] = en
def headerChanged(self, text):
if self.config["header"] != text:
self.headerBtn.setText(_("Set") + " *")
else:
self.headerBtn.setText(_("Set"))
def setHeader(self, text):
if text:
try:
textBytes = utils.str_to_bytes(
text, escape=True, encoding="utf-8")
self.config["header"] = text
self.headerBytes = textBytes
self.headerBtn.setText(_("Set"))
except Exception:
self.hintSignal.emit("error", _("Error"), _("Format error"))
def decodeData(self, data: bytes):
'''
@data bytes, protocol:
| header(4B) | line name len(1B) | line name | x(8B) | y(8B) | sum(1B) |
| AA CC EE BB | 1~255 | roll | double | double | uint8 |
@return haveFrame, dict {
"name": {
"x": [],
"y": []
}
}
'''
# append data
self.rawData += data
# find header
header = self.headerBytes
idx = self.rawData.find(header)
if idx < 0:
return False, self.data
self.rawData = self.rawData[idx:]
# check data length
nameLen = unpack("B", self.rawData[len(header): len(header) + 1])[0]
frameLen = len(header) + nameLen + 18 # 5 + nameLen + 16 + 1
if len(self.rawData) < frameLen:
return False, self.data
# get data
frame = self.rawData[:frameLen]
self.rawData = self.rawData[frameLen:]
_sum = frame[-1]
# check sum
if _sum != sum(frame[:frameLen - 1]) % 256:
return True, self.data
name = frame[len(header) + 1: len(header) +
1 + nameLen].decode("utf-8")
x = unpack("d", frame[-17:-9])[0]
y = unpack("d", frame[-9:-1])[0]
if not name in self.data:
self.data[name] = {
"x": [x],
"y": [y]
}
else:
self.data[name]["x"].append(x)
self.data[name]["y"].append(y)
return True, self.data
def decodeDataAscii(self, data: bytes):
'''
@data bytes, protocol:
$[line name],[x],[y]<,checksum>\n
or $[line name],[y]
"$" means start of frame, "," means separator,
checksum is optional, checksum is sum of all bytes in frame except ",checksum".
e.g.
"$roll,2.0\n"
"$roll,1.0,2.0\n"
"$pitch,1.0,2.0\r\n"
"$pitch,1.0,2.0,179\n" , the 179 = sum(ord(c) for c in "$pitch,1.0,2.0") % 256
@return haveFrame, dict {
"name": {
"x": [],
"y": []
}
}
'''
# append data
self.rawData += data
# find header
header = b"$"
end = b"\n"
idx = self.rawData.find(header)
if idx < 0:
return False, self.data
self.rawData = self.rawData[idx:]
# find \n
idx = self.rawData.find(end)
if idx < 0:
return False, self.data
# get data
frame = self.rawData[1:idx] # pitch,1.0,2.0 pitch,1.0,2.0,179
self.rawData = self.rawData[idx + 1:]
items = frame.split(b",")
if len(items) != 2 and len(items) != 3 and len(items) != 4:
print("-- format error, frame item len(%s, should 3 or 4) error: %s" % (len(items), frame))
return False, self.data
try:
if len(items) == 2:
x = float(self.default_x)
y = float(items[1])
self.default_x += 1
else:
x = float(items[1])
y = float(items[2])
except Exception:
print("-- x or y format error, frame: %s" % frame)
return False, self.data
# checksum
if len(items) == 4:
try:
_sum = int(items[3])
except Exception:
print("-- checksum format error({} not int), frame: {}".format(items[3], frame))
return False, self.data
# get last , index
idx = frame.rfind(b",")
if sum(header + frame[:idx]) % 256 != _sum:
print("-- checksum error")
return False, self.data
name = items[0].decode("utf-8")
if not name in self.data:
self.data[name] = {
"x": [x],
"y": [y]
}
else:
self.data[name]["x"].append(x)
self.data[name]["y"].append(y)
return True, self.data
def pickColor(self, name: str):
if name in self.colors:
return self.colors[name]
else:
if not self.notUsedColors:
self.notUsedColors = self.builtinColors.copy()
color = self.notUsedColors.pop(0)
self.colors[name] = color
return color
def update(self, data: dict):
for k, v in data.items():
if not k in self.curves:
print("add curve:", k)
color = self.pickColor(k)
self.colors[k] = color
self.curves[k] = self.p.plot(pen=pg.mkPen(color=color, width=2),
name=k,)
# symbolBrush=color, symbolPen='w', symbol='o', symbolSize=1)
if self.config["xRangeEnable"] and self.config["xRange"] > 0:
self.p.setXRange(
v["x"][-1] - self.config["xRange"], v["x"][-1])
self.curves[k].setData(x=v["x"], y=v["y"])
def onData(self, data: bytes):
while 1:
haveFrame, allData = self.decodeData(data) if self.config["binary_protocol"] else self.decodeDataAscii(data)
if not haveFrame:
break
data = b''
self.updateSignal.emit(allData)
class Graph_Button(Graph_Widget_Base):
class Button(QWidget):
def __init__(self, rmCallback, addCallback, clickCallback, changeCallback,
value=("", "hello", "hello", "fa.circle", [])):
super().__init__()
if not value[0]:
self.id = str(int(time.time() * 1000))
else:
self.id = value[0]
self.rmCallback = rmCallback
self.changeCallback = changeCallback
self.setProperty("class", "graphBtn")
layout = QHBoxLayout()
layoutSetting = QVBoxLayout()
self.setLayout(layout)
self.button = QPushButton(value[2])
self.button.setProperty("class", "bigBtn")
self.value = value[2]
self.icon = value[3]
self.shortcut = value[4]
layout.addLayout(layoutSetting)
layout.addWidget(self.button)
self.addBtn = QPushButton()
self.rmBtn = QPushButton()
self.editBtn = QPushButton()
self.addBtn.setProperty("class", "smallBtn3")
self.rmBtn.setProperty("class", "smallBtn3")
self.editBtn.setProperty("class", "smallBtn3")
utils_ui.setButtonIcon(self.addBtn, "fa.plus")
utils_ui.setButtonIcon(self.rmBtn, "fa.minus")
utils_ui.setButtonIcon(self.editBtn, "fa.edit")
if self.icon:
utils_ui.setButtonIcon(self.button, self.icon)
if self.shortcut:
tip = self.getShortcutTip()
self.button.setToolTip(tip)
layoutSetting.addStretch()
layoutSetting.addWidget(self.rmBtn)
layoutSetting.addWidget(self.addBtn)
layoutSetting.addWidget(self.editBtn)
self.rmBtn.clicked.connect(self.onRm)
self.addBtn.clicked.connect(lambda: addCallback(self))
self.editBtn.clicked.connect(self.editButton)
self.button.clicked.connect(lambda: clickCallback(
self, utils.str_to_bytes(self.value, escape=True, encoding="utf-8")))
def getInfo(self):
return [self.id, self.button.text(), self.value, self.icon, self.shortcut]
def onRm(self):
utils_ui.clearButtonIcon(self.addBtn)
utils_ui.clearButtonIcon(self.rmBtn)
utils_ui.clearButtonIcon(self.editBtn)
utils_ui.clearButtonIcon(self.button)
self.rmCallback(self)
def editButton(self):
ok, remark, value, icon, shortcut = EditRemarDialog(
self.button.text(), self.icon, shortcut=self.shortcut, value=self.value).exec()
if ok:
self.value = value
self.icon = icon
self.shortcut = shortcut
self.button.setText(remark)
if self.shortcut:
tip = self.getShortcutTip()
self.button.setToolTip(tip)
utils_ui.setButtonIcon(self.button, icon)
self.changeCallback(self)
def getShortcutTip(self):
return _("Shortcut") + ": " + "+".join((name for v, name in self.shortcut))
id = "button"
def __init__(self, parent=None, hintSignal=lambda type, title, msg: None, rmCallback=lambda widget: None, send=lambda x: None, config=None):
default = {
"items": []
}
super().__init__(parent, hintSignal, rmCallback, send, config, default)
self.layout = QHBoxLayout()
self.setLayout(self.layout)
if not self.config["items"]:
button = Graph_Button.Button(
self.onRm, self.onAdd, self.onClick, self.onChange)
self.layout.addWidget(button)
self.buttons = [button]
self.config["items"] = [button.getInfo()]
else:
self.buttons = []
for item in self.config["items"]:
button = Graph_Button.Button(
self.onRm, self.onAdd, self.onClick, self.onChange, value=item)
self.layout.addWidget(button)
self.buttons.append(button)
self.pressedKeys = []
def onRm(self, widget):
'''
@widget Graph_Button.Button
'''
item = widget.getInfo()
for item_ in self.config["items"]:
if item_[0] == item[0]: # id
self.config["items"].remove(item_)
break
self.buttons.remove(widget)
widget.setParent(None)
if not self.buttons:
self.rmCallback(self)
def onAdd(self, widget):
'''
@widget Graph_Button.Button
'''
button = Graph_Button.Button(
self.onRm, self.onAdd, self.onClick, self.onChange)
self.layout.addWidget(button)
self.buttons.append(button)
self.config["items"].append(button.getInfo())
def onClick(self, widget, data: bytes):
self.send(data)
def onChange(self, widget):
self.config["items"][self.buttons.index(widget)] = widget.getInfo()
def onKeyPressEvent(self, event):
key = event.key()
self.pressedKeys.append(key)
for uid, name, value, icon, shortcut in self.config["items"]:
if not shortcut:
continue
if len(shortcut) == len(self.pressedKeys):
same = True
for i in range(len(shortcut)):
if shortcut[i][0] != self.pressedKeys[i]:
same = False
break
if same:
value = utils.str_to_bytes(
value, escape=True, encoding="utf-8")
self.send(value)
def onKeyReleaseEvent(self, event):
key = event.key()
if key in self.pressedKeys:
self.pressedKeys.remove(key)
graphWidgets = {
Graph_Plot.id: Graph_Plot,
Graph_Button.id: Graph_Button,
Graph_Image.id: Graph_Image
# Graph_DragTof.id: Graph_DragTof,
# Graph_MetaSenseLite.id: Graph_MetaSenseLite,
}
+140 -155
View File
@@ -30,8 +30,8 @@ except ImportError:
class Plugin(Plugin_Base): class Plugin(Plugin_Base):
id = "hsjl_time" id = "hsjl_timer"
name = _("hsjl_time") name = _("hsjl_timer")
showReceiveDataSignal = pyqtSignal(str) showReceiveDataSignal = pyqtSignal(str)
# 图片1的字节数据(已存在,提取为常量) # 图片1的字节数据(已存在,提取为常量)
IMAGE1_DATA = [ IMAGE1_DATA = [
@@ -72,9 +72,11 @@ class Plugin(Plugin_Base):
"version": 1, "version": 1,
"customSendItems": [ "customSendItems": [
{ {
"caption": "遥测", "caption": "自检项",
"icon": "fa5.play-circle", "icon": "fa5.play-circle",
"data": "5A 54 E6 EE 00 D4 02 5A FE" "data": "5A 54 E1 EE 00 CF 02 5A FE"
#"data": "5A 54 E1 EE 01 00 00 00 01 15 00 55 00 05 55 57 58 80 00 00 00 00 00 00 00\n\
#00 00 00 01 00 00 00 00 75 17 5A FE "
}, },
{ {
"caption": "图片传输", "caption": "图片传输",
@@ -88,7 +90,7 @@ class Plugin(Plugin_Base):
4E 62 24 11 DE 82 35 D8 00 00 00 00 49 45 4E 44 AE 42 60 82 00 76 C4 5A FE" 4E 62 24 11 DE 82 35 D8 00 00 00 00 49 45 4E 44 AE 42 60 82 00 76 C4 5A FE"
}, },
{ {
"caption": "总控", "caption": "配置发送",
"icon": "fa5.play-circle", "icon": "fa5.play-circle",
"data": "5A 54 E3 EE AA 03 22 00 00 A0 06 5A FE" "data": "5A 54 E3 EE AA 03 22 00 00 A0 06 5A FE"
}, },
@@ -98,6 +100,8 @@ class Plugin(Plugin_Base):
for k in default: for k in default:
if k not in self.config: if k not in self.config:
self.config[k] = default[k] self.config[k] = default[k]
self.config["customSendItems"] = default["customSendItems"]
print("初始化的customSendItems:", self.config["customSendItems"])
def createToggleButton(self): def createToggleButton(self):
toggleBtn = QPushButton("开关: Off") toggleBtn = QPushButton("开关: Off")
@@ -119,31 +123,58 @@ class Plugin(Plugin_Base):
return toggleBtn return toggleBtn
def createIdButton(self): def createIdButton(self):
toggleBtn = QPushButton("id: 01") # 定义ID映射:原始ID -> 显示文本(带标识)
id_display_map = {
"01": "01(主)",
"02": "02(从)",
"03": "03(从)"
}
default_id = "01" # 原始ID默认值
# 按钮显示带标识的文本
toggleBtn = QPushButton(f"id: {id_display_map[default_id]}")
# 存储原始ID(关键:组帧时用这个值)
toggleBtn.setProperty("originalId", default_id)
menu = QMenu(toggleBtn) menu = QMenu(toggleBtn)
ids = ["01", "02", "03"]
def setId(text): def setId(original_id):
toggleBtn.setText(f"id: {text}") # 更新显示文本(带标识)
print(f"id设为 {text}") toggleBtn.setText(f"id: {id_display_map[original_id]}")
# 更新存储的原始ID
toggleBtn.setProperty("originalId", original_id)
print(f"id设为 {original_id}") # 确认原始ID正确
for id_text in ids: # 菜单显示带标识的文本,但传递原始ID
menu.addAction(id_text).triggered.connect(lambda checked, t=id_text: setId(t)) for original_id, display_text in id_display_map.items():
menu.addAction(display_text).triggered.connect(
lambda checked, id=original_id: setId(id)
)
toggleBtn.setMenu(menu) toggleBtn.setMenu(menu)
return toggleBtn return toggleBtn
def createtoIdButton(self): def createtoIdButton(self):
toggleBtn = QPushButton("目的节点id: 01") # 目的节点ID同理处理
id_display_map = {
"01": "01(主)",
"02": "02(从)",
"03": "03(从)"
}
default_id = "01"
toggleBtn = QPushButton(f"目的节点id: {id_display_map[default_id]}")
toggleBtn.setProperty("originalId", default_id) # 存储原始ID
menu = QMenu(toggleBtn) menu = QMenu(toggleBtn)
ids = ["01", "02", "03"]
def setId(text): def setId(original_id):
toggleBtn.setText(f"目的节点id: {text}") toggleBtn.setText(f"目的节点id: {id_display_map[original_id]}")
print(f"目的节点id设为 {text}") toggleBtn.setProperty("originalId", original_id)
print(f"目的节点id设为 {original_id}")
for id_text in ids: for original_id, display_text in id_display_map.items():
menu.addAction(id_text).triggered.connect(lambda checked, t=id_text: setId(t)) menu.addAction(display_text).triggered.connect(
lambda checked, id=original_id: setId(id)
)
toggleBtn.setMenu(menu) toggleBtn.setMenu(menu)
return toggleBtn return toggleBtn
@@ -197,9 +228,11 @@ class Plugin(Plugin_Base):
# ID字段,转为整数 # ID字段,转为整数
try: try:
id_val = int(id_text.split(":")[1].strip(), 16) # 直接获取存储的原始ID(如"01")
original_id = self.idButton.property("originalId")
id_val = int(original_id, 16) # 转为16进制整数
except: except:
id_val = 0x00 id_val = 0x00 # 异常时默认值
frame.append(id_val) frame.append(id_val)
# 速率字段对应映射(参考原来速率的0x22等) # 速率字段对应映射(参考原来速率的0x22等)
@@ -240,9 +273,10 @@ class Plugin(Plugin_Base):
# ID字段,转为整数 # ID字段,转为整数
try: try:
id_val = int(toid_text.split(":")[1].strip(), 16) original_toid = self.toidButton.property("originalId")
id_val = int(original_toid, 16)
except: except:
id_val = 0x01 id_val = 0x01 # 异常时默认值
frame.append(id_val) frame.append(id_val)
# 根据索引选择图片数据 # 根据索引选择图片数据
@@ -381,19 +415,19 @@ class Plugin(Plugin_Base):
self.mainWidget.addWidget(sendItemsOuterWrapper) self.mainWidget.addWidget(sendItemsOuterWrapper)
# 网络状态标签初始化 # 网络状态标签初始化
self.networkStatusLabel = QLabel("网络连接状态: 未连接") self.networkStatusLabel = QLabel("节点状态: 未知")
self.networkStatusLabel.setAlignment(Qt.AlignLeft) self.networkStatusLabel.setAlignment(Qt.AlignLeft)
self.networkStatusLabel.setFont(QFont("Arial", 10)) self.networkStatusLabel.setFont(QFont("Arial", 10))
self.networkPhaseLabel = QLabel("网络运行阶段: 未知") self.networkPhaseLabel = QLabel("天线温度: 未知")
self.networkPhaseLabel.setAlignment(Qt.AlignLeft) self.networkPhaseLabel.setAlignment(Qt.AlignLeft)
self.networkPhaseLabel.setFont(QFont("Arial", 10)) self.networkPhaseLabel.setFont(QFont("Arial", 10))
self.networktemLabel = QLabel("fpga温度: 未知") self.networktemLabel = QLabel("电压状态: 未知")
self.networktemLabel.setAlignment(Qt.AlignLeft) self.networktemLabel.setAlignment(Qt.AlignLeft)
self.networktemLabel.setFont(QFont("Arial", 10)) self.networktemLabel.setFont(QFont("Arial", 10))
self.networkvoltageLabel = QLabel("电压监测: 未知") self.networkvoltageLabel = QLabel("fgpa温度: 未知")
self.networkvoltageLabel.setAlignment(Qt.AlignLeft) self.networkvoltageLabel.setAlignment(Qt.AlignLeft)
self.networkvoltageLabel.setFont(QFont("Arial", 10)) self.networkvoltageLabel.setFont(QFont("Arial", 10))
@@ -434,34 +468,32 @@ class Plugin(Plugin_Base):
return itemWidget return itemWidget
def onUiInitDone(self): def onUiInitDone(self):
# 去掉自检项
self.config["customSendItems"] = [item for item in self.config["customSendItems"] if item["caption"] != "自检"]
# 清空旧布局 # 清空旧布局
self.clearLayout(self.customSendItemsLayout) self.clearLayout(self.customSendItemsLayout)
# 创建“开关”、“ID”、“时隙表”、“速率”按钮,供“总控”使用 # 创建“开关”、“ID”、“时隙表”、“速率”按钮,供“总控”使用
self.toggleButton = self.createToggleButton() self.toggleButton = self.createToggleButton()
self.customSendItemsLayout.addWidget(self.toggleButton, 1, 0) self.customSendItemsLayout.addWidget(self.toggleButton, 1, 0, 1, 2)
self.idButton = self.createIdButton() self.idButton = self.createIdButton()
self.customSendItemsLayout.addWidget(self.idButton, 1, 1) self.customSendItemsLayout.addWidget(self.idButton, 0, 1)
self.slotButton = self.createSlotButton() self.slotButton = self.createSlotButton()
self.customSendItemsLayout.addWidget(self.slotButton, 1, 2) self.customSendItemsLayout.addWidget(self.slotButton, 0, 2)
self.rateButton = self.createRateButton() self.rateButton = self.createRateButton()
self.customSendItemsLayout.addWidget(self.rateButton, 1, 3) self.customSendItemsLayout.addWidget(self.rateButton, 0, 0)
self.toidButton = self.createtoIdButton() self.toidButton = self.createtoIdButton()
self.customSendItemsLayout.addWidget(self.toidButton, 0, 3) self.customSendItemsLayout.addWidget(self.toidButton, 0, 3)
# 第一行固定插入“遥测”、“图片传输”、“总控” # 第一行固定插入“自检”、“图片传输”、“总控”
for item in self.config["customSendItems"]: for item in self.config["customSendItems"]:
if item["caption"] == "遥测": if item["caption"] == "自检项":
print("找到自检项,准备添加到(0,0)") # 新增
w = self.createSendItemWidget(item) w = self.createSendItemWidget(item)
self.customSendItemsLayout.addWidget(w, 0, 0) self.customSendItemsLayout.addWidget(w, 3, 0, 1, 2)
elif item["caption"] == "图片传输": elif item["caption"] == "图片传输":
totalControlWidget = QWidget() totalControlWidget = QWidget()
layout = QHBoxLayout() layout = QHBoxLayout()
@@ -545,16 +577,16 @@ class Plugin(Plugin_Base):
layout.addWidget(label) layout.addWidget(label)
layout.addWidget(sendBtn) layout.addWidget(sendBtn)
self.customSendItemsLayout.addWidget(totalControlWidget, 0, 1) self.customSendItemsLayout.addWidget(totalControlWidget, 3, 2, 1, 2 )
elif item["caption"] == "总控": elif item["caption"] == "配置发送":
totalControlWidget = QWidget() totalControlWidget = QWidget()
layout = QHBoxLayout() layout = QHBoxLayout()
layout.setContentsMargins(0, 0, 0, 0) layout.setContentsMargins(0, 0, 0, 0)
totalControlWidget.setLayout(layout) totalControlWidget.setLayout(layout)
sendBtn = QPushButton("") sendBtn = QPushButton("")
label = QLabel("总控") label = QLabel("配置发送")
utils_ui.setButtonIcon(sendBtn, item.get("icon", "fa.send")) utils_ui.setButtonIcon(sendBtn, item.get("icon", "fa.send"))
sendBtn.setProperty("class", "smallBtn") sendBtn.setProperty("class", "smallBtn")
@@ -568,7 +600,7 @@ class Plugin(Plugin_Base):
# 动态构造指令字符串S # 动态构造指令字符串S
cmd_str = self.buildBindCommand(switch_text, id_text, rate_text, slot_text) cmd_str = self.buildBindCommand(switch_text, id_text, rate_text, slot_text)
print(f"总控: {cmd_str}") print(f"配置发送: {cmd_str}")
data_bytes = self.parseSendData(cmd_str, data_bytes = self.parseSendData(cmd_str,
encoding=self.configGlobal["encoding"], encoding=self.configGlobal["encoding"],
@@ -579,16 +611,13 @@ class Plugin(Plugin_Base):
layout.addWidget(label) layout.addWidget(label)
layout.addWidget(sendBtn) layout.addWidget(sendBtn)
self.customSendItemsLayout.addWidget(totalControlWidget, 0, 2) self.customSendItemsLayout.addWidget(totalControlWidget, 1, 2, 1, 2)
# 第三行放4个状态标签 # 第三行放4个状态标签
self.customSendItemsLayout.addWidget(self.networkStatusLabel, 2, 0) self.customSendItemsLayout.addWidget(self.networkStatusLabel, 2, 0)
self.customSendItemsLayout.addWidget(self.networkPhaseLabel, 2, 1) self.customSendItemsLayout.addWidget(self.networkPhaseLabel, 2, 1)
self.customSendItemsLayout.addWidget(self.networkvoltageLabel, 2, 2) self.customSendItemsLayout.addWidget(self.networkvoltageLabel, 2, 2)
self.customSendItemsLayout.addWidget(self.networktemLabel, 2, 3) self.customSendItemsLayout.addWidget(self.networktemLabel, 2, 3)
# 初始化结构体
self.structStatusCheckRsp = Struct(
"<BB Q IIII IBBBBIB III III III III III i II 3s IIII d Q dBB dB H B 2s BBBbbb HHHBBbB B 3s")
def insertSendItem(self, item = None, load = False): def insertSendItem(self, item = None, load = False):
caption = item["caption"] caption = item["caption"]
@@ -661,8 +690,8 @@ class Plugin(Plugin_Base):
result = self.evalimageTestRsp(binary_data) result = self.evalimageTestRsp(binary_data)
self.showReceiveDataSignal.emit(result) self.showReceiveDataSignal.emit(result)
return return
elif cmd_id == 0xE6: # 遥测回令 elif cmd_id == 0xE1: # 自检回令
result = self.evalStatusCheckRsp(binary_data) result = self.evalSelfTestRsp(binary_data)
self.showReceiveDataSignal.emit(result) self.showReceiveDataSignal.emit(result)
return return
else: else:
@@ -700,130 +729,80 @@ class Plugin(Plugin_Base):
ret += '\n' ret += '\n'
return ret return ret
def evalStatusCheckRsp(self, data: bytes): def evalSelfTestRsp(self, data: bytes):
print('“”“”“”“”“”“”“”“”“”“”“”“')
try: try:
payload = self.structStatusCheckRsp.unpack(data) # 1. 先解析头部获取数据长度和图片类型标识
except : header_struct = Struct("<2s B B") # 前11字节结构
return f"\n### evalStatusCheckRsp:: Failed to unpack data (data length {len(data)} byte).\n" header_data = header_struct.unpack(data[:4])
frame_head, cmd_id, node_type= header_data
print(data)
print(header_data)
img_flag = data[7:8] # 第八个字节
print('m')
# 3. 动态构建完整结构体
struct_format = f"<2s B B BBBBbbb 2B 2B 2B BBbBB 3s B B B 2s"
full_struct = Struct(struct_format)
print('km')
# 计算结构体要求的总长度
required_len = 30 # 头部11 + 数据段data_len + 尾部5
print(f"预期长度: {required_len}, 实际数据长度: {len(data)}")
payload = full_struct.unpack(data)
print('lm')
except Exception as e:
print('x')
return f"\n### evalselfTestRsp:: Failed to unpack data (data length {len(data)} byte).\n错误信息: {str(e)}"
ret = "----------- 自检回令 -----------\n"
ret = "----------- 遥测回令 -----------\n" ret += f"节点编号: 0x{payload[-2]:02X}\n"
ret += "节点类型: " ret += "节点状态: "
if payload[1] == 0xEE: if payload[3] == 0xAA:
ret += f"C频段 (0x{payload[1]:02X})\n" ret += f"正常 (0x{payload[3]:02X})\n"
network_status='正常'
elif payload[6] == 0x55:
ret += f"异常 (0x{payload[3]:02X})\n"
network_status='异常'
else: else:
ret += f"【未知错误】(0x{payload[1]:02X})\n" ret += f"【未知错误】(0x{payload[3]:02X})\n"
network_status='未知错误'
ret += f"时间戳: {payload[2]/1e5:.2f} ms\n" hwOffset = 4
ret += f"FPGA 电压监测: {bin(payload[hwOffset+0])[2:].zfill(8)}\n"
sysbusOffset = 3
linkOffset = 7
phyOffset = 36
hwOffset = 47
ret += "\n -- 总线状态\n"
ret += f"配置装订: {payload[sysbusOffset + 0]:08X}\n"
ret += f"接收数据计数: {payload[sysbusOffset + 1]:>6d}\n"
ret += f"发送数据计数: {payload[sysbusOffset + 2]:>6d}\n"
ret += f"遥测请求计数: {payload[sysbusOffset + 3]:>6d}\n"
ret += "\n -- 链路层状态\n"
ret += f"时元号: {payload[linkOffset + 0]:>6d}\n"
ret += f"时隙号: {payload[linkOffset + 1]:>3d}\n"
ret += "当前角色: "
if payload[linkOffset + 2] == 0xAA:
ret += f"簇首 (0x{payload[linkOffset + 2]:02X})\n"
elif payload[linkOffset + 2] == 0xCC:
ret += f"成员 (0x{payload[linkOffset + 2]:02X})\n"
else:
ret += f"【未知错误】(0x{payload[linkOffset + 2]:02X})\n"
# 网络连接状态更新
network_status = "未连接"
if payload[linkOffset + 3] == 0x11:
network_status = "已连接"
elif payload[linkOffset + 3] == 0x00:
network_status = "未连接"
else:
network_status = f"未知状态 (0x{payload[linkOffset + 3]:02X})"
# 网络运行阶段更新
phase = "未知"
if payload[linkOffset + 4] == 0x11:
phase = "建网阶段"
elif payload[linkOffset + 4] == 0x22:
phase = "业务通信"
else:
phase = f"未知阶段 (0x{payload[linkOffset + 4]:02X})"
self.networkStatusLabel.setText(f"网络连接状态: {network_status}")
self.networkPhaseLabel.setText(f"网络运行阶段: {phase}")
ret += f"重建/入网计数: {payload[linkOffset + 5]:>6d}\n"
ret += f"通信天线位号: {payload[linkOffset + 6]:>1d}\n"
ret += f"建网帧发送计数: {payload[linkOffset + 7]:>6d}\n"
ret += f"建网帧接收计数: {payload[linkOffset + 8]:>6d}\n"
ret += f"建网帧正确计数: {payload[linkOffset + 9]:>6d}\n"
ret += f"应答帧发送计数: {payload[linkOffset + 10]:>6d}\n"
ret += f"应答帧接收计数: {payload[linkOffset + 11]:>6d}\n"
ret += f"应答帧正确计数: {payload[linkOffset + 12]:>6d}\n"
ret += f"分发帧发送计数: {payload[linkOffset + 13]:>6d}\n"
ret += f"分发帧接收计数: {payload[linkOffset + 14]:>6d}\n"
ret += f"分发帧正确计数: {payload[linkOffset + 15]:>6d}\n"
ret += f"网管帧发送计数: {payload[linkOffset + 16]:>6d}\n"
ret += f"网管帧接收计数: {payload[linkOffset + 17]:>6d}\n"
ret += f"网管帧正确计数: {payload[linkOffset + 18]:>6d}\n"
ret += f"RTT 帧发送计数: {payload[linkOffset + 19]:>6d}\n"
ret += f"RTT 帧接收计数: {payload[linkOffset + 20]:>6d}\n"
ret += f"RTT 帧正确计数: {payload[linkOffset + 21]:>6d}\n"
ret += f"业务帧发送计数: {payload[linkOffset + 22]:>6d}\n"
ret += f"业务帧接收计数: {payload[linkOffset + 23]:>6d}\n"
ret += f"业务帧正确计数: {payload[linkOffset + 24]:>6d}\n"
ret += f"时间偏差记录: {payload[linkOffset + 25]/1e2:.2f} us\n"
ret += f"发送数据 ID: {payload[linkOffset + 26]:>6d}\n"
ret += f"接收数据 ID: {payload[linkOffset + 27]:>6d}\n"
ret += "\n -- 物理层状态\n"
ret += f"捕获计数: {payload[phyOffset + 0]:>6d}\n"
ret += f"帧同步计数: {payload[phyOffset + 1]:>6d}\n"
ret += f"译码计数: {payload[phyOffset + 2]:>6d}\n"
ret += f"CRC 正确计数:{payload[phyOffset + 3]:>6d}\n"
ret += f"多普勒估计: {payload[phyOffset + 4]:.2f} Hz\n"
ret += f"正确接收时刻: {payload[phyOffset + 5]/1e2:.2f} us\n"
ret += f"捕获峰均比: {payload[phyOffset + 6]:.2f}\n"
ret += f"捕获相位值: {payload[phyOffset + 7]:>3d}\n"
ret += f"捕获频偏通道: {payload[phyOffset + 8]:>3d}\n"
ret += f"帧同步峰均比: {payload[phyOffset + 9]:.2f}\n"
ret += f"帧同步频偏通道: {payload[phyOffset + 10]:>3d}\n"
ret += f"交织输入数据幅度统计:\t0x{payload[phyOffset + 11]:04X}\n"
ret += f"天线控制模式: {bin(payload[phyOffset + 10])[2:].zfill(8)}\n"
ret += "\n -- 硬件状态\n"
voltage = f"FPGA 电压监测: {bin(payload[hwOffset+0])[2:].zfill(8)}\n"
ret += f"时钟锁定状态: {bin(payload[hwOffset+1])[2:].zfill(8)}\n" ret += f"时钟锁定状态: {bin(payload[hwOffset+1])[2:].zfill(8)}\n"
ret += f"AD 锁定状态: {bin(payload[hwOffset+2])[2:].zfill(8)}\n" ret += f"AD 锁定状态: {bin(payload[hwOffset+2])[2:].zfill(8)}\n"
ret += f"AD 温度 1: {payload[hwOffset+3]:>4d}\n" ret += f"AD 温度 1: {payload[hwOffset+3]:>4d}\n"
ret += f"AD 温度 2: {payload[hwOffset+4]:>4d}\n" ret += f"AD 温度 2: {payload[hwOffset+4]:>4d}\n"
temp = f"FPGA 温度: {payload[hwOffset+5]:>4d}\n" ret += f"FPGA 温度: {payload[hwOffset+5]:>4d}\n"
ret += f"天线-1 工作 PLL: {(payload[hwOffset+6] >> 15) & 0x01:>1d}\n" ret += f"天线-1 工作 PLL: {(payload[hwOffset+6] >> 15) & 0x01:>1d}\n"
ret += f"天线-1 工作频率: {(payload[hwOffset+6] & 0x7FFF)+3700:>4d}\n" ret += f"天线-1 工作频率: {(payload[hwOffset+6] & 0x7FFF)+3700:>4d}\n"
print(payload[3])
ret += f"天线-2 工作 PLL: {(payload[hwOffset+7] >> 15) & 0x01:>1d}\n" ret += f"天线-2 工作 PLL: {(payload[hwOffset+7] >> 15) & 0x01:>1d}\n"
ret += f"天线-2 工作频率: {(payload[hwOffset+7] & 0x7FFF)+3700:>4d}\n" ret += f"天线-2 工作频率: {(payload[hwOffset+7] & 0x7FFF)+3700:>4d}\n"
print(payload[3])
ret += f"天线-3 工作 PLL: {(payload[hwOffset+8] >> 15) & 0x01:>1d}\n" ret += f"天线-3 工作 PLL: {(payload[hwOffset+8] >> 15) & 0x01:>1d}\n"
ret += f"天线-3 工作频率: {(payload[hwOffset+8] & 0x7FFF)+3700:>4d}\n" ret += f"天线-3 工作频率: {(payload[hwOffset+8] & 0x7FFF)+3700:>4d}\n"
print(payload[3])
ret += f"天线 PLL 锁定状态: {bin(payload[hwOffset+9])[2:].zfill(8)}\n" ret += f"天线 PLL 锁定状态: {bin(payload[hwOffset+9])[2:].zfill(8)}\n"
print(payload[3])
ret += f"天线工作模式: {bin(payload[hwOffset+10])[2:].zfill(8)}\n" ret += f"天线工作模式: {bin(payload[hwOffset+10])[2:].zfill(8)}\n"
ret += f"天线温度: {payload[hwOffset+11]:>4d}\n" ret += f"天线温度: {payload[hwOffset+11]:>4d}\n"
print('+') print(payload[3])
print(voltage)
print('_')
self.networkvoltageLabel.setText(f"电压监测: {voltage}")
self.networktemLabel.setText(f"fgpa温度: {temp}")
ret += "--------------------------------\n" ret += "--------------------------------\n"
print(payload[3])
print(network_status)
phase=payload[14]
temp=payload[9]
voltage=payload[4]
self.networkStatusLabel.setText(f"节点状态: {network_status}")
self.networkPhaseLabel.setText(f"天线温度: {phase}")
self.networkvoltageLabel.setText(f"电压状态: {voltage}")
self.networktemLabel.setText(f"fgpa温度: {temp}")
return ret + '\n' + self.printFrame(data) return ret + '\n' + self.printFrame(data)
def evalimageTestRsp(self, data: bytes): def evalimageTestRsp(self, data: bytes):
print('“”“”“”“”“”“”“”“”“”“”“”“') print('“”“”“”“”“”“”“”“”“”“”“”“')
try: try:
@@ -879,26 +858,32 @@ class Plugin(Plugin_Base):
ret += f"帧尾: {frame_tail.hex().upper()}\n" ret += f"帧尾: {frame_tail.hex().upper()}\n"
print('&&&&&') print('&&&&&')
# 图像显示逻辑(保留原始控件调用方式) # 图像显示逻辑(添加缩放倍数控制)
# 截取有效图像数据 # 1. 手动设置缩放倍数(可根据需要修改,例如放大2倍)
# 图像显示逻辑(修正后)
if from_id == 0x01 or from_id == 0x02: if from_id == 0x01 or from_id == 0x02:
# 图像通道 A → 左侧图像控件 # 图像通道 A → 左侧图像控件
if len(self.widgets) >= 1 and isinstance(self.widgets[0], graphWidgets["image"]): if len(self.widgets) >= 1 and isinstance(self.widgets[0], graphWidgets["image"]):
self.widgets[0].onData(image_data) # 发送有效数据 # 仅传递图像数据,缩放倍数已在控件初始化时通过config设置
self.widgets[0].onData(image_data)
else: else:
self.showReceiveDataSignal.emit("\n### No valid image widget found (A1)\n") self.showReceiveDataSignal.emit("\n### No valid image widget found (A1)\n")
print(from_id)
elif from_id == 0x03: elif from_id == 0x03:
# 图像通道 B → 右侧图像控件 # 图像通道 B → 右侧图像控件
if len(self.widgets) >= 2 and isinstance(self.widgets[1], graphWidgets["image"]): if len(self.widgets) >= 2 and isinstance(self.widgets[1], graphWidgets["image"]):
self.widgets[1].onData(image_data) # 发送有效数据 # 仅传递图像数据
self.widgets[1].onData(image_data)
else: else:
self.showReceiveDataSignal.emit("\n### No valid image widget found (A2)\n") self.showReceiveDataSignal.emit("\n### No valid image widget found (A2)\n")
print(from_id) print(from_id)
print('+') print('+')
ret += "------------------------------------\n" ret += "------------------------------------\n"
return ret + '\n' + self.printFrame(data) return ret + '\n' + self.printFrame(data)
print(from_id)
print('+')
ret += "------------------------------------\n"
return ret + '\n' + self.printFrame(data)
def onSent(self, ok, msg, length, path): def onSent(self, ok, msg, length, path):
if ok: if ok: