Skip to content

Commit 0146509

Browse files
committed
feat(fc info): More FC info tests
1 parent 9a7526c commit 0146509

File tree

4 files changed

+908
-292
lines changed

4 files changed

+908
-292
lines changed

ardupilot_methodic_configurator/backend_flightcontroller_info.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"""
1010

1111
from collections.abc import Sequence
12+
from logging import info as logging_info
1213
from typing import Union
1314

1415
from pymavlink import mavutil
@@ -250,3 +251,21 @@ def __classify_vehicle_type(mav_type_int: int) -> str:
250251

251252
# Return the classified vehicle type based on the MAV_TYPE enum
252253
return mav_type_to_vehicle_type.get(mav_type_int, "")
254+
255+
def log_flight_controller_info(self) -> None:
256+
"""Log flight controller information at INFO level."""
257+
logging_info("Firmware Version: %s", self.flight_sw_version_and_type)
258+
logging_info("Firmware first 8 hex bytes of the FC git hash: %s", self.flight_custom_version)
259+
logging_info("Firmware first 8 hex bytes of the ChibiOS git hash: %s", self.os_custom_version)
260+
logging_info("Flight Controller firmware type: %s (%s)", self.firmware_type, self.apj_board_id)
261+
logging_info("Flight Controller HW / board version: %s", self.board_version)
262+
logging_info("Flight Controller USB vendor ID: %s", self.vendor)
263+
logging_info("Flight Controller USB product ID: %s", self.product)
264+
265+
def format_display_value(self, value: Union[str, dict[str, str], None]) -> str:
266+
"""Format a value for display in the UI."""
267+
if value:
268+
if isinstance(value, dict):
269+
return ", ".join(value.keys())
270+
return str(value)
271+
return _("N/A")

ardupilot_methodic_configurator/frontend_tkinter_flightcontroller_info.py

Lines changed: 83 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@
88
SPDX-License-Identifier: GPL-3.0-or-later
99
"""
1010

11+
import logging
1112
import tkinter as tk
12-
13-
# from logging import debug as logging_debug
14-
from logging import info as logging_info
15-
from tkinter import ttk
13+
from tkinter import messagebox, ttk
14+
from typing import Callable, Optional, Union
1615

1716
from ardupilot_methodic_configurator import _, __version__
1817
from ardupilot_methodic_configurator.annotate_params import Par
@@ -21,64 +20,106 @@
2120
from ardupilot_methodic_configurator.frontend_tkinter_progress_window import ProgressWindow
2221

2322

23+
class FlightControllerInfoPresenter:
24+
"""
25+
Business logic for flight controller information presentation.
26+
27+
Separated from UI for better testability.
28+
"""
29+
30+
def __init__(self, flight_controller: FlightController) -> None:
31+
self.flight_controller = flight_controller
32+
self.param_default_values: dict[str, Par] = {}
33+
34+
def get_info_data(self) -> dict[str, Union[str, dict[str, str]]]:
35+
"""Get formatted flight controller information for display."""
36+
return self.flight_controller.info.get_info()
37+
38+
def log_flight_controller_info(self) -> None:
39+
"""Log flight controller information."""
40+
self.flight_controller.info.log_flight_controller_info()
41+
42+
def download_parameters(self, progress_callback: Optional[Callable[[int, int], None]] = None) -> dict[str, Par]:
43+
"""
44+
Download flight controller parameters.
45+
46+
Args:
47+
progress_callback: Optional callback function for progress updates
48+
49+
Returns:
50+
Dictionary of parameter default values
51+
52+
"""
53+
fc_parameters, param_default_values = self.flight_controller.download_params(progress_callback)
54+
self.flight_controller.fc_parameters = fc_parameters
55+
self.param_default_values = param_default_values
56+
return param_default_values
57+
58+
def get_param_default_values(self) -> dict[str, Par]:
59+
"""Get parameter default values."""
60+
return self.param_default_values
61+
62+
2463
class FlightControllerInfoWindow(BaseWindow):
2564
"""Display flight controller hardware, firmware and parameter information."""
2665

2766
def __init__(self, flight_controller: FlightController) -> None:
2867
super().__init__()
2968
self.root.title(_("ArduPilot methodic configurator ") + __version__ + _(" - Flight Controller Info"))
3069
self.root.geometry("500x350") # Adjust the window size as needed
31-
self.flight_controller = flight_controller
32-
self.param_default_values: dict[str, Par] = {}
3370

