diff --git a/MAVProxy/modules/lib/wxrc.py b/MAVProxy/modules/lib/wxrc.py new file mode 100644 index 0000000000..a7629cb9ca --- /dev/null +++ b/MAVProxy/modules/lib/wxrc.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python + +""" + MAVProxy RC GUI +""" +from MAVProxy.modules.lib import multiproc +from MAVProxy.modules.lib.wx_loader import wx +import wx.lib.agw.pygauge as PG +import time +from enum import Enum + + +class PanelType(Enum): + SERVO_OUT = (0, "Servo Out", "Servo") + RC_IN = (1, "RC In", "RC") + + def __new__(cls, value, display_string, short_string): + obj = object.__new__(cls) + obj._value_ = value + obj.display_string = display_string + obj.short_string = short_string + return obj + + +class RCPanel(wx.Panel): + def __init__(self, parent, panelType=PanelType.RC_IN): + super().__init__(parent) + + self.rc_labels = [] + self.rc_gauges = [] + self.rc_sizers = [] + + self.panelType = panelType + + self.sizer_main = wx.BoxSizer(wx.VERTICAL) + if self.panelType == PanelType.RC_IN: + self.create_rc_widgets(18) + else: + self.create_rc_widgets(16) + self.SetSizer(self.sizer_main) + + def create_rc_widgets(self, num_channels): + for i in range(num_channels): + curChanSizer = wx.BoxSizer(wx.HORIZONTAL) + label = wx.StaticText(self, label="{0} Channel {1}:".format(str(self.panelType.short_string), str(i+1))) + gauge = PG.PyGauge(self, range=2000, size=(200, 25), style=wx.GA_HORIZONTAL) # Assuming RC values range from 1000 to 2000 + gauge.SetDrawValue(draw=True, drawPercent=False, font=None, colour=wx.BLACK, formatString=None) + gauge.SetBarColour(wx.GREEN) + gauge.SetBackgroundColour(wx.WHITE) + gauge.SetBorderColor(wx.BLACK) + gauge.SetValue(0) + self.sizer_main.Add(curChanSizer, 1, wx.EXPAND, 0) + self.rc_labels.append(label) + self.rc_gauges.append(gauge) + curChanSizer.Add(label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) + curChanSizer.Add(gauge, 1, wx.ALL | wx.EXPAND, 5) + self.rc_sizers.append(curChanSizer) + + def update_gauges(self, msg): + # Update the gauges based on the relevant mavlink packet info + for i, gauge in enumerate(self.rc_gauges): + if msg.get_type() == 'RC_CHANNELS' and self.panelType == PanelType.RC_IN: + value = getattr(msg, 'chan{0}_raw'.format(i+1), 0) + gauge.SetValue(value) + gauge.Refresh() + elif msg.get_type() == 'SERVO_OUTPUT_RAW' and self.panelType == PanelType.SERVO_OUT: + value = getattr(msg, 'servo{0}_raw'.format(i+1), 0) + gauge.SetValue(value) + gauge.Refresh() + + +class RCFrame(wx.Frame): + def __init__(self, panelType, child_pipe_recv): + super().__init__(None, title=panelType.display_string) + self.panel = RCPanel(self, panelType) + self.Bind(wx.EVT_CLOSE, self.on_close) + self.panel.sizer_main.Fit(self) + # self.Center() + self.Layout() + + self.child_pipe_recv = child_pipe_recv + + # Start a separate timer to update RC data + self.timer = wx.Timer(self) + self.Bind(wx.EVT_TIMER, self.on_timer, self.timer) + self.timer.Start(100) + + def on_timer(self, event): + '''Main Loop.''' + while self.child_pipe_recv.poll(): + msg = self.child_pipe_recv.recv() + if msg: + self.panel.update_gauges(msg) + + def on_close(self, event): + self.Destroy() + + +class RCStatus(): + ''' + A RC input and Servo output GUI for MAVProxy. + ''' + def __init__(self, panelType=PanelType.RC_IN): + self.panelType = panelType + # Create Pipe to send attitude information from module to UI + self.child_pipe_recv, self.parent_pipe_send = multiproc.Pipe() + self.close_event = multiproc.Event() + self.close_event.clear() + self.child = multiproc.Process(target=self.child_task) + self.child.start() + + def child_task(self): + '''child process - this holds all the GUI elements''' + # from MAVProxy.modules.lib import wx_processguard + from MAVProxy.modules.lib.wx_loader import wx + # Create wx application + app = wx.App(False) + app.frame = RCFrame(panelType=self.panelType, child_pipe_recv=self.child_pipe_recv) + app.frame.SetDoubleBuffered(True) + app.frame.Show() + app.MainLoop() + self.close_event.set() # indicate that the GUI has closed + + def close(self): + '''Close the window.''' + self.close_event.set() + if self.is_alive(): + self.child.join(2) + self.parent_pipe_send.close() + self.child_pipe_recv.close() + + def is_alive(self): + '''check if child is still going''' + return self.child.is_alive() + + def processPacket(self, m): + '''Send mavlink packet onwards to panel''' + self.parent_pipe_send.send(m) + + +if __name__ == "__main__": + # test the console + multiproc.freeze_support() + rc_gui = RCStatus() + while rc_gui.is_alive(): + print('test') + time.sleep(0.5) diff --git a/MAVProxy/modules/mavproxy_rc.py b/MAVProxy/modules/mavproxy_rc.py index 1f4316230d..e24f62cb74 100644 --- a/MAVProxy/modules/mavproxy_rc.py +++ b/MAVProxy/modules/mavproxy_rc.py @@ -1,10 +1,16 @@ #!/usr/bin/env python '''rc command handling''' -import time, os, struct +import time, os, struct, sys from pymavlink import mavutil from MAVProxy.modules.lib import mp_module from MAVProxy.modules.lib import mp_settings +from MAVProxy.modules.lib import mp_util + +if mp_util.has_wxpython: + from MAVProxy.modules.lib.mp_menu import MPMenuItem + from MAVProxy.modules.lib.mp_menu import MPMenuSubMenu + class RCModule(mp_module.MPModule): def __init__(self, mpstate): @@ -24,6 +30,56 @@ def __init__(self, mpstate): self.rc_settings.completion) self.override_period = mavutil.periodic_event(self.rc_settings.override_hz) + self.servoout_gui = None + self.rcin_gui = None + self.init_gui_menus() + + def init_gui_menus(self): + '''initialise menus for console''' + self.menu_added_console = False + self.menu = None + + if not mp_util.has_wxpython: + return + + self.menu = MPMenuSubMenu( + "Tools", + items=self.gui_menu_items() + ) + + def idle_task_add_menu_items(self): + '''check for load of other modules, add our items as required''' + + if self.menu is None: + '''can get here if wxpython is not present''' + return + + if self.module('console') is not None: + if not self.menu_added_console: + self.menu_added_console = True + self.module('console').add_menu(self.menu) + else: + self.menu_added_console = False + + def unload(self): + if self.servoout_gui: + self.servoout_gui.close() + if self.rcin_gui: + self.rcin_gui.close() + self.unload_remove_menu_items() + + def unload_remove_menu_items(self): + '''remove out menu items from other modules''' + + if self.menu is None: + '''can get here if wxpython is not present''' + return + + if self.module('console') is not None and self.menu_added_console: + self.menu_added_console = False + self.module('console').remove_menu(self.menu) + super(RCModule, self).unload() + def idle_task(self): self.override_period.frequency = self.rc_settings.override_hz if self.override_period.trigger(): @@ -34,6 +90,19 @@ def idle_task(self): self.send_rc_override() if self.override_counter > 0: self.override_counter -= 1 + self.idle_task_add_menu_items() + # Check if the user has closed any GUI windows + if self.servoout_gui and self.servoout_gui.close_event.wait(0.001): + self.servoout_gui = None + if self.rcin_gui and self.rcin_gui.close_event.wait(0.001): + self.rcin_gui = None + + def mavlink_packet(self, m): + '''handle mavlink packets''' + if m.get_type() == 'RC_CHANNELS' and self.rcin_gui: + self.rcin_gui.processPacket(m) + elif m.get_type() == 'SERVO_OUTPUT_RAW' and self.servoout_gui: + self.servoout_gui.processPacket(m) def send_rc_override(self): '''send RC override packet''' @@ -114,9 +183,26 @@ def cmd_rc(self, args): if len(args) == 1 and args[0] == "status": self.cmd_rc_status() return - + if len(args) == 1 and args[0] == "guiin": + if not mp_util.has_wxpython: + print("No wxpython detected. Cannot show GUI") + elif sys.version_info >= (3, 10) and sys.modules['wx'].__version__ < '4.2.1': + print("wxpython needs to be >=4.2.1 on Python >=3.10. Cannot show GUI") + elif not self.rcin_gui: + from MAVProxy.modules.lib import wxrc + self.rcin_gui = wxrc.RCStatus(panelType=wxrc.PanelType.RC_IN) + return + if len(args) == 1 and args[0] == "guiout": + if not mp_util.has_wxpython: + print("No wxpython detected. Cannot show GUI") + elif sys.version_info >= (3, 10) and sys.modules['wx'].__version__ < '4.2.1': + print("wxpython needs to be >=4.2.1 on Python >=3.10. Cannot show GUI") + elif not self.servoout_gui: + from MAVProxy.modules.lib import wxrc + self.servoout_gui = wxrc.RCStatus(panelType=wxrc.PanelType.SERVO_OUT) + return if len(args) != 2: - print("Usage: rc ") + print("Usage: rc ") return value = int(args[1]) if value > 65535 or value < -1: @@ -135,6 +221,13 @@ def cmd_rc(self, args): channels[channel - 1] = value self.set_override(channels) + def gui_menu_items(self): + return [ + MPMenuItem('RC Inputs', 'RC Inputs', '# rc guiin'), + MPMenuItem('Servo Outputs', 'Servo Outputs', '# rc guiout'), + ] + + def init(mpstate): '''initialise module''' return RCModule(mpstate)