forked from gatsby/df_datalink
434 lines
19 KiB
Python
434 lines
19 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 = "HJ DATALINK"
|
|
name = _("HJ DATALINK")
|
|
showReceiveDataSignal = pyqtSignal(str)
|
|
|
|
def onInit(self, config):
|
|
'''
|
|
init params
|
|
'''
|
|
default = {
|
|
"version": 1,
|
|
"customSendItems": [
|
|
{
|
|
"caption": "自检",
|
|
"icon": "fa5.question-circle",
|
|
"data": "5A 54 E1 EE 00 CF 02 5A FE"
|
|
},
|
|
# {
|
|
# "caption": "数据发送测试 - 1",
|
|
# "icon": "fa5.play-circle",
|
|
# "data": ""
|
|
# },
|
|
# {
|
|
# "caption": "数据发送测试 - 2",
|
|
# "icon": "fa5.play-circle",
|
|
# "data": ""
|
|
# },
|
|
{
|
|
"caption": "装订 - 【主 - 0x01】128 kbps,时隙表 0",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 E3 EE AA 01 22 00 00 7B 06 5A FE"
|
|
},
|
|
{
|
|
"caption": "装订 - 【主 - 0x01】2 Mbps,时隙表 1",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 E3 EE AA 01 44 01 00 C1 06 5A FE"
|
|
},
|
|
{
|
|
"caption": "装订 - 【主 - 0x01】静默",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 E3 EE 55 01 22 00 00 49 06 5A FE"
|
|
},
|
|
{
|
|
"caption": "装订 - 【从 - 0x02】128 kbps,时隙表 0",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 E3 EE AA 02 22 00 00 9F 06 5A FE"
|
|
},
|
|
{
|
|
"caption": "装订 - 【从 - 0x02】2 Mbps,时隙表 1",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 E3 EE AA 02 44 01 00 C2 06 5A FE"
|
|
},
|
|
{
|
|
"caption": "装订 - 【从 - 0x02】静默",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 E3 EE 55 02 22 00 00 0C 06 5A FE"
|
|
},
|
|
{
|
|
"caption": "装订 - 【从 - 0x03】128 kbps,时隙表 0",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 E3 EE AA 03 22 00 00 A0 06 5A FE"
|
|
},
|
|
{
|
|
"caption": "装订 - 【从 - 0x03】2 Mbps,时隙表 1",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 E3 EE AA 03 44 01 00 C3 06 5A FE"
|
|
},
|
|
{
|
|
"caption": "装订 - 【从 - 0x03】静默",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 E3 EE 55 03 22 00 00 0D 06 5A FE"
|
|
},
|
|
{
|
|
"caption": "遥测",
|
|
"icon": "fa5.play-circle",
|
|
"data": "5A 54 E6 EE 00 D4 02 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 HHHBBbB B 3s")
|
|
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")
|
|
self.structFljData = Struct("<dddd IHI fff fff")
|
|
self.structLjData = Struct("<IHI ffff IHI fff fff fff IHI fff fff fff IHI fff fff fff I")
|
|
self.structSysInfo = Struct("<IHI H B I B fff fff fff")
|
|
|
|
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('Retrieved Data Frame:\n' + 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 += f"节点编号: 0x{payload[-2]:02X}\n"
|
|
|
|
ret += "节点类型: "
|
|
if payload[1] == 0xEE:
|
|
ret += f"C频段 (0x{payload[1]:02X})\n"
|
|
else:
|
|
ret += f"【未知错误】(0x{payload[1]:02X})\n"
|
|
|
|
ret += "自检结果: "
|
|
if payload[2] == 0xAA:
|
|
ret += f"正常 (0x{payload[2]:02X})\n"
|
|
elif payload[2] == 0x55:
|
|
ret += f"异常 (0x{payload[2]:02X})\n"
|
|
else:
|
|
ret += f"【未知错误】(0x{payload[2]:02X})\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"天线-1 工作 PLL: {(payload[hwOffset+6] >> 15) & 0x01:>1d}\n"
|
|
ret += f"天线-1 工作频率: {(payload[hwOffset+6] & 0x7FFF)+3700:>4d}\n"
|
|
ret += f"天线-2 工作 PLL: {(payload[hwOffset+7] >> 15) & 0x01:>1d}\n"
|
|
ret += f"天线-2 工作频率: {(payload[hwOffset+7] & 0x7FFF)+3700:>4d}\n"
|
|
ret += f"天线-3 工作 PLL: {(payload[hwOffset+8] >> 15) & 0x01:>1d}\n"
|
|
ret += f"天线-3 工作频率: {(payload[hwOffset+8] & 0x7FFF)+3700:>4d}\n"
|
|
ret += f"天线 PLL 锁定状态: {bin(payload[hwOffset+9])[2:].zfill(8)}\n"
|
|
ret += f"天线工作模式: {bin(payload[hwOffset+10])[2:].zfill(8)}\n"
|
|
ret += f"天线温度: {payload[hwOffset+11]:>4d}\n"
|
|
ret += "--------------------------------\n"
|
|
|
|
return ret + '\n' + self.printFrame(data)
|
|
|
|
def printFrame(self, data: bytes):
|
|
ret = ''
|
|
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 += "节点类型: "
|
|
if payload[1] == 0xEE:
|
|
ret += f"C频段 (0x{payload[1]:02X})\n"
|
|
else:
|
|
ret += f"【未知错误】(0x{payload[1]:02X})\n"
|
|
|
|
ret += f"时间戳: {payload[2]/1e5:.2f} ms\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"
|
|
|
|
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]:>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"
|
|
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"天线-1 工作 PLL: {(payload[hwOffset+6] >> 15) & 0x01:>1d}\n"
|
|
ret += f"天线-1 工作频率: {(payload[hwOffset+6] & 0x7FFF)+3700:>4d}\n"
|
|
ret += f"天线-2 工作 PLL: {(payload[hwOffset+7] >> 15) & 0x01:>1d}\n"
|
|
ret += f"天线-2 工作频率: {(payload[hwOffset+7] & 0x7FFF)+3700:>4d}\n"
|
|
ret += f"天线-3 工作 PLL: {(payload[hwOffset+8] >> 15) & 0x01:>1d}\n"
|
|
ret += f"天线-3 工作频率: {(payload[hwOffset+8] & 0x7FFF)+3700:>4d}\n"
|
|
ret += f"天线 PLL 锁定状态: {bin(payload[hwOffset+9])[2:].zfill(8)}\n"
|
|
ret += f"天线工作模式: {bin(payload[hwOffset+10])[2:].zfill(8)}\n"
|
|
ret += f"天线温度: {payload[hwOffset+11]:>4d}\n"
|
|
|
|
ret += "--------------------------------\n"
|
|
return ret + '\n' + self.printFrame(data)
|
|
|
|
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)
|