ardupilot-methodic-configurator 2.6.0__py3-none-any.whl → 2.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ardupilot-methodic-configurator might be problematic. Click here for more details.
- ardupilot_methodic_configurator/__init__.py +2 -2
- ardupilot_methodic_configurator/__main__.py +25 -1
- ardupilot_methodic_configurator/annotate_params.py +49 -14
- ardupilot_methodic_configurator/argparse_check_range.py +1 -1
- ardupilot_methodic_configurator/backend_filesystem.py +7 -3
- ardupilot_methodic_configurator/backend_filesystem_configuration_steps.py +53 -6
- ardupilot_methodic_configurator/backend_filesystem_json_with_schema.py +3 -3
- ardupilot_methodic_configurator/backend_filesystem_program_settings.py +145 -8
- ardupilot_methodic_configurator/backend_filesystem_vehicle_components.py +3 -3
- ardupilot_methodic_configurator/backend_flightcontroller.py +37 -20
- ardupilot_methodic_configurator/backend_flightcontroller_info.py +1 -1
- ardupilot_methodic_configurator/backend_internet.py +1 -1
- ardupilot_methodic_configurator/backend_mavftp.py +1 -1
- ardupilot_methodic_configurator/battery_cell_voltages.py +1 -1
- ardupilot_methodic_configurator/common_arguments.py +1 -1
- ardupilot_methodic_configurator/configuration_manager.py +450 -115
- ardupilot_methodic_configurator/configuration_steps_ArduCopter.json +6 -4
- ardupilot_methodic_configurator/configuration_steps_ArduPlane.json +3 -1
- ardupilot_methodic_configurator/configuration_steps_Heli.json +3 -1
- ardupilot_methodic_configurator/configuration_steps_Rover.json +3 -1
- ardupilot_methodic_configurator/configuration_steps_strings.py +5 -3
- ardupilot_methodic_configurator/data_model_ardupilot_parameter.py +55 -2
- ardupilot_methodic_configurator/data_model_configuration_step.py +96 -46
- ardupilot_methodic_configurator/data_model_fc_ids.py +16 -7
- ardupilot_methodic_configurator/data_model_motor_test.py +1 -1
- ardupilot_methodic_configurator/data_model_par_dict.py +25 -11
- ardupilot_methodic_configurator/data_model_software_updates.py +1 -1
- ardupilot_methodic_configurator/data_model_template_overview.py +1 -1
- ardupilot_methodic_configurator/data_model_vehicle_components.py +1 -1
- ardupilot_methodic_configurator/data_model_vehicle_components_base.py +3 -2
- ardupilot_methodic_configurator/data_model_vehicle_components_display.py +1 -1
- ardupilot_methodic_configurator/data_model_vehicle_components_import.py +2 -1
- ardupilot_methodic_configurator/data_model_vehicle_components_json_schema.py +1 -1
- ardupilot_methodic_configurator/data_model_vehicle_components_templates.py +1 -1
- ardupilot_methodic_configurator/data_model_vehicle_components_validation.py +41 -1
- ardupilot_methodic_configurator/data_model_vehicle_project.py +1 -1
- ardupilot_methodic_configurator/data_model_vehicle_project_creator.py +1 -1
- ardupilot_methodic_configurator/data_model_vehicle_project_opener.py +1 -1
- ardupilot_methodic_configurator/extract_param_defaults.py +1 -1
- ardupilot_methodic_configurator/frontend_tkinter_autoresize_combobox.py +1 -1
- ardupilot_methodic_configurator/frontend_tkinter_base_window.py +5 -1
- ardupilot_methodic_configurator/frontend_tkinter_component_editor.py +55 -29
- ardupilot_methodic_configurator/frontend_tkinter_component_editor_base.py +18 -13
- ardupilot_methodic_configurator/frontend_tkinter_component_template_manager.py +1 -1
- ardupilot_methodic_configurator/frontend_tkinter_connection_selection.py +1 -1
- ardupilot_methodic_configurator/frontend_tkinter_directory_selection.py +1 -1
- ardupilot_methodic_configurator/frontend_tkinter_entry_dynamic.py +1 -1
- ardupilot_methodic_configurator/frontend_tkinter_flightcontroller_info.py +1 -1
- ardupilot_methodic_configurator/frontend_tkinter_font.py +1 -1
- ardupilot_methodic_configurator/frontend_tkinter_motor_test.py +1 -1
- ardupilot_methodic_configurator/frontend_tkinter_pair_tuple_combobox.py +7 -1
- ardupilot_methodic_configurator/frontend_tkinter_parameter_editor.py +50 -102
- ardupilot_methodic_configurator/frontend_tkinter_parameter_editor_documentation_frame.py +24 -58
- ardupilot_methodic_configurator/frontend_tkinter_parameter_editor_table.py +24 -56
- ardupilot_methodic_configurator/frontend_tkinter_progress_window.py +1 -1
- ardupilot_methodic_configurator/frontend_tkinter_project_creator.py +1 -1
- ardupilot_methodic_configurator/frontend_tkinter_project_opener.py +1 -1
- ardupilot_methodic_configurator/frontend_tkinter_rich_text.py +1 -1
- ardupilot_methodic_configurator/frontend_tkinter_scroll_frame.py +1 -1
- ardupilot_methodic_configurator/frontend_tkinter_show.py +1 -1
- ardupilot_methodic_configurator/frontend_tkinter_software_update.py +1 -1
- ardupilot_methodic_configurator/frontend_tkinter_stage_progress.py +19 -29
- ardupilot_methodic_configurator/frontend_tkinter_template_overview.py +1 -1
- ardupilot_methodic_configurator/frontend_tkinter_usage_popup_window.py +1 -1
- ardupilot_methodic_configurator/internationalization.py +1 -1
- ardupilot_methodic_configurator/param_pid_adjustment_update.py +43 -39
- ardupilot_methodic_configurator/tempcal_imu.py +1 -1
- ardupilot_methodic_configurator/vehicle_components.py +1 -1
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/AirCar_v1/14_logging.param +3 -3
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Big_Owl/14_logging.param +3 -3
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Chimera7/14_logging.param +3 -3
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/FETtec-5/14_logging.param +3 -3
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/GazeboIrisWithTargetFollow/14_logging.param +3 -3
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X500/14_logging.param +3 -3
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X500_V2/14_logging.param +3 -3
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X650_LTE/14_logging.param +3 -3
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Hoverit_X11+/14_logging.param +3 -3
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Hoverit_X13/14_logging.param +3 -3
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Marmotte5v2/14_logging.param +3 -3
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/ReadyToSkyZD550/14_logging.param +3 -3
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/TarotFY680Hexacopter/05_remote_controller.param +1 -1
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/TarotFY680Hexacopter/14_logging.param +3 -3
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/TarotFY680Hexacopter/47_position_controller.param +2 -2
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Tarot_X4/14_logging.param +3 -3
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/X11_plus/14_logging.param +3 -3
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.3.8-params/14_logging.param +3 -3
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.4.4-params/14_logging.param +3 -3
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.5.x-params/14_logging.param +3 -3
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.6.x-params/14_logging.param +3 -3
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/empty_4.5.x/10_gnss.param +1 -1
- ardupilot_methodic_configurator/vehicle_templates/ArduCopter/empty_4.6.x/10_gnss.param +1 -1
- {ardupilot_methodic_configurator-2.6.0.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/METADATA +11 -6
- {ardupilot_methodic_configurator-2.6.0.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/RECORD +106 -106
- {ardupilot_methodic_configurator-2.6.0.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/WHEEL +0 -0
- {ardupilot_methodic_configurator-2.6.0.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/entry_points.txt +0 -0
- {ardupilot_methodic_configurator-2.6.0.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSE.md +0 -0
- {ardupilot_methodic_configurator-2.6.0.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/Apache-2.0.txt +0 -0
- {ardupilot_methodic_configurator-2.6.0.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/BSD-3-Clause.txt +0 -0
- {ardupilot_methodic_configurator-2.6.0.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/GPL-3.0-or-later.txt +0 -0
- {ardupilot_methodic_configurator-2.6.0.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/LGPL-3.0-or-later.txt +0 -0
- {ardupilot_methodic_configurator-2.6.0.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/MIT-CMU.txt +0 -0
- {ardupilot_methodic_configurator-2.6.0.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/MIT.txt +0 -0
- {ardupilot_methodic_configurator-2.6.0.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/MPL-2.0.txt +0 -0
- {ardupilot_methodic_configurator-2.6.0.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/PSF-2.0.txt +0 -0
- {ardupilot_methodic_configurator-2.6.0.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/credits/CREDITS.md +0 -0
- {ardupilot_methodic_configurator-2.6.0.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/top_level.txt +0 -0
|
@@ -5,7 +5,7 @@ Contains state information but no GUI code.
|
|
|
5
5
|
Aggregates flight controller and filesystem access in a single interface.
|
|
6
6
|
Uses exceptions for error handling, the GUI layer will catch and display them.
|
|
7
7
|
|
|
8
|
-
This file is part of
|
|
8
|
+
This file is part of ArduPilot Methodic Configurator. https://github.com/ArduPilot/MethodicConfigurator
|
|
9
9
|
|
|
10
10
|
SPDX-FileCopyrightText: 2024-2025 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>
|
|
11
11
|
|
|
@@ -17,9 +17,11 @@ from logging import error as logging_error
|
|
|
17
17
|
from logging import info as logging_info
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from typing import Callable, Optional
|
|
20
|
+
from webbrowser import open as webbrowser_open # to open the web documentation
|
|
20
21
|
|
|
21
22
|
from ardupilot_methodic_configurator import _
|
|
22
23
|
from ardupilot_methodic_configurator.backend_filesystem import LocalFilesystem
|
|
24
|
+
from ardupilot_methodic_configurator.backend_filesystem_configuration_steps import PhaseData
|
|
23
25
|
from ardupilot_methodic_configurator.backend_flightcontroller import FlightController
|
|
24
26
|
from ardupilot_methodic_configurator.backend_internet import download_file_from_url
|
|
25
27
|
from ardupilot_methodic_configurator.data_model_ardupilot_parameter import ArduPilotParameter
|
|
@@ -33,6 +35,7 @@ SelectFileCallback = Callable[[str, list[str]], Optional[str]] # (title, filety
|
|
|
33
35
|
ShowWarningCallback = Callable[[str, str], None] # (title, message) -> None
|
|
34
36
|
ShowErrorCallback = Callable[[str, str], None] # (title, message) -> None
|
|
35
37
|
ShowInfoCallback = Callable[[str, str], None] # (title, message) -> None
|
|
38
|
+
AskRetryCancelCallback = Callable[[str, str], bool] # (title, message) -> bool
|
|
36
39
|
|
|
37
40
|
|
|
38
41
|
class OperationNotPossibleError(Exception):
|
|
@@ -46,57 +49,62 @@ class InvalidParameterNameError(Exception):
|
|
|
46
49
|
# pylint: disable=too-many-lines
|
|
47
50
|
|
|
48
51
|
|
|
49
|
-
class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
52
|
+
class ConfigurationManager: # pylint: disable=too-many-public-methods, too-many-instance-attributes
|
|
50
53
|
"""
|
|
51
54
|
Manages configuration state, including flight controller and filesystem access.
|
|
52
55
|
|
|
53
56
|
This class aggregates the flight controller and filesystem access to provide a unified interface
|
|
54
|
-
for managing configuration state. It holds references to the flight controller and filesystem,
|
|
57
|
+
for managing configuration state. It holds protected references to the flight controller and filesystem,
|
|
55
58
|
and provides methods to interact with them.
|
|
56
59
|
"""
|
|
57
60
|
|
|
58
61
|
def __init__(self, current_file: str, flight_controller: FlightController, filesystem: LocalFilesystem) -> None:
|
|
59
62
|
self.current_file = current_file
|
|
60
|
-
self.
|
|
61
|
-
self.
|
|
62
|
-
self.
|
|
63
|
+
self._flight_controller = flight_controller
|
|
64
|
+
self._local_filesystem = filesystem
|
|
65
|
+
self._config_step_processor = ConfigurationStepProcessor(self._local_filesystem)
|
|
63
66
|
|
|
64
|
-
# self.
|
|
67
|
+
# self.current_step_parameters is rebuilt on every repopulate(...) call and only contains the ArduPilotParameter
|
|
65
68
|
# objects needed for the current table view.
|
|
66
|
-
self.
|
|
69
|
+
self.current_step_parameters: dict[str, ArduPilotParameter] = {}
|
|
67
70
|
|
|
71
|
+
# Track parameters added by user (not in original file) or renamed by the system in the current configuration step
|
|
72
|
+
self._added_parameters: set[str] = set()
|
|
73
|
+
|
|
74
|
+
# Track parameters deleted by user (were in original file) or renamed by the system in the current configuration step
|
|
75
|
+
self._deleted_parameters: set[str] = set()
|
|
76
|
+
|
|
77
|
+
self._at_least_one_changed = False
|
|
78
|
+
|
|
79
|
+
# frontend_tkinter_parameter_editor_table.py API start
|
|
68
80
|
@property
|
|
69
81
|
def connected_vehicle_type(self) -> str:
|
|
70
82
|
return (
|
|
71
|
-
getattr(self.
|
|
72
|
-
if hasattr(self.
|
|
83
|
+
getattr(self._flight_controller.info, "vehicle_type", "")
|
|
84
|
+
if hasattr(self._flight_controller, "info") and self._flight_controller.info is not None
|
|
73
85
|
else ""
|
|
74
86
|
)
|
|
75
87
|
|
|
76
88
|
@property
|
|
77
89
|
def is_fc_connected(self) -> bool:
|
|
78
|
-
return self.
|
|
90
|
+
return self._flight_controller.master is not None
|
|
79
91
|
|
|
80
92
|
@property
|
|
81
93
|
def fc_parameters(self) -> dict[str, float]:
|
|
82
94
|
return (
|
|
83
|
-
self.
|
|
84
|
-
if hasattr(self.
|
|
95
|
+
self._flight_controller.fc_parameters
|
|
96
|
+
if hasattr(self._flight_controller, "fc_parameters") and self._flight_controller.fc_parameters is not None
|
|
85
97
|
else {}
|
|
86
98
|
)
|
|
87
99
|
|
|
88
100
|
@property
|
|
89
101
|
def is_mavftp_supported(self) -> bool:
|
|
90
102
|
return (
|
|
91
|
-
getattr(self.
|
|
92
|
-
if hasattr(self.
|
|
103
|
+
getattr(self._flight_controller.info, "is_mavftp_supported", False)
|
|
104
|
+
if hasattr(self._flight_controller, "info") and self._flight_controller.info is not None
|
|
93
105
|
else False
|
|
94
106
|
)
|
|
95
107
|
|
|
96
|
-
@property
|
|
97
|
-
def current_file_parameters(self) -> ParDict:
|
|
98
|
-
return self.filesystem.file_parameters.get(self.current_file, ParDict())
|
|
99
|
-
|
|
100
108
|
def handle_imu_temperature_calibration_workflow( # pylint: disable=too-many-arguments, too-many-positional-arguments
|
|
101
109
|
self,
|
|
102
110
|
selected_file: str,
|
|
@@ -126,7 +134,9 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
126
134
|
|
|
127
135
|
"""
|
|
128
136
|
# Check if IMU temperature calibration should be offered for this file
|
|
129
|
-
tempcal_imu_result_param_filename, tempcal_imu_result_param_fullpath =
|
|
137
|
+
tempcal_imu_result_param_filename, tempcal_imu_result_param_fullpath = (
|
|
138
|
+
self._local_filesystem.tempcal_imu_result_param_tuple()
|
|
139
|
+
)
|
|
130
140
|
if selected_file != tempcal_imu_result_param_filename:
|
|
131
141
|
return False
|
|
132
142
|
|
|
@@ -161,13 +171,13 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
161
171
|
log_parm=False,
|
|
162
172
|
online=False,
|
|
163
173
|
tclr=False,
|
|
164
|
-
figpath=self.
|
|
174
|
+
figpath=self._local_filesystem.vehicle_dir,
|
|
165
175
|
progress_callback=progress_callback,
|
|
166
176
|
)
|
|
167
177
|
|
|
168
178
|
try:
|
|
169
179
|
# Reload parameter files after calibration
|
|
170
|
-
self.
|
|
180
|
+
self._local_filesystem.file_parameters = self._local_filesystem.read_params_from_files()
|
|
171
181
|
return True
|
|
172
182
|
except SystemExit as exp:
|
|
173
183
|
show_error(_("Fatal error reading parameter files"), f"{exp}")
|
|
@@ -186,13 +196,13 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
186
196
|
auto_changed_by contains the tool name that requires external changes.
|
|
187
197
|
|
|
188
198
|
"""
|
|
189
|
-
auto_changed_by = self.
|
|
190
|
-
if auto_changed_by and self.
|
|
199
|
+
auto_changed_by = self._local_filesystem.auto_changed_by(selected_file)
|
|
200
|
+
if auto_changed_by and self._flight_controller.fc_parameters:
|
|
191
201
|
# Filter relevant FC parameters for this file
|
|
192
202
|
relevant_fc_params = {
|
|
193
203
|
key: value
|
|
194
|
-
for key, value in self.
|
|
195
|
-
if key in self.
|
|
204
|
+
for key, value in self._flight_controller.fc_parameters.items()
|
|
205
|
+
if key in self.current_step_parameters
|
|
196
206
|
}
|
|
197
207
|
return True, relevant_fc_params, auto_changed_by
|
|
198
208
|
return False, None, auto_changed_by
|
|
@@ -209,7 +219,7 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
209
219
|
bool: True if parameters were copied successfully.
|
|
210
220
|
|
|
211
221
|
"""
|
|
212
|
-
params_copied = self.
|
|
222
|
+
params_copied = self._local_filesystem.copy_fc_values_to_file(selected_file, relevant_fc_params)
|
|
213
223
|
return bool(params_copied)
|
|
214
224
|
|
|
215
225
|
def get_file_jump_options(self, selected_file: str) -> dict[str, str]:
|
|
@@ -223,7 +233,7 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
223
233
|
dict: Dictionary mapping destination files to their messages.
|
|
224
234
|
|
|
225
235
|
"""
|
|
226
|
-
return self.
|
|
236
|
+
return self._local_filesystem.jump_possible(selected_file)
|
|
227
237
|
|
|
228
238
|
def should_download_file_from_url_workflow(
|
|
229
239
|
self,
|
|
@@ -246,11 +256,11 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
246
256
|
bool: True if download was successful or not needed, False if download failed.
|
|
247
257
|
|
|
248
258
|
"""
|
|
249
|
-
url, local_filename = self.
|
|
259
|
+
url, local_filename = self._local_filesystem.get_download_url_and_local_filename(selected_file)
|
|
250
260
|
if not url or not local_filename:
|
|
251
261
|
return True # No download required
|
|
252
262
|
|
|
253
|
-
if self.
|
|
263
|
+
if self._local_filesystem.vehicle_configuration_file_exists(local_filename):
|
|
254
264
|
return True # File already exists in the vehicle directory, no need to download it
|
|
255
265
|
|
|
256
266
|
# Ask user for confirmation
|
|
@@ -291,16 +301,16 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
291
301
|
bool: True if upload was successful or not needed, False if upload failed.
|
|
292
302
|
|
|
293
303
|
"""
|
|
294
|
-
local_filename, remote_filename = self.
|
|
304
|
+
local_filename, remote_filename = self._local_filesystem.get_upload_local_and_remote_filenames(selected_file)
|
|
295
305
|
if not local_filename or not remote_filename:
|
|
296
306
|
return True # No upload required
|
|
297
307
|
|
|
298
|
-
if not self.
|
|
308
|
+
if not self._local_filesystem.vehicle_configuration_file_exists(local_filename):
|
|
299
309
|
error_msg = _("Local file {local_filename} does not exist")
|
|
300
310
|
show_error(_("Will not upload any file"), error_msg.format(local_filename=local_filename))
|
|
301
311
|
return False
|
|
302
312
|
|
|
303
|
-
if self.
|
|
313
|
+
if self._flight_controller.master is None:
|
|
304
314
|
show_warning(_("Will not upload any file"), _("No flight controller connection"))
|
|
305
315
|
return False
|
|
306
316
|
|
|
@@ -312,7 +322,7 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
312
322
|
return True # User declined upload
|
|
313
323
|
|
|
314
324
|
# Attempt upload
|
|
315
|
-
if not self.
|
|
325
|
+
if not self._flight_controller.upload_file(local_filename, remote_filename, progress_callback):
|
|
316
326
|
error_msg = _("Failed to upload {local_filename} to {remote_filename}, please upload it manually")
|
|
317
327
|
show_error(_("Upload failed"), error_msg.format(local_filename=local_filename, remote_filename=remote_filename))
|
|
318
328
|
return False
|
|
@@ -331,18 +341,18 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
331
341
|
|
|
332
342
|
"""
|
|
333
343
|
# Download all parameters from the flight controller
|
|
334
|
-
fc_parameters, param_default_values = self.
|
|
344
|
+
fc_parameters, param_default_values = self._flight_controller.download_params(
|
|
335
345
|
progress_callback,
|
|
336
|
-
Path(self.
|
|
337
|
-
Path(self.
|
|
346
|
+
Path(self._local_filesystem.vehicle_dir) / "complete.param",
|
|
347
|
+
Path(self._local_filesystem.vehicle_dir) / "00_default.param",
|
|
338
348
|
)
|
|
339
349
|
|
|
340
350
|
# Update the flight controller parameters
|
|
341
|
-
self.
|
|
351
|
+
self._flight_controller.fc_parameters = fc_parameters
|
|
342
352
|
|
|
343
353
|
# Write default values to file if available
|
|
344
354
|
if param_default_values:
|
|
345
|
-
self.
|
|
355
|
+
self._local_filesystem.write_param_default_values_to_file(param_default_values)
|
|
346
356
|
|
|
347
357
|
return fc_parameters, param_default_values
|
|
348
358
|
|
|
@@ -373,17 +383,17 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
373
383
|
# Write each selected parameter to the flight controller
|
|
374
384
|
for param_name, param in selected_params.items():
|
|
375
385
|
try:
|
|
376
|
-
if param_name not in self.
|
|
377
|
-
self.
|
|
386
|
+
if param_name not in self._flight_controller.fc_parameters or not is_within_tolerance(
|
|
387
|
+
self._flight_controller.fc_parameters[param_name], param.value
|
|
378
388
|
):
|
|
379
|
-
param_metadata = self.
|
|
389
|
+
param_metadata = self._local_filesystem.doc_dict.get(param_name, None)
|
|
380
390
|
if param_metadata and param_metadata.get("RebootRequired", False):
|
|
381
|
-
self.
|
|
382
|
-
if param_name in self.
|
|
391
|
+
self._flight_controller.set_param(param_name, float(param.value))
|
|
392
|
+
if param_name in self._flight_controller.fc_parameters:
|
|
383
393
|
logging_info(
|
|
384
394
|
_("Parameter %s changed from %f to %f, reset required"),
|
|
385
395
|
param_name,
|
|
386
|
-
self.
|
|
396
|
+
self._flight_controller.fc_parameters[param_name],
|
|
387
397
|
param.value,
|
|
388
398
|
)
|
|
389
399
|
else:
|
|
@@ -391,12 +401,12 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
391
401
|
reset_required = True
|
|
392
402
|
# Check if any of the selected parameters have a _TYPE, _EN, or _ENABLE suffix
|
|
393
403
|
elif param_name.endswith(("_TYPE", "_EN", "_ENABLE", "SID_AXIS")):
|
|
394
|
-
self.
|
|
395
|
-
if param_name in self.
|
|
404
|
+
self._flight_controller.set_param(param_name, float(param.value))
|
|
405
|
+
if param_name in self._flight_controller.fc_parameters:
|
|
396
406
|
logging_info(
|
|
397
407
|
_("Parameter %s changed from %f to %f, possible reset required"),
|
|
398
408
|
param_name,
|
|
399
|
-
self.
|
|
409
|
+
self._flight_controller.fc_parameters[param_name],
|
|
400
410
|
param.value,
|
|
401
411
|
)
|
|
402
412
|
else:
|
|
@@ -423,10 +433,13 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
423
433
|
int: Extra sleep time in seconds.
|
|
424
434
|
|
|
425
435
|
"""
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
436
|
+
param_boot_delay = (
|
|
437
|
+
self.current_step_parameters["BRD_BOOT_DELAY"].get_new_value()
|
|
438
|
+
if "BRD_BOOT_DELAY" in self.current_step_parameters
|
|
439
|
+
else 0.0
|
|
440
|
+
)
|
|
441
|
+
flightcontroller_boot_delay = self._flight_controller.fc_parameters.get("BRD_BOOT_DELAY", 0)
|
|
442
|
+
return int(max(param_boot_delay, flightcontroller_boot_delay) // 1000 + 1) # round up
|
|
430
443
|
|
|
431
444
|
def _reset_and_reconnect_flight_controller(
|
|
432
445
|
self, progress_callback: Optional[Callable] = None, sleep_time: Optional[int] = None
|
|
@@ -446,7 +459,7 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
446
459
|
sleep_time = self._calculate_reset_time()
|
|
447
460
|
|
|
448
461
|
# Call reset_and_reconnect with a callback to update the reset progress bar and the progress message
|
|
449
|
-
return self.
|
|
462
|
+
return self._flight_controller.reset_and_reconnect(progress_callback, None, int(sleep_time))
|
|
450
463
|
|
|
451
464
|
def reset_and_reconnect_workflow( # pylint: disable=too-many-arguments, too-many-positional-arguments
|
|
452
465
|
self,
|
|
@@ -492,7 +505,7 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
492
505
|
|
|
493
506
|
return True # No reset needed
|
|
494
507
|
|
|
495
|
-
def
|
|
508
|
+
def _upload_parameters_to_fc(self, selected_params: dict, show_error: Callable[[str, str], None]) -> int:
|
|
496
509
|
"""
|
|
497
510
|
Upload selected parameters to flight controller.
|
|
498
511
|
|
|
@@ -510,15 +523,15 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
510
523
|
|
|
511
524
|
for param_name, param in selected_params.items():
|
|
512
525
|
try:
|
|
513
|
-
self.
|
|
514
|
-
if param_name not in self.
|
|
515
|
-
self.
|
|
526
|
+
self._flight_controller.set_param(param_name, param.value)
|
|
527
|
+
if param_name not in self._flight_controller.fc_parameters or not is_within_tolerance(
|
|
528
|
+
self._flight_controller.fc_parameters[param_name], param.value
|
|
516
529
|
):
|
|
517
|
-
if param_name in self.
|
|
530
|
+
if param_name in self._flight_controller.fc_parameters:
|
|
518
531
|
logging_info(
|
|
519
532
|
_("Parameter %s changed from %f to %f"),
|
|
520
533
|
param_name,
|
|
521
|
-
self.
|
|
534
|
+
self._flight_controller.fc_parameters[param_name],
|
|
522
535
|
param.value,
|
|
523
536
|
)
|
|
524
537
|
else:
|
|
@@ -593,7 +606,7 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
593
606
|
"39_autotune_roll_pitch_results.param",
|
|
594
607
|
]
|
|
595
608
|
|
|
596
|
-
report_file_path = Path(getattr(self.
|
|
609
|
+
report_file_path = Path(getattr(self._local_filesystem, "vehicle_dir", ".")) / "tuning_report.csv"
|
|
597
610
|
|
|
598
611
|
# Write a CSV with a header ("param", <list of files>) and one row per parameter.
|
|
599
612
|
with open(report_file_path, "w", newline="", encoding="utf-8") as file:
|
|
@@ -605,34 +618,107 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
605
618
|
for param_file in report_files:
|
|
606
619
|
try:
|
|
607
620
|
if param_file == "00_default.param":
|
|
608
|
-
value = str(self.
|
|
621
|
+
value = str(self._local_filesystem.param_default_dict[param_name].value)
|
|
609
622
|
else:
|
|
610
|
-
value = str(self.
|
|
623
|
+
value = str(self._local_filesystem.file_parameters[param_file][param_name].value)
|
|
611
624
|
except (KeyError, ValueError):
|
|
612
625
|
# On any unexpected structure, leave the value empty (don't crash)
|
|
613
626
|
value = ""
|
|
614
627
|
row.append(value)
|
|
615
628
|
writer.writerow(row)
|
|
616
629
|
|
|
617
|
-
def
|
|
630
|
+
def upload_selected_params_workflow( # pylint: disable=too-many-arguments, too-many-positional-arguments
|
|
631
|
+
self,
|
|
632
|
+
selected_params: dict,
|
|
633
|
+
ask_confirmation: AskConfirmationCallback,
|
|
634
|
+
ask_retry_cancel: AskRetryCancelCallback,
|
|
635
|
+
show_error: ShowErrorCallback,
|
|
636
|
+
progress_callback_for_reset: Optional[Callable] = None,
|
|
637
|
+
progress_callback_for_download: Optional[Callable] = None,
|
|
638
|
+
) -> None:
|
|
639
|
+
"""
|
|
640
|
+
Complete workflow for uploading selected parameters, including reset, upload, validation, and retry.
|
|
641
|
+
|
|
642
|
+
Args:
|
|
643
|
+
selected_params: Dictionary of parameters to upload.
|
|
644
|
+
ask_confirmation: Callback to ask user for confirmation.
|
|
645
|
+
ask_retry_cancel: Callback to ask user to retry or cancel on upload error.
|
|
646
|
+
show_error: Callback to show error messages.
|
|
647
|
+
progress_callback_for_reset: Optional callback for reset progress.
|
|
648
|
+
progress_callback_for_download: Optional callback for download progress.
|
|
649
|
+
|
|
650
|
+
"""
|
|
651
|
+
logging_info(
|
|
652
|
+
_("Uploading %d selected %s parameters to flight controller..."),
|
|
653
|
+
len(selected_params),
|
|
654
|
+
self.current_file,
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
# Upload parameters that require reset
|
|
658
|
+
reset_happened = self.upload_parameters_that_require_reset_workflow(
|
|
659
|
+
selected_params,
|
|
660
|
+
ask_confirmation,
|
|
661
|
+
show_error,
|
|
662
|
+
progress_callback_for_reset,
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
# Upload the selected parameters
|
|
666
|
+
nr_changed = self._upload_parameters_to_fc(selected_params, show_error)
|
|
667
|
+
|
|
668
|
+
if reset_happened or nr_changed > 0:
|
|
669
|
+
self._at_least_one_changed = True
|
|
670
|
+
|
|
671
|
+
if self._at_least_one_changed:
|
|
672
|
+
# Re-download all parameters to validate
|
|
673
|
+
self.download_flight_controller_parameters(progress_callback_for_download)
|
|
674
|
+
param_upload_error = self._validate_uploaded_parameters(selected_params)
|
|
675
|
+
|
|
676
|
+
if param_upload_error:
|
|
677
|
+
if ask_retry_cancel(
|
|
678
|
+
_("Parameter upload error"),
|
|
679
|
+
_("Failed to upload the following parameters to the flight controller:\n")
|
|
680
|
+
+ f"{(', ').join(param_upload_error)}",
|
|
681
|
+
):
|
|
682
|
+
# Retry the entire workflow
|
|
683
|
+
self.upload_selected_params_workflow(
|
|
684
|
+
selected_params,
|
|
685
|
+
ask_confirmation,
|
|
686
|
+
ask_retry_cancel,
|
|
687
|
+
show_error,
|
|
688
|
+
progress_callback_for_reset,
|
|
689
|
+
progress_callback_for_download,
|
|
690
|
+
)
|
|
691
|
+
# If not retrying, continue without success message
|
|
692
|
+
else:
|
|
693
|
+
logging_info(_("All parameters uploaded to the flight controller successfully"))
|
|
694
|
+
|
|
695
|
+
self._export_fc_params_missing_or_different()
|
|
696
|
+
|
|
697
|
+
self._write_current_file()
|
|
698
|
+
self._at_least_one_changed = False
|
|
699
|
+
|
|
700
|
+
# frontend_tkinter_parameter_editor_table.py API end
|
|
701
|
+
|
|
702
|
+
# frontend_tkinter_parameter_editor.py API start
|
|
703
|
+
def _validate_uploaded_parameters(self, selected_params: dict) -> list[str]:
|
|
618
704
|
logging_info(_("Re-downloaded all parameters from the flight controller"))
|
|
619
705
|
|
|
620
706
|
# Validate that the read parameters are the same as the ones in the current_file
|
|
621
707
|
param_upload_error = []
|
|
622
708
|
for param_name, param in selected_params.items():
|
|
623
709
|
if (
|
|
624
|
-
param_name in self.
|
|
710
|
+
param_name in self._flight_controller.fc_parameters
|
|
625
711
|
and param is not None
|
|
626
|
-
and not is_within_tolerance(self.
|
|
712
|
+
and not is_within_tolerance(self._flight_controller.fc_parameters[param_name], float(param.value))
|
|
627
713
|
):
|
|
628
714
|
logging_error(
|
|
629
715
|
_("Parameter %s upload to the flight controller failed. Expected: %f, Actual: %f"),
|
|
630
716
|
param_name,
|
|
631
717
|
param.value,
|
|
632
|
-
self.
|
|
718
|
+
self._flight_controller.fc_parameters[param_name],
|
|
633
719
|
)
|
|
634
720
|
param_upload_error.append(param_name)
|
|
635
|
-
if param_name not in self.
|
|
721
|
+
if param_name not in self._flight_controller.fc_parameters:
|
|
636
722
|
logging_error(
|
|
637
723
|
_("Parameter %s upload to the flight controller failed. Expected: %f, Actual: N/A"),
|
|
638
724
|
param_name,
|
|
@@ -650,18 +736,20 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
650
736
|
|
|
651
737
|
"""
|
|
652
738
|
# Create FC parameters dictionary
|
|
653
|
-
fc_parameters = ParDict.from_fc_parameters(self.
|
|
739
|
+
fc_parameters = ParDict.from_fc_parameters(self._flight_controller.fc_parameters)
|
|
654
740
|
|
|
655
741
|
# Early exit if no FC parameters available
|
|
656
742
|
if len(fc_parameters) == 0:
|
|
657
743
|
return fc_parameters
|
|
658
744
|
|
|
659
745
|
# Remove default parameters from FC parameters if default file exists
|
|
660
|
-
fc_parameters.remove_if_value_is_similar(self.
|
|
746
|
+
fc_parameters.remove_if_value_is_similar(self._local_filesystem.param_default_dict, is_within_tolerance)
|
|
661
747
|
|
|
662
748
|
# Filter out read-only parameters efficiently - only check params that exist in fc_parameters
|
|
663
749
|
readonly_params_to_remove = [
|
|
664
|
-
param_name
|
|
750
|
+
param_name
|
|
751
|
+
for param_name in fc_parameters
|
|
752
|
+
if self._local_filesystem.doc_dict.get(param_name, {}).get("ReadOnly", False)
|
|
665
753
|
]
|
|
666
754
|
for param_name in readonly_params_to_remove:
|
|
667
755
|
del fc_parameters[param_name]
|
|
@@ -681,13 +769,13 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
681
769
|
last_filename: Last configuration file to process (inclusive).
|
|
682
770
|
|
|
683
771
|
"""
|
|
684
|
-
if not self.
|
|
772
|
+
if not self._flight_controller.fc_parameters:
|
|
685
773
|
return
|
|
686
774
|
|
|
687
775
|
# Create the compounded state of all parameters stored in the AMC .param files
|
|
688
776
|
compound = ParDict()
|
|
689
777
|
first_config_step_filename = None
|
|
690
|
-
for file_name, file_params in self.
|
|
778
|
+
for file_name, file_params in self._local_filesystem.file_parameters.items():
|
|
691
779
|
if file_name != "00_default.param":
|
|
692
780
|
if first_config_step_filename is None:
|
|
693
781
|
first_config_step_filename = file_name
|
|
@@ -722,7 +810,7 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
722
810
|
first_name_without_ext = first_config_step_filename.rsplit(".", 1)[0] if first_config_step_filename else "unknown"
|
|
723
811
|
# the last filename already has the .param extension
|
|
724
812
|
filename = f"fc_params_missing_or_different_in_the_amc_param_files_{first_name_without_ext}_to_{last_filename}"
|
|
725
|
-
self.
|
|
813
|
+
self._local_filesystem.export_to_param(params_missing_in_the_amc_param_files, filename, annotate_doc=False)
|
|
726
814
|
logging_info(
|
|
727
815
|
_("Exported %d FC parameters missing or different in AMC files to %s"),
|
|
728
816
|
len(params_missing_in_the_amc_param_files),
|
|
@@ -731,10 +819,10 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
731
819
|
else:
|
|
732
820
|
logging_info(_("No FC parameters are missing or different from AMC parameter files"))
|
|
733
821
|
|
|
734
|
-
def
|
|
822
|
+
def _export_fc_params_missing_or_different(self) -> None:
|
|
735
823
|
non_default_non_read_only_fc_params = self._get_non_default_non_read_only_fc_params()
|
|
736
824
|
|
|
737
|
-
last_config_step_filename = list(self.
|
|
825
|
+
last_config_step_filename = list(self._local_filesystem.file_parameters.keys())[-1]
|
|
738
826
|
# Export FC parameters that are missing or different from AMC parameter files
|
|
739
827
|
self._export_fc_params_missing_or_different_in_amc_files(non_default_non_read_only_fc_params, self.current_file)
|
|
740
828
|
self._export_fc_params_missing_or_different_in_amc_files(
|
|
@@ -759,7 +847,7 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
759
847
|
run_in_thread: Callback to run the download in a thread (optional).
|
|
760
848
|
|
|
761
849
|
"""
|
|
762
|
-
if self.
|
|
850
|
+
if self._flight_controller.master is None:
|
|
763
851
|
show_error(_("Error"), _("No flight controller connected"))
|
|
764
852
|
return
|
|
765
853
|
|
|
@@ -771,26 +859,29 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
771
859
|
if not filename:
|
|
772
860
|
return
|
|
773
861
|
|
|
774
|
-
success = self.
|
|
862
|
+
success = self._flight_controller.download_last_flight_log(filename, progress_callback)
|
|
775
863
|
if success:
|
|
776
864
|
show_info(_("Success"), _("Flight log downloaded successfully to:\n%s") % filename)
|
|
777
865
|
else:
|
|
778
866
|
show_error(_("Error"), _("Failed to download flight log. Check the console for details."))
|
|
779
867
|
|
|
780
|
-
def is_configuration_step_optional(self, file_name: str, threshold_pct: int = 20) -> bool:
|
|
868
|
+
def is_configuration_step_optional(self, file_name: Optional[str] = None, threshold_pct: int = 20) -> bool:
|
|
781
869
|
"""
|
|
782
870
|
Check if the configuration step for the given file is optional.
|
|
783
871
|
|
|
784
872
|
Args:
|
|
785
|
-
file_name: Name of the configuration file to check.
|
|
873
|
+
file_name: Name of the configuration file to check, defaults to self.current_file.
|
|
786
874
|
threshold_pct: Threshold percentage below which the step is considered optional.
|
|
787
875
|
|
|
788
876
|
Returns:
|
|
789
877
|
bool: True if the configuration step is optional, False if mandatory.
|
|
790
878
|
|
|
791
879
|
"""
|
|
880
|
+
if file_name is None:
|
|
881
|
+
file_name = self.current_file
|
|
882
|
+
|
|
792
883
|
# Check if the configuration step for the given file is optional
|
|
793
|
-
mandatory_text, _mandatory_url = self.
|
|
884
|
+
mandatory_text, _mandatory_url = self._local_filesystem.get_documentation_text_and_url(file_name, "mandatory")
|
|
794
885
|
# Extract percentage from mandatory_text like "80% mandatory (20% optional)"
|
|
795
886
|
percentage = 0
|
|
796
887
|
if mandatory_text:
|
|
@@ -801,21 +892,24 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
801
892
|
|
|
802
893
|
return percentage <= threshold_pct
|
|
803
894
|
|
|
804
|
-
def get_next_non_optional_file(self, current_file: str) -> Optional[str]:
|
|
895
|
+
def get_next_non_optional_file(self, current_file: Optional[str] = None) -> Optional[str]:
|
|
805
896
|
"""
|
|
806
897
|
Get the next non-optional configuration file in sequence.
|
|
807
898
|
|
|
808
899
|
Args:
|
|
809
|
-
current_file: The current parameter file being processed.
|
|
900
|
+
current_file: The current parameter file being processed, defaults to self.current_file.
|
|
810
901
|
|
|
811
902
|
Returns:
|
|
812
903
|
Optional[str]: Next non-optional file name, or None if at the end.
|
|
813
904
|
|
|
814
905
|
"""
|
|
815
|
-
files = list(self.
|
|
906
|
+
files = list(self._local_filesystem.file_parameters.keys())
|
|
816
907
|
if not files:
|
|
817
908
|
return None
|
|
818
909
|
|
|
910
|
+
if current_file is None:
|
|
911
|
+
current_file = self.current_file
|
|
912
|
+
|
|
819
913
|
try:
|
|
820
914
|
next_file_index = files.index(current_file) + 1
|
|
821
915
|
|
|
@@ -843,14 +937,14 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
843
937
|
|
|
844
938
|
"""
|
|
845
939
|
# Get annotated FC parameters
|
|
846
|
-
annotated_fc_parameters = self.
|
|
847
|
-
self.
|
|
940
|
+
annotated_fc_parameters = self._local_filesystem.annotate_intermediate_comments_to_param_dict(
|
|
941
|
+
self._flight_controller.fc_parameters
|
|
848
942
|
)
|
|
849
943
|
if not annotated_fc_parameters:
|
|
850
944
|
return {}
|
|
851
945
|
|
|
852
946
|
# Categorize parameters using filesystem logic
|
|
853
|
-
categorized = self.
|
|
947
|
+
categorized = self._local_filesystem.categorize_parameters(annotated_fc_parameters)
|
|
854
948
|
if not categorized or len(categorized) != 3:
|
|
855
949
|
# Return empty dict if categorization fails or returns empty tuple
|
|
856
950
|
return {}
|
|
@@ -1009,13 +1103,13 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
1009
1103
|
return False
|
|
1010
1104
|
|
|
1011
1105
|
# If file exists, ask user for confirmation
|
|
1012
|
-
if self.
|
|
1106
|
+
if self._local_filesystem.vehicle_configuration_file_exists(filename):
|
|
1013
1107
|
msg = _("{} file already exists.\nDo you want to overwrite it?")
|
|
1014
1108
|
should_write_file = ask_confirmation(_("Overwrite existing file"), msg.format(filename))
|
|
1015
1109
|
|
|
1016
1110
|
# Write the file using if confirmed and has parameters
|
|
1017
1111
|
if should_write_file:
|
|
1018
|
-
self.
|
|
1112
|
+
self._local_filesystem.export_to_param(param_dict, filename, annotate_doc)
|
|
1019
1113
|
logging_info(_("Summary file %s written"), filename)
|
|
1020
1114
|
|
|
1021
1115
|
return should_write_file
|
|
@@ -1042,14 +1136,14 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
1042
1136
|
should_write_file = True # Default to writing new files
|
|
1043
1137
|
|
|
1044
1138
|
# If file exists, ask user for confirmation
|
|
1045
|
-
if self.
|
|
1046
|
-
zip_file_path = self.
|
|
1139
|
+
if self._local_filesystem.zip_file_exists():
|
|
1140
|
+
zip_file_path = self._local_filesystem.zip_file_path()
|
|
1047
1141
|
msg = _("{} file already exists.\nDo you want to overwrite it?")
|
|
1048
1142
|
should_write_file = ask_confirmation(_("Overwrite existing file"), msg.format(zip_file_path))
|
|
1049
1143
|
|
|
1050
1144
|
if should_write_file:
|
|
1051
|
-
self.
|
|
1052
|
-
zip_file_path = self.
|
|
1145
|
+
self._local_filesystem.zip_files(files_to_zip)
|
|
1146
|
+
zip_file_path = self._local_filesystem.zip_file_path()
|
|
1053
1147
|
msg = _(
|
|
1054
1148
|
"All relevant files have been zipped into the \n"
|
|
1055
1149
|
"{zip_file_path} file.\n\nYou can now upload this file to the ArduPilot Methodic\n"
|
|
@@ -1061,18 +1155,75 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
1061
1155
|
|
|
1062
1156
|
def repopulate_configuration_step_parameters(
|
|
1063
1157
|
self,
|
|
1064
|
-
) -> tuple[
|
|
1158
|
+
) -> tuple[list[tuple[str, str]], list[tuple[str, str]]]:
|
|
1065
1159
|
"""
|
|
1066
1160
|
Process the configuration step for the current file and update the self.parameters.
|
|
1067
1161
|
|
|
1068
1162
|
Returns:
|
|
1069
|
-
tuple: (
|
|
1163
|
+
tuple: (ui_errors, ui_infos)
|
|
1070
1164
|
|
|
1071
1165
|
"""
|
|
1072
|
-
|
|
1073
|
-
|
|
1166
|
+
# Reset tracking sets when navigating to new file
|
|
1167
|
+
self._added_parameters.clear()
|
|
1168
|
+
self._deleted_parameters.clear()
|
|
1169
|
+
|
|
1170
|
+
# Process configuration step and get operations to apply
|
|
1171
|
+
self.current_step_parameters, ui_errors, ui_infos, duplicates_to_remove, renames_to_apply, derived_params = (
|
|
1172
|
+
self._config_step_processor.process_configuration_step(self.current_file, self.fc_parameters)
|
|
1074
1173
|
)
|
|
1075
|
-
|
|
1174
|
+
|
|
1175
|
+
# Apply derived parameters to domain model using specialized setters
|
|
1176
|
+
for param_name, derived_par in derived_params.items():
|
|
1177
|
+
if param_name in self.current_step_parameters:
|
|
1178
|
+
# Update existing forced/derived parameter with new value using dedicated setter
|
|
1179
|
+
# The setter methods will raise ValueError for invalid parameters (not forced/derived, readonly, etc.)
|
|
1180
|
+
try:
|
|
1181
|
+
self.current_step_parameters[param_name].set_forced_or_derived_value(float(derived_par.value))
|
|
1182
|
+
if derived_par.comment:
|
|
1183
|
+
self.current_step_parameters[param_name].set_forced_or_derived_change_reason(derived_par.comment)
|
|
1184
|
+
except (ValueError, TypeError) as e:
|
|
1185
|
+
logging_error(
|
|
1186
|
+
_("Failed to apply derived parameter %s: %s"),
|
|
1187
|
+
param_name,
|
|
1188
|
+
str(e),
|
|
1189
|
+
)
|
|
1190
|
+
else:
|
|
1191
|
+
# Parameter in derived_params but not in self.parameters - this is unexpected
|
|
1192
|
+
logging_error(
|
|
1193
|
+
_("Derived parameter %s not found in current parameters, skipping"),
|
|
1194
|
+
param_name,
|
|
1195
|
+
)
|
|
1196
|
+
|
|
1197
|
+
# Apply rename operations to domain model using add/delete tracking
|
|
1198
|
+
for old_name in duplicates_to_remove:
|
|
1199
|
+
# Mark duplicate as deleted
|
|
1200
|
+
if old_name in self._local_filesystem.file_parameters.get(self.current_file, ParDict()):
|
|
1201
|
+
self._deleted_parameters.add(old_name)
|
|
1202
|
+
# Remove from domain model
|
|
1203
|
+
if old_name in self.current_step_parameters:
|
|
1204
|
+
del self.current_step_parameters[old_name]
|
|
1205
|
+
|
|
1206
|
+
for old_name, new_name in renames_to_apply:
|
|
1207
|
+
# Get the parameter value from the original file
|
|
1208
|
+
original_params = self._local_filesystem.file_parameters.get(self.current_file, ParDict())
|
|
1209
|
+
if old_name in original_params:
|
|
1210
|
+
# Mark old parameter as deleted
|
|
1211
|
+
self._deleted_parameters.add(old_name)
|
|
1212
|
+
|
|
1213
|
+
# Create new parameter with renamed name
|
|
1214
|
+
old_par = original_params[old_name]
|
|
1215
|
+
self.current_step_parameters[new_name] = self._config_step_processor.create_ardupilot_parameter(
|
|
1216
|
+
new_name, old_par, self.current_file, self.fc_parameters
|
|
1217
|
+
)
|
|
1218
|
+
|
|
1219
|
+
# Mark new parameter as added
|
|
1220
|
+
self._added_parameters.add(new_name)
|
|
1221
|
+
|
|
1222
|
+
# Remove old parameter from domain model
|
|
1223
|
+
if old_name in self.current_step_parameters:
|
|
1224
|
+
del self.current_step_parameters[old_name]
|
|
1225
|
+
|
|
1226
|
+
return ui_errors, ui_infos
|
|
1076
1227
|
|
|
1077
1228
|
def get_different_parameters(self) -> dict[str, ArduPilotParameter]:
|
|
1078
1229
|
"""
|
|
@@ -1082,7 +1233,7 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
1082
1233
|
Dictionary of parameters that are different from FC
|
|
1083
1234
|
|
|
1084
1235
|
"""
|
|
1085
|
-
return self.
|
|
1236
|
+
return self._config_step_processor.filter_different_parameters(self.current_step_parameters)
|
|
1086
1237
|
|
|
1087
1238
|
def delete_parameter_from_current_file(self, param_name: str) -> None:
|
|
1088
1239
|
"""
|
|
@@ -1092,18 +1243,30 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
1092
1243
|
param_name: The name of the parameter to delete
|
|
1093
1244
|
|
|
1094
1245
|
"""
|
|
1095
|
-
|
|
1096
|
-
if param_name in self.
|
|
1097
|
-
|
|
1246
|
+
# If parameter was in original file, mark as deleted
|
|
1247
|
+
if param_name in self._local_filesystem.file_parameters.get(self.current_file, ParDict()):
|
|
1248
|
+
self._deleted_parameters.add(param_name)
|
|
1249
|
+
|
|
1250
|
+
# If it was previously added in this session, remove from added set
|
|
1251
|
+
self._added_parameters.discard(param_name)
|
|
1252
|
+
|
|
1253
|
+
# Remove from runtime state
|
|
1254
|
+
if param_name in self.current_step_parameters:
|
|
1255
|
+
del self.current_step_parameters[param_name]
|
|
1098
1256
|
|
|
1099
1257
|
def get_possible_add_param_names(self) -> list[str]:
|
|
1100
1258
|
"""Return a sorted list of possible parameter names to add, or raise OperationNotPossibleError if not possible."""
|
|
1101
|
-
param_dict = self.
|
|
1259
|
+
param_dict = self._local_filesystem.doc_dict or self.fc_parameters
|
|
1102
1260
|
if not param_dict:
|
|
1103
1261
|
raise OperationNotPossibleError(
|
|
1104
1262
|
_("No apm.pdef.xml file and no FC connected. Not possible autocomplete parameter names.")
|
|
1105
1263
|
)
|
|
1106
|
-
|
|
1264
|
+
|
|
1265
|
+
# Build set of currently active parameters from domain model
|
|
1266
|
+
active_params = set(self.current_step_parameters.keys())
|
|
1267
|
+
|
|
1268
|
+
# Find parameters that aren't currently active
|
|
1269
|
+
possible_add_param_names = [param_name for param_name in param_dict if param_name not in active_params]
|
|
1107
1270
|
possible_add_param_names.sort()
|
|
1108
1271
|
return possible_add_param_names
|
|
1109
1272
|
|
|
@@ -1119,34 +1282,206 @@ class ConfigurationManager: # pylint: disable=too-many-public-methods
|
|
|
1119
1282
|
if not param_name:
|
|
1120
1283
|
raise InvalidParameterNameError(_("Parameter name can not be empty."))
|
|
1121
1284
|
|
|
1122
|
-
if
|
|
1285
|
+
# Check if parameter already exists (in original file, added, or not deleted)
|
|
1286
|
+
original_file_params = self._local_filesystem.file_parameters.get(self.current_file, ParDict())
|
|
1287
|
+
is_in_original = param_name in original_file_params
|
|
1288
|
+
is_already_added = param_name in self._added_parameters
|
|
1289
|
+
is_deleted = param_name in self._deleted_parameters
|
|
1290
|
+
|
|
1291
|
+
if (is_in_original and not is_deleted) or is_already_added:
|
|
1123
1292
|
raise InvalidParameterNameError(_("Parameter already exists, edit it instead"))
|
|
1124
1293
|
|
|
1125
1294
|
fc_parameters = self.fc_parameters
|
|
1126
1295
|
if fc_parameters:
|
|
1127
1296
|
if param_name in fc_parameters:
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1297
|
+
# Create the parameter in domain model
|
|
1298
|
+
par = Par(fc_parameters[param_name], "")
|
|
1299
|
+
self.current_step_parameters[param_name] = self._config_step_processor.create_ardupilot_parameter(
|
|
1300
|
+
param_name, par, self.current_file, fc_parameters
|
|
1131
1301
|
)
|
|
1302
|
+
|
|
1303
|
+
# Track addition
|
|
1304
|
+
if not is_in_original:
|
|
1305
|
+
self._added_parameters.add(param_name)
|
|
1306
|
+
# If was previously deleted, remove from deleted set
|
|
1307
|
+
self._deleted_parameters.discard(param_name)
|
|
1308
|
+
|
|
1132
1309
|
return True
|
|
1133
1310
|
raise InvalidParameterNameError(_("Parameter name not found in the flight controller."))
|
|
1134
1311
|
|
|
1135
|
-
if self.
|
|
1136
|
-
if param_name in self.
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
param_name, self.current_file_parameters[param_name], self.current_file, fc_parameters
|
|
1312
|
+
if self._local_filesystem.doc_dict:
|
|
1313
|
+
if param_name in self._local_filesystem.doc_dict:
|
|
1314
|
+
# Create the parameter in domain model
|
|
1315
|
+
par = Par(self._local_filesystem.param_default_dict.get(param_name, Par(0, "")).value, "")
|
|
1316
|
+
self.current_step_parameters[param_name] = self._config_step_processor.create_ardupilot_parameter(
|
|
1317
|
+
param_name, par, self.current_file, fc_parameters
|
|
1142
1318
|
)
|
|
1319
|
+
|
|
1320
|
+
# Track addition
|
|
1321
|
+
if not is_in_original:
|
|
1322
|
+
self._added_parameters.add(param_name)
|
|
1323
|
+
# If was previously deleted, remove from deleted set
|
|
1324
|
+
self._deleted_parameters.discard(param_name)
|
|
1325
|
+
|
|
1143
1326
|
return True
|
|
1144
1327
|
raise InvalidParameterNameError(
|
|
1145
1328
|
_("'{param_name}' not found in the apm.pdef.xml file.").format(param_name=param_name)
|
|
1146
1329
|
)
|
|
1147
1330
|
|
|
1148
|
-
if not fc_parameters and not self.
|
|
1331
|
+
if not fc_parameters and not self._local_filesystem.doc_dict:
|
|
1149
1332
|
raise OperationNotPossibleError(
|
|
1150
1333
|
_("Can not add parameter when no FC is connected and no apm.pdef.xml file exists.")
|
|
1151
1334
|
)
|
|
1152
1335
|
return False
|
|
1336
|
+
|
|
1337
|
+
def get_parameters_as_par_dict(self, param_names: Optional[list[str]] = None) -> ParDict:
|
|
1338
|
+
"""
|
|
1339
|
+
Extract Par objects from ArduPilotParameter domain models.
|
|
1340
|
+
|
|
1341
|
+
This method converts the domain model objects to data transfer objects (Par)
|
|
1342
|
+
that can be used for file operations or flight controller uploads.
|
|
1343
|
+
|
|
1344
|
+
Args:
|
|
1345
|
+
param_names: Optional list of parameter names to include.
|
|
1346
|
+
If None, includes all parameters.
|
|
1347
|
+
|
|
1348
|
+
Returns:
|
|
1349
|
+
ParDict containing Par objects with current values and change reasons
|
|
1350
|
+
|
|
1351
|
+
"""
|
|
1352
|
+
if param_names is None:
|
|
1353
|
+
param_names = list(self.current_step_parameters.keys())
|
|
1354
|
+
|
|
1355
|
+
return ParDict(
|
|
1356
|
+
{
|
|
1357
|
+
name: Par(self.current_step_parameters[name].get_new_value(), self.current_step_parameters[name].change_reason)
|
|
1358
|
+
for name in param_names
|
|
1359
|
+
if name in self.current_step_parameters
|
|
1360
|
+
}
|
|
1361
|
+
)
|
|
1362
|
+
|
|
1363
|
+
def has_unsaved_changes(self) -> bool:
|
|
1364
|
+
"""
|
|
1365
|
+
Check if any changes have been made that need to be saved.
|
|
1366
|
+
|
|
1367
|
+
This includes:
|
|
1368
|
+
- User edits to parameter values
|
|
1369
|
+
- Derived parameter changes (tracked via is_dirty)
|
|
1370
|
+
- Forced parameter changes (tracked via is_dirty)
|
|
1371
|
+
- Connection renaming changes (tracked via _added_parameters and _deleted_parameters)
|
|
1372
|
+
- Parameter additions
|
|
1373
|
+
- Parameter deletions
|
|
1374
|
+
|
|
1375
|
+
Returns:
|
|
1376
|
+
True if there are unsaved changes, False otherwise
|
|
1377
|
+
|
|
1378
|
+
"""
|
|
1379
|
+
# Check for structural changes (additions/deletions, including from renames)
|
|
1380
|
+
if self._added_parameters or self._deleted_parameters:
|
|
1381
|
+
return True
|
|
1382
|
+
|
|
1383
|
+
# Check individual parameter edits (value or comment changes)
|
|
1384
|
+
return any(param.is_dirty for param in self.current_step_parameters.values())
|
|
1385
|
+
|
|
1386
|
+
def get_last_configuration_step_number(self) -> Optional[int]:
|
|
1387
|
+
if self._local_filesystem.configuration_phases:
|
|
1388
|
+
# Get the first two characters of the last configuration step filename
|
|
1389
|
+
last_step_filename = next(reversed(self._local_filesystem.file_parameters.keys()))
|
|
1390
|
+
return int(last_step_filename[:2]) + 1 if len(last_step_filename) >= 2 else 1
|
|
1391
|
+
return None
|
|
1392
|
+
|
|
1393
|
+
def get_sorted_phases_with_end_and_weight(self, last_step_nr: int) -> dict[str, PhaseData]:
|
|
1394
|
+
return self._local_filesystem.get_sorted_phases_with_end_and_weight(last_step_nr)
|
|
1395
|
+
|
|
1396
|
+
def get_vehicle_directory(self) -> str:
|
|
1397
|
+
return self._local_filesystem.vehicle_dir
|
|
1398
|
+
|
|
1399
|
+
def parameter_files(self) -> list[str]:
|
|
1400
|
+
return list(self._local_filesystem.file_parameters.keys())
|
|
1401
|
+
|
|
1402
|
+
def parameter_documentation_available(self) -> bool:
|
|
1403
|
+
return bool(self._local_filesystem.doc_dict)
|
|
1404
|
+
|
|
1405
|
+
def configuration_phases(self) -> dict[str, PhaseData]:
|
|
1406
|
+
return self._local_filesystem.configuration_phases
|
|
1407
|
+
|
|
1408
|
+
def _write_current_file(self) -> None:
|
|
1409
|
+
self._local_filesystem.write_last_uploaded_filename(self.current_file)
|
|
1410
|
+
|
|
1411
|
+
def export_current_file(self, annotate_doc: bool) -> None:
|
|
1412
|
+
# Convert domain model parameters to Par objects for export
|
|
1413
|
+
export_params = self.get_parameters_as_par_dict()
|
|
1414
|
+
|
|
1415
|
+
# Export to file
|
|
1416
|
+
self._local_filesystem.export_to_param(export_params, self.current_file, annotate_doc)
|
|
1417
|
+
|
|
1418
|
+
# Update the filesystem's file_parameters to match what was saved
|
|
1419
|
+
self._local_filesystem.file_parameters[self.current_file] = export_params
|
|
1420
|
+
|
|
1421
|
+
self._added_parameters.clear()
|
|
1422
|
+
self._deleted_parameters.clear()
|
|
1423
|
+
# copy parameters new values to their _values_on_file
|
|
1424
|
+
for param in self.current_step_parameters.values():
|
|
1425
|
+
param.copy_new_value_to_file()
|
|
1426
|
+
|
|
1427
|
+
def open_documentation_in_browser(self, filename: str) -> None:
|
|
1428
|
+
_blog_text, blog_url = self.get_documentation_text_and_url("blog", filename)
|
|
1429
|
+
_wiki_text, wiki_url = self.get_documentation_text_and_url("wiki", filename)
|
|
1430
|
+
_external_tool_text, external_tool_url = self.get_documentation_text_and_url("external_tool", filename)
|
|
1431
|
+
if wiki_url:
|
|
1432
|
+
webbrowser_open(url=wiki_url, new=0, autoraise=False)
|
|
1433
|
+
if external_tool_url:
|
|
1434
|
+
webbrowser_open(url=external_tool_url, new=0, autoraise=False)
|
|
1435
|
+
if blog_url:
|
|
1436
|
+
webbrowser_open(url=blog_url, new=0, autoraise=True)
|
|
1437
|
+
|
|
1438
|
+
# frontend_tkinter_parameter_editor.py API end
|
|
1439
|
+
|
|
1440
|
+
# frontend_tkinter_parameter_editor_documentation_frame.py API start
|
|
1441
|
+
def get_documentation_text_and_url(self, key: str, filename: Optional[str] = None) -> tuple[str, str]:
|
|
1442
|
+
if filename is None:
|
|
1443
|
+
filename = self.current_file
|
|
1444
|
+
return self._local_filesystem.get_documentation_text_and_url(filename, key)
|
|
1445
|
+
|
|
1446
|
+
def get_why_why_now_tooltip(self) -> str:
|
|
1447
|
+
why_tooltip_text = self._local_filesystem.get_seq_tooltip_text(self.current_file, "why")
|
|
1448
|
+
why_now_tooltip_text = self._local_filesystem.get_seq_tooltip_text(self.current_file, "why_now")
|
|
1449
|
+
tooltip_text = ""
|
|
1450
|
+
if why_tooltip_text:
|
|
1451
|
+
tooltip_text += _("Why: ") + _(why_tooltip_text) + "\n"
|
|
1452
|
+
if why_now_tooltip_text:
|
|
1453
|
+
tooltip_text += _("Why now: ") + _(why_now_tooltip_text)
|
|
1454
|
+
return tooltip_text
|
|
1455
|
+
|
|
1456
|
+
def get_documentation_frame_title(self) -> str:
|
|
1457
|
+
if self.current_file:
|
|
1458
|
+
title = _("{current_file} Documentation")
|
|
1459
|
+
return title.format(current_file=self.current_file)
|
|
1460
|
+
return _("Documentation")
|
|
1461
|
+
|
|
1462
|
+
def parse_mandatory_level_percentage(self, text: str) -> tuple[int, str]:
|
|
1463
|
+
"""
|
|
1464
|
+
Parse and validate the mandatory level percentage from text.
|
|
1465
|
+
|
|
1466
|
+
Args:
|
|
1467
|
+
text: The text containing the mandatory level information
|
|
1468
|
+
|
|
1469
|
+
Returns:
|
|
1470
|
+
tuple: (percentage_value, tooltip_text)
|
|
1471
|
+
percentage_value: 0-100 for valid percentage, 0 for invalid
|
|
1472
|
+
tooltip_text: Formatted tooltip text
|
|
1473
|
+
|
|
1474
|
+
"""
|
|
1475
|
+
current_file = self.current_file or ""
|
|
1476
|
+
try:
|
|
1477
|
+
# Extract up to 3 digits from the start of the mandatory text
|
|
1478
|
+
percentage = int("".join([c for c in text[:3] if c.isdigit()]))
|
|
1479
|
+
if 0 <= percentage <= 100:
|
|
1480
|
+
tooltip = _("This configuration step ({current_file} intermediate parameter file) is {percentage}% mandatory")
|
|
1481
|
+
return percentage, tooltip.format(current_file=current_file, percentage=percentage)
|
|
1482
|
+
raise ValueError
|
|
1483
|
+
except ValueError:
|
|
1484
|
+
tooltip = _("Mandatory level not available for this configuration step ({current_file})")
|
|
1485
|
+
return 0, tooltip.format(current_file=current_file)
|
|
1486
|
+
|
|
1487
|
+
# frontend_tkinter_parameter_editor_documentation_frame.py API end
|