Skip to content

Commit 8f04f1e

Browse files
committed
rename to serial forwarding
1 parent ae28d25 commit 8f04f1e

File tree

3 files changed

+244
-157
lines changed

3 files changed

+244
-157
lines changed

dronecan_gui_tool/panels/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from . import esc_panel
1313
from . import actuator_panel
1414
from . import RTK_panel
15-
from . import uCenter_panel
15+
from . import serial_panel
1616

1717
class PanelDescriptor:
1818
def __init__(self, module):
@@ -37,5 +37,5 @@ def safe_spawn(self, parent, node):
3737
PanelDescriptor(esc_panel),
3838
PanelDescriptor(actuator_panel),
3939
PanelDescriptor(RTK_panel),
40-
PanelDescriptor(uCenter_panel)
40+
PanelDescriptor(serial_panel)
4141
], key=lambda x: x.name)
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
#
2+
# Copyright (C) 2023 DroneCAN Development Team <dronecan.org>
3+
#
4+
# This software is distributed under the terms of the MIT License.
5+
#
6+
# Author: Andrew Tridgell
7+
#
8+
9+
import dronecan
10+
from functools import partial
11+
from PyQt5.QtWidgets import QGridLayout, QWidget, QLabel, QDialog, \
12+
QTableWidget, QVBoxLayout, QGroupBox, QTableWidgetItem, QLineEdit, \
13+
QComboBox, QHBoxLayout, QSpinBox
14+
from PyQt5.QtCore import Qt, QTimer
15+
from ..widgets import get_icon
16+
from . import rtcm3
17+
import time
18+
import socket
19+
import errno
20+
21+
__all__ = 'PANEL_NAME', 'spawn', 'get_icon'
22+
23+
PANEL_NAME = 'Serial Forwarding'
24+
25+
_singleton = None
26+
27+
class serialPanel(QDialog):
28+
def __init__(self, parent, node):
29+
super(serialPanel, self).__init__(parent)
30+
self.setWindowTitle('Serial Forwarding')
31+
self.setAttribute(Qt.WA_DeleteOnClose)
32+
33+
self.sock = None
34+
self.listen_sock = None
35+
self.addr = None
36+
self.logfile = None
37+
self.num_rx_bytes = 0
38+
self.num_tx_bytes = 0
39+
self.node = node
40+
self.tunnel = None
41+
self.target_dev = -1
42+
43+
layout = QVBoxLayout()
44+
45+
self.node_select = QComboBox()
46+
self.baud_select = QComboBox()
47+
for b in ["Unchanged", 9600, 19200, 38400, 57600, 115200, 230400, 430600, 921600]:
48+
self.baud_select.addItem(str(b))
49+
self.baud_select.currentIndexChanged.connect(self.change_baud)
50+
51+
self.lock_select = QComboBox()
52+
self.lock_select.addItem("UnLocked")
53+
self.lock_select.addItem("Locked")
54+
55+
self.port_select = QSpinBox()
56+
self.port_select.setMinimum(1)
57+
self.port_select.setMaximum(65535)
58+
self.port_select.setValue(2001)
59+
self.port_select.valueChanged.connect(self.restart_listen)
60+
61+
self.state = QLineEdit()
62+
self.state.setText("disconnected")
63+
self.state.setReadOnly(True)
64+
self.rx_bytes = QLineEdit()
65+
self.rx_bytes.setText("0")
66+
self.rx_bytes.setReadOnly(True)
67+
68+
self.tx_bytes = QLineEdit()
69+
self.tx_bytes.setText("0")
70+
self.tx_bytes.setReadOnly(True)
71+
72+
layout.addLayout(self.labelWidget('Node', self.node_select))
73+
layout.addLayout(self.labelWidget('UART Locking', self.lock_select))
74+
layout.addLayout(self.labelWidget('Baudrate', self.baud_select))
75+
layout.addLayout(self.labelWidget('Listen Port', self.port_select))
76+
layout.addLayout(self.labelWidget("State", self.state))
77+
layout.addLayout(self.labelWidget("RX Bytes", self.rx_bytes))
78+
layout.addLayout(self.labelWidget("TX Bytes", self.tx_bytes))
79+
80+
self.setLayout(layout)
81+
self.resize(400, 200)
82+
83+
self.restart_listen()
84+
QTimer.singleShot(10, self.check_connection)
85+
QTimer.singleShot(250, self.update_nodes)
86+
87+
def labelWidget(self, label, widget):
88+
hlayout = QHBoxLayout()
89+
hlayout.addWidget(QLabel(label, self))
90+
hlayout.addWidget(widget)
91+
return hlayout
92+
93+
def __del__(self):
94+
print("serial closing")
95+
if self.listen_sock is not None:
96+
self.listen_sock.close()
97+
if self.sock is not None:
98+
self.sock.close()
99+
if self.tunnel is not None:
100+
self.tunnel.close()
101+
self.tunnel = None
102+
global _singleton
103+
_singleton = None
104+
105+
def closeEvent(self, event):
106+
self.__del__()
107+
super(serialPanel, self).closeEvent(event)
108+
109+
def update_nodes(self):
110+
'''update list of available nodes'''
111+
QTimer.singleShot(250, self.update_nodes)
112+
from ..widgets.node_monitor import app_node_monitor
113+
if app_node_monitor is None:
114+
print("no app_node_monitor")
115+
return
116+
node_list = []
117+
for nid in app_node_monitor._registry.keys():
118+
r = app_node_monitor._registry[nid]
119+
if r.info is not None:
120+
node_list.append("%u: %s" % (nid, r.info.name.decode()))
121+
else:
122+
node_list.append("%u" % nid)
123+
node_list = sorted(node_list)
124+
current_node = sorted([self.node_select.itemText(i) for i in range(self.node_select.count())])
125+
for n in node_list:
126+
if not n in current_node:
127+
print("Adding %s" % n)
128+
self.node_select.addItem(n)
129+
130+
def restart_listen(self):
131+
'''stop and restart listening socket'''
132+
if self.listen_sock is not None:
133+
self.listen_sock.close()
134+
self.listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
135+
self.listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
136+
self.listen_sock.bind(('', int(self.port_select.value())))
137+
self.listen_sock.setblocking(False)
138+
self.listen_sock.listen(1)
139+
self.state.setText("disconnected")
140+
141+
def get_baudrate(self):
142+
'''work out baud rate to use'''
143+
baud = self.baud_select.currentText()
144+
return 0 if baud == "Unchanged" else int(baud)
145+
146+
def change_baud(self):
147+
'''callback when selected baud rate changes'''
148+
if self.tunnel:
149+
baud = self.get_baudrate()
150+
self.tunnel.baudrate = baud
151+
print("change baudrate to %u" % baud)
152+
153+
def process_socket(self):
154+
'''process data from the socket'''
155+
while True:
156+
try:
157+
buf = self.sock.recv(120)
158+
except socket.error as ex:
159+
if ex.errno not in [ errno.EAGAIN, errno.EWOULDBLOCK ]:
160+
print("Closing: ", ex)
161+
self.sock = None
162+
if self.tunnel is not None:
163+
self.tunnel.close()
164+
self.tunnel = None
165+
return
166+
if buf is None or len(buf) == 0:
167+
break
168+
self.tunnel.write(buf)
169+
self.num_tx_bytes += len(buf)
170+
self.tx_bytes.setText("%u" % self.num_tx_bytes)
171+
172+
def process_tunnel(self):
173+
'''process data from the tunnel'''
174+
while True:
175+
buf = self.tunnel.read(120)
176+
if buf is None or len(buf) == 0:
177+
break
178+
try:
179+
self.sock.send(buf)
180+
except Exception:
181+
print("Closing: ", ex)
182+
self.sock = None
183+
if self.tunnel is not None:
184+
self.tunnel.close()
185+
self.tunnel = None
186+
return
187+
188+
self.num_rx_bytes += len(buf)
189+
self.rx_bytes.setText("%u" % self.num_rx_bytes)
190+
191+
def check_connection(self):
192+
'''called at 100Hz to process data'''
193+
QTimer.singleShot(10, self.check_connection)
194+
if self.sock is not None:
195+
self.process_socket()
196+
self.process_tunnel()
197+
198+
if self.sock is None:
199+
try:
200+
sock, self.addr = self.listen_sock.accept()
201+
except Exception as e:
202+
if e.errno not in [ errno.EAGAIN, errno.EWOULDBLOCK ]:
203+
print("ucenter listen fail")
204+
self.restart_listen()
205+
return
206+
return
207+
self.sock = sock
208+
self.sock.setblocking(False)
209+
self.state.setText("connection from %s:%u" % (self.addr[0], self.addr[1]))
210+
self.num_rx_bytes = 0
211+
self.num_tx_bytes = 0
212+
if self.tunnel is not None:
213+
self.tunnel.close()
214+
target_node = int(self.node_select.currentText().split(':')[0])
215+
216+
locked = self.lock_select.currentText() == "Locked"
217+
self.tunnel = dronecan.DroneCANSerial(None, target_node, self.target_dev,
218+
node=self.node,
219+
lock_port=locked, baudrate=self.get_baudrate())
220+
print("ucenter connection from %s" % str(self.addr))
221+
try:
222+
self.logfile = open("ubx.dat", "wb")
223+
except Exception:
224+
pass
225+
226+
227+
228+
def spawn(parent, node):
229+
global _singleton
230+
if _singleton is None:
231+
try:
232+
_singleton = serialPanel(parent, node)
233+
except Exception as ex:
234+
print(ex)
235+
236+
_singleton.show()
237+
_singleton.raise_()
238+
_singleton.activateWindow()
239+
240+
return _singleton
241+
242+
get_icon = partial(get_icon, 'asterisk')

0 commit comments

Comments
 (0)