71+
self.presenter = FlightControllerInfoPresenter(flight_controller)
72+
73+
self._create_info_display()
74+
self.presenter.log_flight_controller_info()
75+
76+
# Schedule parameter download after UI is ready
77+
self.root.after(50, self._download_flight_controller_parameters)
78+
self.root.mainloop()
79+
80+
def _create_info_display(self) -> None:
81+
"""Create the flight controller information display."""
3482
# Create a frame to hold all the labels and text fields
3583
self.info_frame = ttk.Frame(self.main_frame)
3684
self.info_frame.pack(fill=tk.BOTH, padx=20, pady=20)
3785

3886
# Dynamically create labels and text fields for each attribute
39-
for row_nr, (description, attr_value) in enumerate(flight_controller.info.get_info().items()):
40-
label = ttk.Label(self.info_frame, text=f"{description}:")
41-
label.grid(row=row_nr, column=0, sticky="w")
42-
43-
text_field = ttk.Entry(self.info_frame)
44-
text_field.grid(row=row_nr, column=1, sticky="ew", columnspan=1)
45-
46-
# Check if the attribute exists and has a non-empty value before inserting
47-
if attr_value:
48-
if isinstance(attr_value, dict):
49-
text_field.insert(tk.END, (", ").join(attr_value.keys()))
50-
else:
51-
text_field.insert(tk.END, attr_value)
52-
else:
53-
text_field.insert(tk.END, _("N/A")) # Insert "Not Available" if the attribute is missing or empty
54-
text_field.configure(state="readonly")
87+
info_data = self.presenter.get_info_data()
88+
for row_nr, (description, attr_value) in enumerate(info_data.items()):
89+
self._create_info_row(row_nr, description, attr_value)
5590

5691
self.info_frame.columnconfigure(1, weight=1)
5792

58-
logging_info(_("Firmware Version: %s"), flight_controller.info.flight_sw_version_and_type)
59-
logging_info(_("Firmware first 8 hex bytes of the FC git hash: %s"), flight_controller.info.flight_custom_version)
60-
logging_info(_("Firmware first 8 hex bytes of the ChibiOS git hash: %s"), flight_controller.info.os_custom_version)
61-
logging_info(
62-
_("Flight Controller firmware type: %s (%s)"),
63-
flight_controller.info.firmware_type,
64-
flight_controller.info.apj_board_id,
65-
)
66-
logging_info(_("Flight Controller HW / board version: %s"), flight_controller.info.board_version)
67-
logging_info(_("Flight Controller USB vendor ID: %s"), flight_controller.info.vendor)
68-
logging_info(_("Flight Controller USB product ID: %s"), flight_controller.info.product)
93+
def _create_info_row(self, row_nr: int, description: str, attr_value: Union[str, dict[str, str]]) -> None:
94+
"""Create a single row of information display."""
95+
label = ttk.Label(self.info_frame, text=f"{description}:")
96+
label.grid(row=row_nr, column=0, sticky="w")
6997

70-
self.root.after(50, self.download_flight_controller_parameters()) # type: ignore[func-returns-value]
71-
self.root.mainloop()
98+
text_field = ttk.Entry(self.info_frame)
99+
text_field.grid(row=row_nr, column=1, sticky="ew", columnspan=1)
100+
101+
# Format the value for display using the backend logic
102+
display_value = self.presenter.flight_controller.info.format_display_value(attr_value)
103+
text_field.insert(tk.END, display_value)
104+
text_field.configure(state="readonly")
72105

73-
def download_flight_controller_parameters(self) -> None:
106+
def _download_flight_controller_parameters(self) -> None:
107+
"""Download flight controller parameters with progress feedback."""
74108
param_download_progress_window = ProgressWindow(
75109
self.root, _("Downloading FC parameters"), _("Downloaded {} of {} parameters")
76110
)
77-
self.flight_controller.fc_parameters, self.param_default_values = self.flight_controller.download_params(
78-
param_download_progress_window.update_progress_bar
79-
)
80-
param_download_progress_window.destroy() # for the case that '--device test' and there is no real FC connected
81-
self.root.destroy()
111+
112+
try:
113+
self.presenter.download_parameters(param_download_progress_window.update_progress_bar)
114+
except Exception as e: # pylint: disable=broad-exception-caught
115+
# Log the error
116+
logging.error("Failed to download parameters: %s", e)
117+
# Show an error message to the user
118+
messagebox.showerror(_("Error"), f"{_('Failed to download parameters')}: {e}")
119+
finally:
120+
param_download_progress_window.destroy() # for the case that '--device test' and there is no real FC connected
121+
self.root.destroy()
82122

83123
def get_param_default_values(self) -> dict[str, Par]:
84-
return self.param_default_values
124+
"""Get parameter default values from the presenter."""
125+
return self.presenter.get_param_default_values()

0 commit comments

Comments
 (0)