Skip to content

Commit

Permalink
IMPROVEMENT: Move connection selection code to it's own file
Browse files Browse the repository at this point in the history
  • Loading branch information
amilcarlucas committed Apr 10, 2024
1 parent 9226ef7 commit bcee897
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 118 deletions.
79 changes: 3 additions & 76 deletions MethodicConfigurator/frontend_tkinter.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import tkinter as tk
from tkinter import filedialog
from tkinter import messagebox
from tkinter import simpledialog
from tkinter import ttk
from logging import debug as logging_debug
from logging import info as logging_info
Expand All @@ -20,7 +19,6 @@
from logging import critical as logging_critical
from PIL import Image
from PIL import ImageTk
from platform import system as platform_system
from typing import List
from typing import Tuple

Expand All @@ -31,13 +29,13 @@
from backend_flightcontroller import FlightController

from frontend_tkinter_base import show_no_param_files_error
from frontend_tkinter_base import show_no_connection_error
from frontend_tkinter_base import show_tooltip
from frontend_tkinter_base import PairTupleCombobox
from frontend_tkinter_base import AutoResizeCombobox
from frontend_tkinter_base import ScrollFrame
from frontend_tkinter_base import BaseWindow

from frontend_tkinter_connection_selection import ConnectionSelectionWindow


