392 lines
17 KiB
Python
392 lines
17 KiB
Python
'''
|
|
@brief This is an example plugin, can receive and send data
|
|
@author
|
|
@date
|
|
@license LGPL-3.0
|
|
'''
|
|
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QTextEdit, QPushButton, QLabel, QScrollArea, QSplitter
|
|
from PyQt5.QtCore import Qt, pyqtSignal
|
|
from PyQt5.QtGui import QFont, QTextCursor
|
|
from struct import Struct
|
|
try:
|
|
import utils_ui
|
|
from plugins.base import Plugin_Base
|
|
from conn import ConnectionStatus
|
|
from i18n import _
|
|
from widgets import statusBar
|
|
except ImportError:
|
|
from COMTool import utils_ui
|
|
from COMTool.plugins.base import Plugin_Base
|
|
from COMTool.i18n import _
|
|
from COMTool.conn import ConnectionStatus
|
|
from COMTool.widgets import statusBar
|
|
|
|
class Plugin(Plugin_Base):
|
|
id = "DEIFEI"
|
|
name = _("DUIFEI")
|
|
showReceiveDataSignal = pyqtSignal(str)
|
|
|
|
def onInit(self, config):
|
|
'''
|
|
init params
|
|
'''
|
|
default = {
|
|
"version": 1,
|
|
"customSendItems": [
|
|
{
|
|
"caption": "Self Test",
|
|
"icon": "fa5.question-circle",
|
|
"data": "5A 54 E1 DD 00 BE 02 5A FE"
|
|
},
|
|
{
|
|
"caption": "Config - FLJ Low Power",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 E3 DD 88 11 00 00 00 59 06 5A FE"
|
|
},{
|
|
"caption": "Config - FLJ Full Power",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 E3 DD 66 11 00 00 00 37 06 5A FE"
|
|
}, {
|
|
"caption": "Config - FLJ Silent",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 E3 DD 00 11 00 00 00 D1 06 5A FE"
|
|
},{
|
|
"caption": "Config - LJ Low Power",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 E3 DD 88 00 00 00 00 48 06 5A FE"
|
|
},{
|
|
"caption": "Config - LJ Full Power",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 E3 DD 66 00 00 00 00 26 06 5A FE"
|
|
},{
|
|
"caption": "Config - LJ Silent",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 E3 DD 00 00 00 00 00 C0 06 5A FE"
|
|
},{
|
|
"caption": "StatusCheck - FLJ",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 E6 DD 99 00 5C 03 5A FE"
|
|
},{
|
|
"caption": "StatusCheck - LJ",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 F6 DD 99 00 6C 03 5A FE"
|
|
},{
|
|
"caption": "DataRetrieve - FLJ",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 E5 DD 77 00 39 03 5A FE"
|
|
},{
|
|
"caption": "DataRetrieve - LJ",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 F5 DD 77 00 49 03 5A FE"
|
|
}
|
|
]
|
|
}
|
|
self.config = config
|
|
for k in default:
|
|
if not k in self.config:
|
|
self.config[k] = default[k]
|
|
|
|
def onConnChanged(self, status:ConnectionStatus, msg:str):
|
|
print("-- connection changed: {}, msg: {}".format(status, msg))
|
|
|
|
def onWidgetMain(self, parent):
|
|
'''
|
|
main widget, just return a QWidget object
|
|
'''
|
|
self.mainWidget = QSplitter(Qt.Vertical)
|
|
# receive widget
|
|
self.receiveWidget = QTextEdit("")
|
|
font = QFont('Consolas, DejaVu Sans Mono, Courier New, monospace, Microsoft YaHei', 10)
|
|
self.receiveWidget.setFont(font)
|
|
self.receiveWidget.setLineWrapMode(QTextEdit.NoWrap)
|
|
# clear button
|
|
self.clearBtn = QPushButton("")
|
|
utils_ui.setButtonIcon(self.clearBtn, "mdi6.broom")
|
|
# send scroll
|
|
self.customSendScroll = QScrollArea()
|
|
self.customSendScroll.setMinimumHeight(20)
|
|
self.customSendScroll.setWidgetResizable(True)
|
|
self.customSendScroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
self.customSendScroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
cutomSendItemsWraper = QWidget()
|
|
self.customSendScroll.setWidget(cutomSendItemsWraper)
|
|
|
|
# -- wrapper widget
|
|
cutomSendItemsWraper0 = QWidget()
|
|
cutomSendItemsWraper0.setProperty("class", "scrollbar2")
|
|
layout0 = QVBoxLayout()
|
|
layout0.setContentsMargins(0,8,0,0)
|
|
cutomSendItemsWraper0.setLayout(layout0)
|
|
layout0.addWidget(self.customSendScroll)
|
|
customSendItemsLayoutWrapper = QVBoxLayout()
|
|
customSendItemsLayoutWrapper.setContentsMargins(0,0,0,0)
|
|
cutomSendItemsWraper.setLayout(customSendItemsLayoutWrapper)
|
|
|
|
# -- items container
|
|
self.customItems = QWidget()
|
|
self.customSendItemsLayout = QVBoxLayout()
|
|
self.customSendItemsLayout.setContentsMargins(0,0,0,0)
|
|
self.customItems.setLayout(self.customSendItemsLayout)
|
|
customSendItemsLayoutWrapper.addWidget(self.customItems)
|
|
customSendItemsLayoutWrapper.addStretch(0)
|
|
|
|
# add widgets
|
|
self.mainWidget.addWidget(self.receiveWidget)
|
|
self.mainWidget.addWidget(self.clearBtn)
|
|
self.mainWidget.addWidget(cutomSendItemsWraper0)
|
|
self.mainWidget.setStretchFactor(0, 2)
|
|
self.mainWidget.setStretchFactor(1, 1)
|
|
self.mainWidget.setStretchFactor(2, 11)
|
|
|
|
# event
|
|
def clearReceived():
|
|
self.receiveWidget.clear();self.statusBar.clear()
|
|
self.clearBtn.clicked.connect(clearReceived)
|
|
return self.mainWidget
|
|
|
|
def onWidgetStatusBar(self, parent):
|
|
self.statusBar = statusBar(rxTxCount=True)
|
|
return self.statusBar
|
|
|
|
def onUiInitDone(self):
|
|
'''
|
|
UI init done, you can update your widget here
|
|
this method runs in UI thread, do not block too long
|
|
'''
|
|
newItems = []
|
|
print(self.config["customSendItems"])
|
|
for item in self.config["customSendItems"]:
|
|
print(item)
|
|
item = self.insertSendItem(item, load=True)
|
|
newItems.append(item)
|
|
self.config["customSendItems"] = newItems
|
|
self.showReceiveDataSignal.connect(self.showReceivedData)
|
|
# frame struct
|
|
self.structSelfTestRsp = Struct("<BB B BBBbbb BbBBB H")
|
|
self.structStatusCheckRsp = Struct("<BB Q HIII IBBBBI BBHH HHHIIIIII i II I IIII d Q dBB dB H I BBBbbb BbBBB H")
|
|
|
|
def insertSendItem(self, item = None, load = False):
|
|
caption = item["caption"]
|
|
itemWidget = QWidget()
|
|
layout = QHBoxLayout()
|
|
layout.setContentsMargins(0,0,0,0)
|
|
itemWidget.setLayout(layout)
|
|
send = QPushButton("")
|
|
label = QLabel(caption)
|
|
if (not "icon" in item) or not item["icon"]:
|
|
item["icon"] = "fa.send"
|
|
utils_ui.setButtonIcon(send, item["icon"])
|
|
send.setProperty("class", "smallBtn")
|
|
def sendCustomData(idx):
|
|
self.sendCustomItem(self.config["customSendItems"][idx])
|
|
send.clicked.connect(lambda: sendCustomData(self.customSendItemsLayout.indexOf(itemWidget)))
|
|
layout.addWidget(label)
|
|
layout.addWidget(send)
|
|
self.customSendItemsLayout.addWidget(itemWidget)
|
|
if not load:
|
|
self.config["customSendItems"].append(item)
|
|
return item
|
|
|
|
def showReceivedData(self, text: str):
|
|
curScrollValue = self.receiveWidget.verticalScrollBar().value()
|
|
self.receiveWidget.moveCursor(QTextCursor.End)
|
|
endScrollValue = self.receiveWidget.verticalScrollBar().value()
|
|
self.receiveWidget.insertPlainText(text)
|
|
if curScrollValue < endScrollValue:
|
|
self.receiveWidget.verticalScrollBar().setValue(curScrollValue)
|
|
else:
|
|
self.receiveWidget.moveCursor(QTextCursor.End)
|
|
|
|
def onReceived(self, data : bytes):
|
|
'''
|
|
call in receive thread, not UI thread
|
|
'''
|
|
self.statusBar.addRx(len(data))
|
|
## Apply data validation check
|
|
if len(data) < 7:
|
|
self.showReceiveDataSignal.emit("\n### Wrong data format: wrong frame header/tail\n")
|
|
return
|
|
|
|
dataLen = data[-3]
|
|
if data[0] != 0x5A or data[1] != 0x54 or data[-1] != 0xFE or data[-2] != 0x5A:
|
|
self.showReceiveDataSignal.emit("\n### Wrong data format: wrong length\n")
|
|
|
|
checkSum = 0
|
|
for i in data[2:-4]:
|
|
checkSum += i
|
|
if checkSum & 0xFF != data[-4]:
|
|
self.showReceiveDataSignal.emit("\n### Wrong data format: wrong check sum\n")
|
|
|
|
## Eval data
|
|
data = data[2:-5].replace(b'\x00\x54\xFE', b'\x54\xFE')
|
|
# self.printFrame(data)
|
|
if data[0] == 0xE1:
|
|
# eval self test
|
|
self.showReceiveDataSignal.emit(self.evalSelfTestRsp(data) + "\n")
|
|
elif data[0] == 0xE6 or data[0] == 0xF6:
|
|
self.showReceiveDataSignal.emit(self.evalStatusCheckRsp(data) + "\n")
|
|
elif data[0] == 0xE5 or data[0] == 0xF5:
|
|
self.showReceiveDataSignal.emit(self.printFrame(data) + "\n")
|
|
else:
|
|
self.showReceiveDataSignal.emit(f"\n### Unexpected response code 0x{data[0]:02x}.\n")
|
|
|
|
def evalSelfTestRsp(self, data: bytes):
|
|
try:
|
|
payload = self.structSelfTestRsp.unpack(data)
|
|
except :
|
|
return f"\n### evalSelfTestRsp:: Failed to unpack data (data length {len(data)} byte).\n"
|
|
|
|
ret = "----------- 自检回令 -----------\n"
|
|
ret += "节点类型: "
|
|
ret += "K频段(0xDD)\n" if payload[1] == 0xDD else "S频段(0xDD)\n"
|
|
|
|
ret += "自检结果: "
|
|
ret += "正常(0xAA)\n" if payload[2] == 0xAA else "异常(0x55 or other)\n"
|
|
|
|
hwOffset = 3
|
|
|
|
ret += f"FPGA 电压监测: {bin(payload[hwOffset+0])[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 温度 1: {payload[hwOffset+3]:>4d}\n"
|
|
ret += f"AD 温度 2: {payload[hwOffset+4]:>4d}\n"
|
|
ret += f"FPGA 温度: {payload[hwOffset+5]:>4d}\n"
|
|
ret += f"电压监测: {bin(payload[hwOffset+6])[2:].zfill(8)}\n"
|
|
ret += f"射频温度: {payload[hwOffset+7]:>4d}\n"
|
|
ret += f"锁定指示: {bin(payload[hwOffset+8])[2:].zfill(8)}\n"
|
|
ret += f"MGC 1~2: {payload[hwOffset+9] & 0x0f:>2d} - {payload[hwOffset+9] >> 4:>2d}\n"
|
|
ret += f"MGC 3~4: {payload[hwOffset+10] & 0x0f:>2d} - {payload[hwOffset+10] >> 4:>2d}\n"
|
|
ret += "--------------------------------\n"
|
|
|
|
return ret
|
|
|
|
def printFrame(self, data: bytes):
|
|
ret = 'Retrieved Data Frame:\n'
|
|
for i in range(len(data)):
|
|
ret += f'{data[i]:02X}'
|
|
ret += '\n' if i%16 == 15 else ' '
|
|
ret += '\n'
|
|
return ret
|
|
|
|
def evalStatusCheckRsp(self, data: bytes):
|
|
try:
|
|
payload = self.structStatusCheckRsp.unpack(data)
|
|
except :
|
|
return f"\n### evalStatusCheckRsp:: Failed to unpack data (data length {len(data)} byte).\n"
|
|
|
|
ret = "----------- 遥测回令 -----------\n"
|
|
ret += "节点类型: "
|
|
ret += "K频段(0xDD)\n" if payload[1] == 0xDD else "S频段(0xDD)\n"
|
|
|
|
ret += f"时间戳: {payload[2]/1e2:.2f} us\n"
|
|
|
|
sysbusOffset = 3
|
|
linkOffset = 7
|
|
phyOffset = 30
|
|
hwOffset = 43
|
|
|
|
ret += "\n -- 总线状态\n"
|
|
ret += f"配置装订: {payload[sysbusOffset + 0]:04X}\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"
|
|
|
|
ret += "网络连接状态: "
|
|
if payload[linkOffset + 3] == 0x11:
|
|
ret += f"已连接 (0x{payload[linkOffset + 3]:02X})\n"
|
|
elif payload[linkOffset + 3] == 0x00:
|
|
ret += f"未连接 (0x{payload[linkOffset + 3]:02X})\n"
|
|
else:
|
|
ret += f"【未知错误】(0x{payload[linkOffset + 3]:02X})\n"
|
|
|
|
ret += "网络运行阶段: "
|
|
if payload[linkOffset + 4] == 0x11:
|
|
ret += f"扫描建网 (0x{payload[linkOffset + 4]:02X})\n"
|
|
elif payload[linkOffset + 4] == 0x22:
|
|
ret += f"业务通信 (0x{payload[linkOffset + 4]:02X})\n"
|
|
else:
|
|
ret += f"【未知错误】(0x{payload[linkOffset + 4]:02X})\n"
|
|
|
|
ret += f"重建/入网计数: {payload[linkOffset + 5]:>6d}\n"
|
|
ret += f"通信波位号: {payload[linkOffset + 6]:>2d}\n"
|
|
|
|
ret += "通信波束宽度: "
|
|
if payload[linkOffset + 7] == 0x00:
|
|
ret += f"8° 窄波束 (0x{payload[linkOffset + 7]:02X})\n"
|
|
elif payload[linkOffset + 7] == 0x10:
|
|
ret += f"25° 次波束 (0x{payload[linkOffset + 7]:02X})\n"
|
|
elif payload[linkOffset + 7] == 0x20:
|
|
ret += f"60° 次波束 (0x{payload[linkOffset + 7]:02X})\n"
|
|
elif payload[linkOffset + 7] == 0x30:
|
|
ret += f"120° 宽波束 (0x{payload[linkOffset + 7]:02X})\n"
|
|
else:
|
|
ret += f"【未知错误】(0x{payload[linkOffset + 7]:02X})\n"
|
|
|
|
ret += f"离轴角: {payload[linkOffset + 8]*0.1:.2f}°\n"
|
|
ret += f"偏航角: {payload[linkOffset + 9]*0.1:.2f}°\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"时间偏差记录: {payload[linkOffset + 19]/1e2:.2f} us\n"
|
|
ret += f"发送数据 ID: {payload[linkOffset + 20]:>6d}\n"
|
|
ret += f"接收数据 ID: {payload[linkOffset + 21]:>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 += "\n -- 硬件状态\n"
|
|
ret += f"FPGA 电压监测: {bin(payload[hwOffset+0])[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 温度 1: {payload[hwOffset+3]:>4d}\n"
|
|
ret += f"AD 温度 2: {payload[hwOffset+4]:>4d}\n"
|
|
ret += f"FPGA 温度: {payload[hwOffset+5]:>4d}\n"
|
|
ret += f"电压监测: {bin(payload[hwOffset+6])[2:].zfill(8)}\n"
|
|
ret += f"射频温度: {payload[hwOffset+7]:>4d}\n"
|
|
ret += f"锁定指示: {bin(payload[hwOffset+8])[2:].zfill(8)}\n"
|
|
ret += f"MGC 1~2: {payload[hwOffset+9] & 0x0f:>2d} - {payload[hwOffset+9] >> 4:>2d}\n"
|
|
ret += f"MGC 3~4: {payload[hwOffset+10] & 0x0f:>2d} - {payload[hwOffset+10] >> 4:>2d}\n"
|
|
|
|
ret += "--------------------------------\n"
|
|
return ret
|
|
|
|
def onSent(self, ok, msg, length, path):
|
|
if ok:
|
|
self.statusBar.addTx(length)
|
|
else:
|
|
self.hintSignal.emit("error", _("Error"), _("Send data failed!") + " " + msg)
|
|
|
|
def sendCustomItem(self, item):
|
|
data = item["data"]
|
|
dateBytes = self.parseSendData(data, encoding = self.configGlobal["encoding"], isHexStr = True)
|
|
if dateBytes:
|
|
self.send(dateBytes, callback=self.onSent) |