Skip to content

Commit 9f61393

Browse files
committed
test(flightcontroller info): improve the tests
1 parent 98dbd3f commit 9f61393

2 files changed

Lines changed: 449 additions & 587 deletions

File tree

ardupilot_methodic_configurator/frontend_tkinter_flightcontroller_info.py

Lines changed: 98 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
from logging import error as logging_error
1515
from logging import info as logging_info
1616
from tkinter import ttk
17+
from typing import Callable, Optional
18+
19+
from serial.serialutil import SerialException
1720

1821
from ardupilot_methodic_configurator import _, __version__
1922
from ardupilot_methodic_configurator.annotate_params import Par
@@ -25,19 +28,45 @@
2528
class FlightControllerInfoWindow(BaseWindow):
2629
"""Display flight controller hardware, firmware and parameter information."""
2730

28-
def __init__(self, flight_controller: FlightController) -> None:
29-
super().__init__()
31+
def __init__(self, flight_controller: FlightController, root_tk: Optional[tk.Tk] = None) -> None:
32+
"""
33+
Initialize the FlightControllerInfoWindow.
34+
35+
Args:
36+
flight_controller: The flight controller to display information for
37+
root_tk: Optional parent Tk root window
38+
39+
"""
40+
super().__init__(root_tk)
3041
self.root.title(_("ArduPilot methodic configurator ") + __version__ + _(" - Flight Controller Info"))
3142
self.root.geometry("500x350") # Adjust the window size as needed
3243
self.flight_controller = flight_controller
3344
self.param_default_values: dict[str, Par] = {}
3445

46+
# Initialize UI components
47+
self._init_ui()
48+
49+
# Log flight controller information
50+
self._log_flight_controller_info()
51+
52+
# Schedule parameter download after window is shown
53+
if not root_tk: # Only schedule if this is the main window
54+
self.root.after(50, self._schedule_download_parameters)
55+
56+
def _init_ui(self) -> None:
57+
"""Initialize the UI components."""
3558
# Create a frame to hold all the labels and text fields
3659
self.info_frame = ttk.Frame(self.main_frame)
3760
self.info_frame.pack(fill=tk.BOTH, padx=20, pady=20)
3861

3962
# Dynamically create labels and text fields for each attribute
40-
for row_nr, (description, attr_value) in enumerate(flight_controller.info.get_info().items()):
63+
self._create_info_fields()
64+
65+
self.info_frame.columnconfigure(1, weight=1)
66+
67+
def _create_info_fields(self) -> None:
68+
"""Create the information fields in the UI."""
69+
for row_nr, (description, attr_value) in enumerate(self.flight_controller.info.get_info().items()):
4170
label = ttk.Label(self.info_frame, text=f"{description}:")
4271
label.grid(row=row_nr, column=0, sticky="w")
4372

@@ -54,41 +83,79 @@ def __init__(self, flight_controller: FlightController) -> None:
5483
text_field.insert(tk.END, _("N/A")) # Insert "Not Available" if the attribute is missing or empty
5584
text_field.configure(state="readonly")
5685

57-
self.info_frame.columnconfigure(1, weight=1)
58-
59-
logging_info(_("Firmware Version: %s"), flight_controller.info.flight_sw_version_and_type)
60-
logging_info(_("Firmware first 8 hex bytes of the FC git hash: %s"), flight_controller.info.flight_custom_version)
61-
logging_info(_("Firmware first 8 hex bytes of the ChibiOS git hash: %s"), flight_controller.info.os_custom_version)
86+
def _log_flight_controller_info(self) -> None:
87+
"""Log information about the flight controller."""
88+
logging_info(_("Firmware Version: %s"), self.flight_controller.info.flight_sw_version_and_type)
89+
logging_info(_("Firmware first 8 hex bytes of the FC git hash: %s"), self.flight_controller.info.flight_custom_version)
90+
logging_info(
91+
_("Firmware first 8 hex bytes of the ChibiOS git hash: %s"), self.flight_controller.info.os_custom_version
92+
)
6293
logging_info(
6394
_("Flight Controller firmware type: %s (%s)"),
64-
flight_controller.info.firmware_type,
65-
flight_controller.info.apj_board_id,
95+
self.flight_controller.info.firmware_type,
96+
self.flight_controller.info.apj_board_id,
6697
)
67-
logging_info(_("Flight Controller HW / board version: %s"), flight_controller.info.board_version)
68-
logging_info(_("Flight Controller USB vendor ID: %s"), flight_controller.info.vendor)
69-
logging_info(_("Flight Controller USB product ID: %s"), flight_controller.info.product)
98+
logging_info(_("Flight Controller HW / board version: %s"), self.flight_controller.info.board_version)
99+
logging_info(_("Flight Controller USB vendor ID: %s"), self.flight_controller.info.vendor)
100+
logging_info(_("Flight Controller USB product ID: %s"), self.flight_controller.info.product)
70101