def show_about_window(root, version: str):
# Create a new window for the custom "About" message
Expand Down Expand Up @@ -153,22 +151,7 @@ def __init__(self, current_file: str, flight_controller: FlightController,
self.file_selection_combobox.pack(side=tk.TOP, anchor=tk.NW, pady=(4, 0))

# Create a new frame inside the config_subframe for the flight controller connection selection label and combobox
conn_selection_frame = tk.Frame(config_subframe)
conn_selection_frame.pack(side=tk.RIGHT, fill="x", expand=False, padx=(6, 4))

# Create a description label for the flight controller connection selection
conn_selection_label = tk.Label(conn_selection_frame, text="flight controller connection:")
conn_selection_label.pack(side=tk.TOP, anchor=tk.NW) # Add the label to the top of the conn_selection_frame

# Create a read-only combobox for flight controller connection selection
self.conn_selection_combobox = PairTupleCombobox(conn_selection_frame, self.flight_controller.get_connection_tuples(),
self.flight_controller.comport.device if
hasattr(self.flight_controller.comport, "device") else None,
state='readonly')
self.conn_selection_combobox.bind("<<ComboboxSelected>>", self.on_select_connection_combobox_change)
self.conn_selection_combobox.pack(side=tk.TOP, anchor=tk.NW, pady=(4, 0))
show_tooltip(self.conn_selection_combobox, "Select the flight controller connection\nYou can add a custom connection "
"to the existing ones")
ConnectionSelectionWindow(self, config_subframe, self.flight_controller)

# Load the ArduPilot logo and scale it down to image_height pixels in height
image_height = 40
Expand Down Expand Up @@ -295,41 +278,6 @@ def on_param_file_combobox_change(self, _event, forced: bool = False):
self.update_documentation_labels()
self.repopulate_parameter_table(selected_file)

def on_select_connection_combobox_change(self, _event):
selected_connection = self.conn_selection_combobox.getSelectedKey()
logging_debug(f"Connection combobox changed to: {selected_connection}")
if self.flight_controller.master is None or selected_connection != self.flight_controller.comport.device:
if selected_connection == 'Add another':
self.on_add_connection(None)
return
self.reconnect(selected_connection)

def on_add_connection(self, _event):
# Open the connection selection dialog
selected_connection = simpledialog.askstring("Flight Controller Connection",
"Enter the connection string to the flight controller. "
"Examples are:\n\nCOM4 (on windows)\n"
"/dev/serial/by-id/usb-xxx (on linux)\n"
"tcp:127.0.0.1:5761\n"
"udp:udp:127.0.0.1:14551")
logging_debug(f"Will add new connection: {selected_connection} if not duplicated")
self.flight_controller.add_connection(selected_connection)
connection_tuples = self.flight_controller.get_connection_tuples()
logging_debug(f"Updated connection tuples: {connection_tuples} with selected connection: {selected_connection}")
self.conn_selection_combobox.set_entries_tupple(connection_tuples, selected_connection)
self.reconnect(selected_connection)

def reconnect(self, selected_connection: str = None):
if selected_connection:
[self.connection_progress_window,
self.connection_progress_bar,
self.connection_progress_label] = self.create_progress_window("Connecting with the FC")
error_message = self.flight_controller.connect(selected_connection, self.update_connection_progress_bar)
if error_message:
show_no_connection_error(error_message)
return
self.connection_progress_window.destroy()

def read_flight_controller_parameters(self, reread: bool = False):
[self.param_read_progress_window,
self.param_read_progress_bar,
Expand Down Expand Up @@ -558,27 +506,6 @@ def get_write_selected_params(self):
def on_show_only_changed_checkbox_change(self):
self.repopulate_parameter_table(self.current_file)

def update_connection_progress_bar(self, current_value: int, max_value: int):
"""
Update the FC connection progress bar and the progress message with the current progress.
Args:
current_value (int): The current progress value.
max_value (int): The maximum progress value.
"""
self.connection_progress_window.lift()

self.connection_progress_bar['value'] = current_value
self.connection_progress_bar['maximum'] = max_value
self.connection_progress_bar.update()

# Update the reset progress message
self.connection_progress_label.config(text=f"waiting for {current_value} of {max_value} seconds")

# Close the reset progress window when the process is complete
if current_value == max_value:
self.connection_progress_window.destroy()

def update_reset_progress_bar(self, current_value: int, max_value: int):
"""
Update the FC reset progress bar and the progress message with the current progress.
Expand Down
42 changes: 0 additions & 42 deletions MethodicConfigurator/frontend_tkinter_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
# from logging import info as logging_info
from logging import warning as logging_warning
from logging import error as logging_error
from logging import critical as logging_critical
from platform import system as platform_system

from backend_filesystem import LocalFilesystem
Expand Down Expand Up @@ -74,47 +73,6 @@ def update_combobox_width(combobox):
combobox.config(width=max(min_width, max_width))


# https://dev.to/geraldew/python-tkinter-an-exercise-in-wrapping-the-combobox-ndb
class PairTupleCombobox(ttk.Combobox):

def _process_listPairTuple(self, ip_listPairTuple):
r_list_keys = []
r_list_shows = []
for tpl in ip_listPairTuple:
r_list_keys.append(tpl[0])
r_list_shows.append(tpl[1])
return r_list_keys, r_list_shows

def __init__(self, container, p_listPairTuple, selected_element, *args, **kwargs):
super().__init__(container, *args, **kwargs)
self.set_entries_tupple(p_listPairTuple, selected_element)

def set_entries_tupple(self, p_listPairTuple, selected_element):
self.list_keys, self.list_shows = self._process_listPairTuple(p_listPairTuple)
self['values'] = tuple(self.list_shows)
# still need to set the default value from the nominated key
if selected_element:
try:
default_key_index = self.list_keys.index(selected_element)
self.current(default_key_index)
except IndexError:
logging_critical("connection combobox selected string '%s' not in list %s", selected_element, self.list_keys)
exit(1)
except ValueError:
logging_critical("connection combobox selected string '%s' not in list %s", selected_element, self.list_keys)
exit(1)
update_combobox_width(self)
else:
logging_warning("No connection combobox element selected")

def getSelectedKey(self):
try:
i_index = self.current()
return self.list_keys[i_index]
except IndexError:
return None


class AutoResizeCombobox(ttk.Combobox):

def __init__(self, container, values, selected_element, tooltip, *args, **kwargs):
Expand Down
152 changes: 152 additions & 0 deletions MethodicConfigurator/frontend_tkinter_connection_selection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#!/usr/bin/env python3

'''
This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
(C) 2024 Amilcar do Carmo Lucas, IAV GmbH
SPDX-License-Identifier: GPL-3
'''

import tkinter as tk
from tkinter import ttk
from tkinter import simpledialog
from logging import debug as logging_debug
from logging import warning as logging_warning
from logging import critical as logging_critical

from backend_flightcontroller import FlightController

from frontend_tkinter_base import show_no_connection_error
from frontend_tkinter_base import show_tooltip
from frontend_tkinter_base import update_combobox_width


# https://dev.to/geraldew/python-tkinter-an-exercise-in-wrapping-the-combobox-ndb
class PairTupleCombobox(ttk.Combobox):

def _process_listPairTuple(self, ip_listPairTuple):
r_list_keys = []
r_list_shows = []
for tpl in ip_listPairTuple:
r_list_keys.append(tpl[0])
r_list_shows.append(tpl[1])
return r_list_keys, r_list_shows

def __init__(self, container, p_listPairTuple, selected_element, *args, **kwargs):
super().__init__(container, *args, **kwargs)
self.set_entries_tupple(p_listPairTuple, selected_element)

def set_entries_tupple(self, p_listPairTuple, selected_element):
self.list_keys, self.list_shows = self._process_listPairTuple(p_listPairTuple)
self['values'] = tuple(self.list_shows)
# still need to set the default value from the nominated key
if selected_element:
try:
default_key_index = self.list_keys.index(selected_element)
self.current(default_key_index)
except IndexError:
logging_critical("connection combobox selected string '%s' not in list %s", selected_element, self.list_keys)
exit(1)
except ValueError:
logging_critical("connection combobox selected string '%s' not in list %s", selected_element, self.list_keys)
exit(1)
update_combobox_width(self)
else:
logging_warning("No connection combobox element selected")

def getSelectedKey(self):
try:
i_index = self.current()
return self.list_keys[i_index]
except IndexError:
return None


class ConnectionSelectionWindow():
def __init__(self, parent, parent_frame, flight_controller: FlightController):
self.parent = parent
self.flight_controller = flight_controller
self.previous_selection = flight_controller.comport.device if hasattr(self.flight_controller.comport, "device") \
else None

# Create a new frame for the flight controller connection selection label and combobox
conn_selection_frame = tk.Frame(parent_frame)
conn_selection_frame.pack(side=tk.RIGHT, fill="x", expand=False, padx=(6, 4))

# Create a description label for the flight controller connection selection
conn_selection_label = tk.Label(conn_selection_frame, text="flight controller connection:")
conn_selection_label.pack(side=tk.TOP, anchor=tk.NW) # Add the label to the top of the conn_selection_frame

# Create a read-only combobox for flight controller connection selection
self.conn_selection_combobox = PairTupleCombobox(conn_selection_frame, self.flight_controller.get_connection_tuples(),
self.previous_selection,
state='readonly')
self.conn_selection_combobox.bind("<<ComboboxSelected>>", self.on_select_connection_combobox_change)
self.conn_selection_combobox.pack(side=tk.TOP, anchor=tk.NW, pady=(4, 0))
show_tooltip(self.conn_selection_combobox, "Select the flight controller connection\nYou can add a custom connection "
"to the existing ones")

def on_select_connection_combobox_change(self, _event):
selected_connection = self.conn_selection_combobox.getSelectedKey()
logging_debug(f"Connection combobox changed to: {selected_connection}")
if self.flight_controller.master is None or selected_connection != self.flight_controller.comport.device:
if selected_connection == 'Add another':
if not self.add_connection() and self.previous_selection:
# nothing got selected revert to the current connection
self.conn_selection_combobox.set(self.previous_selection)
return
self.reconnect(selected_connection)

def add_connection(self):
# Open the connection selection dialog
selected_connection = simpledialog.askstring("Flight Controller Connection",
"Enter the connection string to the flight controller. "
"Examples are:\n\nCOM4 (on windows)\n"
"/dev/serial/by-id/usb-xxx (on linux)\n"
"tcp:127.0.0.1:5761\n"
"udp:udp:127.0.0.1:14551")
if selected_connection:
logging_debug(f"Will add new connection: {selected_connection} if not duplicated")
self.flight_controller.add_connection(selected_connection)
connection_tuples = self.flight_controller.get_connection_tuples()
logging_debug(f"Updated connection tuples: {connection_tuples} with selected connection: {selected_connection}")
self.conn_selection_combobox.set_entries_tupple(connection_tuples, selected_connection)
self.reconnect(selected_connection)
else:
logging_debug(f"Add connection canceled or string empty {selected_connection}")
return selected_connection

def reconnect(self, selected_connection: str = None):
if selected_connection:
[self.connection_progress_window,
self.connection_progress_bar,
self.connection_progress_label] = self.parent.create_progress_window("Connecting with the FC")
error_message = self.flight_controller.connect(selected_connection, self.update_connection_progress_bar)
if error_message:
show_no_connection_error(error_message)
return True
self.connection_progress_window.destroy()
self.previous_selection = self.flight_controller.comport.device # Store the current connection as the previous selection
return False

def update_connection_progress_bar(self, current_value: int, max_value: int):
"""
Update the FC connection progress bar and the progress message with the current progress.
Args:
current_value (int): The current progress value.
max_value (int): The maximum progress value.
"""
self.connection_progress_window.lift()

self.connection_progress_bar['value'] = current_value
self.connection_progress_bar['maximum'] = max_value
self.connection_progress_bar.update()

# Update the reset progress message
self.connection_progress_label.config(text=f"waiting for {current_value} of {max_value} seconds")

# Close the reset progress window when the process is complete
if current_value == max_value:
self.connection_progress_window.destroy()

0 comments on commit bcee897

Please sign in to comment.