Skip to content

Commit 0d91a4e

Browse files
committed
WIP
1 parent 89d9e27 commit 0d91a4e

6 files changed

Lines changed: 1724 additions & 689 deletions
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
"""
2+
ArduPilot parameter domain model.
3+
4+
This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
5+
6+
SPDX-FileCopyrightText: 2024-2025 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>
7+
8+
SPDX-License-Identifier: GPL-3.0-or-later
9+
"""
10+
11+
from typing import Any, Optional
12+
13+
from ardupilot_methodic_configurator import _
14+
from ardupilot_methodic_configurator.annotate_params import Par
15+
from ardupilot_methodic_configurator.backend_filesystem import is_within_tolerance
16+
17+
18+
class ArduPilotParameter:
19+
"""Domain model representing an ArduPilot parameter with all its attributes."""
20+
21+
def __init__(
22+
self,
23+
name: str,
24+
par_obj: Par,
25+
metadata: Optional[dict[str, Any]] = None,
26+
default_par: Optional[Par] = None,
27+
fc_value: Optional[float] = None,
28+
is_forced: bool = False,
29+
is_derived: bool = False,
30+
) -> None:
31+
"""
32+
Initialize the parameter with all its attributes.
33+
34+
Args:
35+
name: Name of the parameter
36+
par_obj: Par object containing value and comment
37+
metadata: Dictionary of parameter metadata (from pdef.xml files)
38+
default_par: Default parameter object for comparison
39+
fc_value: Value from the flight controller, if connected
40+
is_forced: Whether this parameter is forced (cannot be edited)
41+
is_derived: Whether this parameter is derived (calculated automatically)
42+
43+
"""
44+
self.name = name
45+
self.value = par_obj.value
46+
self.comment = par_obj.comment
47+
self.metadata = metadata or {}
48+
self.default_value = default_par.value if default_par else None
49+
self.fc_value = fc_value
50+
self.is_forced = is_forced
51+
self.is_derived = is_derived
52+
53+
@property
54+
def is_calibration(self) -> bool:
55+
"""Return True if this is a calibration parameter."""
56+
return self.metadata.get("Calibration", False)
57+
58+
@property
59+
def is_readonly(self) -> bool:
60+
"""Return True if this is a readonly parameter."""
61+
return self.metadata.get("ReadOnly", False)
62+
63+
@property
64+
def is_bitmask(self) -> bool:
65+
"""Return True if this parameter uses a bitmask representation."""
66+
return "Bitmask" in self.metadata
67+
68+
@property
69+
def has_default_value(self) -> bool:
70+
"""Return True if the current value equals the default value."""
71+
return self.default_value is not None and is_within_tolerance(self.value, self.default_value)
72+
73+
@property
74+
def has_fc_value(self) -> bool:
75+
"""Return True if there is a flight controller value for this parameter."""
76+
return self.fc_value is not None
77+
78+
@property
79+
def is_different_from_fc(self) -> bool:
80+
"""Return True if the parameter value is different from the flight controller value."""
81+
return self.fc_value is not None and not is_within_tolerance(self.value, self.fc_value)
82+
83+
@property
84+
def doc_tooltip(self) -> str:
85+
"""Return the documentation tooltip for this parameter."""
86+
return self.metadata.get("doc_tooltip", _("No documentation available in apm.pdef.xml for this parameter"))
87+
88+
@property
89+
def unit(self) -> str:
90+
"""Return the unit of this parameter."""
91+
return self.metadata.get("unit", "")
92+
93+
@property
94+
def unit_tooltip(self) -> str:
95+
"""Return the unit tooltip for this parameter."""
96+
return str(self.metadata.get("unit_tooltip", _("No documentation available in apm.pdef.xml for this parameter")))
97+
98+
@property
99+
def values_dict(self) -> dict:
100+
"""Return the values dictionary for this parameter if it exists."""
101+
return self.metadata.get("values", {}) or {}
102+
103+
@property
104+
def bitmask_dict(self) -> dict:
105+
"""Return the bitmask dictionary for this parameter if it exists."""
106+
return self.metadata.get("Bitmask", {}) or {}
107+
108+
@property
109+
def min_value(self) -> Optional[float]:
110+
"""Return the minimum allowed value for this parameter."""
111+
return self.metadata.get("min", None)
112+
113+
@property
114+
def max_value(self) -> Optional[float]:
115+
"""Return the maximum allowed value for this parameter."""
116+
return self.metadata.get("max", None)
117+
118+
@property
119+
def value_as_string(self) -> str:
120+
"""Return the parameter value formatted as a string."""
121+
return format(self.value, ".6f").rstrip("0").rstrip(".")
122+
123+
@property
124+
def fc_value_as_string(self) -> str:
125+
"""Return the flight controller value formatted as a string."""
126+
if self.fc_value is not None:
127+
return format(self.fc_value, ".6f").rstrip("0").rstrip(".")
128+
return _("N/A")
129+
130+
def is_in_values_dict(self) -> bool:
131+
"""Return True if the current value is in the values dictionary."""
132+
return bool(self.values_dict and self.value_as_string in self.values_dict)
133+
134+
def get_selected_value_from_dict(self) -> Optional[str]:
135+
"""Return the string representation from the values dictionary for the current value."""
136+
if self.is_in_values_dict():
137+
return self.values_dict.get(self.value_as_string, None)
138+
return None
139+
140+
def set_value(self, value: float) -> bool:
141+
"""
142+
Set the parameter value and return whether it changed.
143+
144+
Args:
145+
value: The new value to set
146+
147+
Returns:
148+
True if the value was changed, False otherwise
149+
150+
"""
151+
if self.is_forced or self.is_derived:
152+
return False
153+
154+
if is_within_tolerance(self.value, value):
155+
return False
156+
157+
self.value = value
158+
return True
159+
160+
def set_comment(self, comment: str) -> bool:
161+
"""
162+
Set the parameter comment and return whether it changed.
163+
164+
Args:
165+
comment: The new comment to set
166+
167+
Returns:
168+
True if the comment was changed, False otherwise
169+
170+
"""
171+
if self.is_forced or self.is_derived:
172+
return False
173+
174+
if comment == self.comment or (comment == "" and self.comment is None):
175+
return False
176+
177+
self.comment = comment
178+
return True
179+
180+
def is_editable(self) -> bool:
181+
"""
182+
Check if this parameter is editable.
183+
184+
Returns:
185+
True if the parameter can be edited, False otherwise
186+
187+
"""
188+
return not (self.is_forced or self.is_derived or self.is_readonly)
189+
190+
def is_valid_value(self, value: float) -> bool:
191+
"""
192+
Check if a value is valid for this parameter.
193+
194+
Args:
195+
value: The value to check
196+
197+
Returns:
198+
True if the value is valid, False otherwise
199+
200+
"""
201+
if self.min_value is not None and value < self.min_value:
202+
return False
203+
204+
return not (self.max_value is not None and value > self.max_value)

