Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions dronecan_gui_tool/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@

assert sys.version[0] == '3'

def parse_load_modules(argv):
modules = [m.strip() for m in argv.split(",") if m.strip()]
if not modules:
return None
return modules

from argparse import ArgumentParser
parser = ArgumentParser(description='DroneCAN GUI tool')

Expand All @@ -28,6 +34,8 @@
parser.add_argument("--filtered", action='store_true', help="enable filtering of DroneCAN traffic")
parser.add_argument("--target-system", help="set the targetted system", type=int, default=0)

parser.add_argument("--load-module", type=parse_load_modules, nargs=1, help="Comma-separated list of modules to load (e.g. mod1,mod2).") # exactly one argument required if flag is present

args = parser.parse_args()

#
Expand Down Expand Up @@ -95,7 +103,18 @@
from .widgets.can_adapter_control_panel import spawn_window as spawn_can_adapter_control_panel

from .panels import PANELS
from .panels import import_panel

EXT_PLUGINS = []
modules = args.load_module[0] if args.load_module else []
if len(modules) > 0:
for module in modules:
try:
panel = import_panel(module)
EXT_PLUGINS.append(panel)
except Exception as ex:
print(f"Unable to load {module}: {ex}")
print(f"Loaded {len(EXT_PLUGINS)} plugin modules!")

NODE_NAME = 'org.dronecan.gui_tool'

Expand Down Expand Up @@ -204,6 +223,39 @@ def __init__(self, node, iface_name, iface_kwargs):
action.triggered.connect(lambda state, panel=panel: panel.safe_spawn(self, self._node))
panels_menu.addAction(action)

#
# External Modules menu
#
def get_or_create_submenu(parent_menu, menu_name):
"""
Find a submenu with menu_name under parent_menu, or create it if not found.
"""
for action in parent_menu.actions():
submenu = action.menu()
if submenu and submenu.title() == menu_name:
return submenu
# Not found, create new submenu
return parent_menu.addMenu(menu_name)

if len(EXT_PLUGINS) > 0:
extern_modules_menu = self.menuBar().addMenu('P&lugins')
for idx, panel in enumerate(EXT_PLUGINS):
menu_path = getattr(panel, "menu_path", "")
path_parts = [p for p in menu_path.split("/") if p]

current_menu = extern_modules_menu
for part in path_parts:
current_menu = get_or_create_submenu(current_menu, part)

action = QAction(panel.name, self)
icon = panel.get_icon()
if icon:
action.setIcon(icon)
if idx < 9:
action.setShortcut(QKeySequence(f'Ctrl+Shift+[,{(idx + 1)}'))
action.triggered.connect(lambda state, panel=panel: panel.safe_spawn(self, self._node))
current_menu.addAction(action)

#
# Help menu
#
Expand Down
19 changes: 19 additions & 0 deletions dronecan_gui_tool/panels/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from . import hobbywing_esc
from . import rc_panel

import importlib.util

class PanelDescriptor:
def __init__(self, module):
self.name = module.PANEL_NAME
Expand All @@ -36,6 +38,23 @@ def safe_spawn(self, parent, node):
except Exception as ex:
show_error('Panel error', 'Could not spawn panel', ex)

def import_panel(name):
"""Given a package name like 'foo.bar.quux', imports the package
and returns the desired module."""
spec = importlib.util.find_spec(name)
mod = None
if spec is None:
raise Exception(f"Module '{name}' not found!")
else:
mod = importlib.import_module(name)
print(f"Successfully imported {name} from {mod.__file__}")
return PluginPanelDescriptor(mod)

class PluginPanelDescriptor(PanelDescriptor):
def __init__(self, module):
super().__init__(module)

self.menu_path = getattr(module, "MENU_PATH", "")

PANELS = [
PanelDescriptor(esc_panel),
Expand Down