71-
self.root.after(50, self.download_flight_controller_parameters()) # type: ignore[func-returns-value]
102+
def _schedule_download_parameters(self) -> None:
103+
"""Schedule the download of parameters and exit when complete."""
104+
try:
105+
self.download_flight_controller_parameters()
106+
self.root.destroy()
107+
except (OSError, ConnectionError, SerialException, PermissionError, ValueError, RuntimeError) as e:
108+
logging_error(_("Failed to download parameters: %s"), str(e))
109+
self.root.destroy()
110+
111+
def run(self) -> None:
112+
"""Run the application main loop."""
72113
self.root.mainloop()
73114

74-
def download_flight_controller_parameters(self) -> None:
75-
"""Download parameters from the flight controller with progress tracking and error handling."""
76-
param_download_progress_window = ProgressWindow(
77-
self.root, _("Downloading FC parameters"), _("Downloaded {} of {} parameters")
78-
)
79-
try:
80-
self.flight_controller.fc_parameters, self.param_default_values = self.flight_controller.download_params(
81-
param_download_progress_window.update_progress_bar
115+
def download_flight_controller_parameters(self, progress_callback: Optional[Callable[[int, int], None]] = None) -> None:
116+
"""
117+
Download parameters from the flight controller with progress tracking and error handling.
118+
119+
Args:
120+
progress_callback: Optional callback function to track progress
121+
122+
Raises:
123+
ConnectionError: If connection to the flight controller fails
124+
SerialException: If there's an error with the serial communication
125+
PermissionError: If there's a permission error when accessing the device
126+
IOError: If there's an I/O error during communication
127+
ValueError: If there's an invalid value during parameter processing
128+
RuntimeError: If any other runtime error occurs during parameter download
129+
130+
"""
131+
if progress_callback:
132+
# Use provided callback directly
133+
try:
134+
self.flight_controller.fc_parameters, self.param_default_values = self.flight_controller.download_params(
135+
progress_callback
136+
)
137+
except (OSError, ConnectionError, SerialException, PermissionError, ValueError, RuntimeError) as e:
138+
logging_error(_("Error downloading flight controller parameters: %s"), str(e))
139+
# Re-raise the exception after logging
140+
raise
141+
else:
142+
# Create progress window and use its update function
143+
param_download_progress_window = ProgressWindow(
144+
self.root, _("Downloading FC parameters"), _("Downloaded {} of {} parameters")
82145
)
83-
except Exception as e:
84-
logging_error(_("Error downloading flight controller parameters: %s"), str(e))
85-
# Make sure to destroy the progress window even on error
86-
param_download_progress_window.destroy()
87-
# Re-raise the exception after cleanup
88-
raise
89-
# Normal cleanup path when no exceptions occur
90-
param_download_progress_window.destroy() # for the case that '--device test' and there is no real FC connected
91-
self.root.destroy()
146+
try:
147+
self.flight_controller.fc_parameters, self.param_default_values = self.flight_controller.download_params(
148+
param_download_progress_window.update_progress_bar
149+
)
150+
except (OSError, ConnectionError, SerialException, PermissionError, ValueError, RuntimeError) as e:
151+
logging_error(_("Error downloading flight controller parameters: %s"), str(e))
152+
# Make sure to destroy the progress window even on error
153+
param_download_progress_window.destroy()
154+
# Re-raise the exception after cleanup
155+
raise
156+
# Normal cleanup path when no exceptions occur
157+
param_download_progress_window.destroy() # for the case that '--device test' and there is no real FC connected
92158

93159
def get_param_default_values(self) -> dict[str, Par]:
160+
"""Get the default parameter values."""
94161
return self.param_default_values

0 commit comments

Comments
 (0)