ardupilot_methodic_configurator/frontend_tkinter_parameter_editor.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
from ardupilot_methodic_configurator.frontend_tkinter_show import show_tooltip
4545
from ardupilot_methodic_configurator.frontend_tkinter_stage_progress import StageProgressBar
4646
from ardupilot_methodic_configurator.frontend_tkinter_usage_popup_window import UsagePopupWindow
47+
from ardupilot_methodic_configurator.parameter_editor_model import ParameterEditorModel
48+
from ardupilot_methodic_configurator.parameter_repository import LocalFilesystemParameterRepository
4749
from ardupilot_methodic_configurator.tempcal_imu import IMUfit
4850

4951

@@ -140,6 +142,10 @@ def __init__(self, current_file: str, flight_controller: FlightController, local
140142
self.flight_controller = flight_controller
141143
self.local_filesystem = local_filesystem
142144

145+
# Initialize the parameter repository and model
146+
self.parameter_repository = LocalFilesystemParameterRepository(local_filesystem)
147+
self.parameter_model = ParameterEditorModel(self.parameter_repository)
148+
143149
self.at_least_one_changed_parameter_written = False
144150
self.file_selection_combobox: AutoResizeCombobox
145151
self.show_only_differences: tk.BooleanVar
@@ -184,6 +190,9 @@ def __init__(self, current_file: str, flight_controller: FlightController, local
184190
self.documentation_frame = DocumentationFrame(self.main_frame, self.local_filesystem, self.current_file)
185191
self.documentation_frame.documentation_frame.pack(side=tk.TOP, fill="x", expand=False, pady=(2, 2), padx=(4, 4))
186192

193+
# Load the initial parameters into the model
194+
self.parameter_model.load_parameters(self.current_file)
195+
187196
self.__create_parameter_area_widgets()
188197

189198
# trigger a table update to ask the user what to do in the case this file needs special actions
@@ -237,7 +246,7 @@ def __create_conf_widgets(self, version: str) -> None:
237246
font_family, _font_size = get_widget_font_family_and_size(file_selection_label)
238247
self.legend_frame(config_subframe, font_family)
239248

240-
image_label = BaseWindow.put_image_in_label(config_frame, LocalFilesystem.application_logo_filepath())
249+
image_label = BaseWindow.put_image_in_label(config_frame, ProgramSettings.application_logo_filepath())
241250
image_label.pack(side=tk.RIGHT, anchor=tk.NE, padx=(4, 4), pady=(4, 0))
242251
image_label.bind("<Button-1>", lambda event: show_about_window(self.main_frame, version)) # noqa: ARG005
243252
show_tooltip(image_label, _("User Manual, Support Forum, Report a Bug, Licenses, Source Code"))
@@ -287,8 +296,20 @@ def __create_parameter_area_widgets(self) -> None:
287296
value=bool(ProgramSettings.get_setting("annotate_docs_into_param_files"))
288297
)
289298

290-
# Create a Scrollable parameter editor table
291-
self.parameter_editor_table = ParameterEditorTable(self.main_frame, self.local_filesystem, self)
299+
# Create a Scrollable parameter editor table connected to our model
300+
self.parameter_editor_table = ParameterEditorTable(
301+
self.main_frame,
302+
self,
303+
self.local_filesystem.param_default_dict,
304+
self.local_filesystem.doc_dict,
305+
self.local_filesystem,
306+
self.current_file,
307+
parameter_model=self.parameter_model,
308+
parameter_upload_callback=self.flight_controller.set_param,
309+
fc_parameters_dict=self.flight_controller.fc_parameters
310+
if hasattr(self.flight_controller, "fc_parameters")
311+
else {},
312+
)
292313
self.repopulate_parameter_table(self.current_file)
293314
self.parameter_editor_table.pack(side="top", fill="both", expand=True)
294315

0 commit comments

Comments
 (0)