Skip to content

Commit

Permalink
Modules: Add rc and servo gui
Browse files Browse the repository at this point in the history
  • Loading branch information
stephendade committed Apr 11, 2024
1 parent 6c1cf36 commit 5b40c5c
Show file tree
Hide file tree
Showing 2 changed files with 243 additions and 3 deletions.
147 changes: 147 additions & 0 deletions MAVProxy/modules/lib/wxrc.py
Original file line number Diff line number Diff line change
@@ -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)
99 changes: 96 additions & 3 deletions MAVProxy/modules/mavproxy_rc.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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():
Expand All @@ -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'''
Expand Down Expand Up @@ -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 <set|channel|all|clear|status> <pwmvalue>")
print("Usage: rc <set|channel|all|clear|status|guiin|guiout> <pwmvalue>")
return
value = int(args[1])
if value > 65535 or value < -1:
Expand All @@ -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)

0 comments on commit 5b40c5c

Please sign in to comment.