ardupilot-methodic-configurator 2.6.1__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.

Files changed (106) hide show
  1. ardupilot_methodic_configurator/__init__.py +2 -2
  2. ardupilot_methodic_configurator/__main__.py +25 -1
  3. ardupilot_methodic_configurator/annotate_params.py +49 -14
  4. ardupilot_methodic_configurator/argparse_check_range.py +1 -1
  5. ardupilot_methodic_configurator/backend_filesystem.py +7 -3
  6. ardupilot_methodic_configurator/backend_filesystem_configuration_steps.py +53 -6
  7. ardupilot_methodic_configurator/backend_filesystem_json_with_schema.py +3 -3
  8. ardupilot_methodic_configurator/backend_filesystem_program_settings.py +145 -8
  9. ardupilot_methodic_configurator/backend_filesystem_vehicle_components.py +3 -3
  10. ardupilot_methodic_configurator/backend_flightcontroller.py +37 -20
  11. ardupilot_methodic_configurator/backend_flightcontroller_info.py +1 -1
  12. ardupilot_methodic_configurator/backend_internet.py +1 -1
  13. ardupilot_methodic_configurator/backend_mavftp.py +1 -1
  14. ardupilot_methodic_configurator/battery_cell_voltages.py +1 -1
  15. ardupilot_methodic_configurator/common_arguments.py +1 -1
  16. ardupilot_methodic_configurator/configuration_manager.py +450 -115
  17. ardupilot_methodic_configurator/configuration_steps_ArduCopter.json +6 -4
  18. ardupilot_methodic_configurator/configuration_steps_ArduPlane.json +3 -1
  19. ardupilot_methodic_configurator/configuration_steps_Heli.json +3 -1
  20. ardupilot_methodic_configurator/configuration_steps_Rover.json +3 -1
  21. ardupilot_methodic_configurator/configuration_steps_strings.py +5 -3
  22. ardupilot_methodic_configurator/data_model_ardupilot_parameter.py +55 -2
  23. ardupilot_methodic_configurator/data_model_configuration_step.py +96 -46
  24. ardupilot_methodic_configurator/data_model_fc_ids.py +16 -7
  25. ardupilot_methodic_configurator/data_model_motor_test.py +1 -1
  26. ardupilot_methodic_configurator/data_model_par_dict.py +25 -11
  27. ardupilot_methodic_configurator/data_model_software_updates.py +1 -1
  28. ardupilot_methodic_configurator/data_model_template_overview.py +1 -1
  29. ardupilot_methodic_configurator/data_model_vehicle_components.py +1 -1
  30. ardupilot_methodic_configurator/data_model_vehicle_components_base.py +3 -2
  31. ardupilot_methodic_configurator/data_model_vehicle_components_display.py +1 -1
  32. ardupilot_methodic_configurator/data_model_vehicle_components_import.py +2 -1
  33. ardupilot_methodic_configurator/data_model_vehicle_components_json_schema.py +1 -1
  34. ardupilot_methodic_configurator/data_model_vehicle_components_templates.py +1 -1
  35. ardupilot_methodic_configurator/data_model_vehicle_components_validation.py +41 -1
  36. ardupilot_methodic_configurator/data_model_vehicle_project.py +1 -1
  37. ardupilot_methodic_configurator/data_model_vehicle_project_creator.py +1 -1
  38. ardupilot_methodic_configurator/data_model_vehicle_project_opener.py +1 -1
  39. ardupilot_methodic_configurator/extract_param_defaults.py +1 -1
  40. ardupilot_methodic_configurator/frontend_tkinter_autoresize_combobox.py +1 -1
  41. ardupilot_methodic_configurator/frontend_tkinter_base_window.py +5 -1
  42. ardupilot_methodic_configurator/frontend_tkinter_component_editor.py +55 -29
  43. ardupilot_methodic_configurator/frontend_tkinter_component_editor_base.py +18 -13
  44. ardupilot_methodic_configurator/frontend_tkinter_component_template_manager.py +1 -1
  45. ardupilot_methodic_configurator/frontend_tkinter_connection_selection.py +1 -1
  46. ardupilot_methodic_configurator/frontend_tkinter_directory_selection.py +1 -1
  47. ardupilot_methodic_configurator/frontend_tkinter_entry_dynamic.py +1 -1
  48. ardupilot_methodic_configurator/frontend_tkinter_flightcontroller_info.py +1 -1
  49. ardupilot_methodic_configurator/frontend_tkinter_font.py +1 -1
  50. ardupilot_methodic_configurator/frontend_tkinter_motor_test.py +1 -1
  51. ardupilot_methodic_configurator/frontend_tkinter_pair_tuple_combobox.py +7 -1
  52. ardupilot_methodic_configurator/frontend_tkinter_parameter_editor.py +50 -102
  53. ardupilot_methodic_configurator/frontend_tkinter_parameter_editor_documentation_frame.py +24 -58
  54. ardupilot_methodic_configurator/frontend_tkinter_parameter_editor_table.py +24 -56
  55. ardupilot_methodic_configurator/frontend_tkinter_progress_window.py +1 -1
  56. ardupilot_methodic_configurator/frontend_tkinter_project_creator.py +1 -1
  57. ardupilot_methodic_configurator/frontend_tkinter_project_opener.py +1 -1
  58. ardupilot_methodic_configurator/frontend_tkinter_rich_text.py +1 -1
  59. ardupilot_methodic_configurator/frontend_tkinter_scroll_frame.py +1 -1
  60. ardupilot_methodic_configurator/frontend_tkinter_show.py +1 -1
  61. ardupilot_methodic_configurator/frontend_tkinter_software_update.py +1 -1
  62. ardupilot_methodic_configurator/frontend_tkinter_stage_progress.py +19 -29
  63. ardupilot_methodic_configurator/frontend_tkinter_template_overview.py +1 -1
  64. ardupilot_methodic_configurator/frontend_tkinter_usage_popup_window.py +1 -1
  65. ardupilot_methodic_configurator/internationalization.py +1 -1
  66. ardupilot_methodic_configurator/param_pid_adjustment_update.py +43 -39
  67. ardupilot_methodic_configurator/tempcal_imu.py +1 -1
  68. ardupilot_methodic_configurator/vehicle_components.py +1 -1
  69. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/AirCar_v1/14_logging.param +3 -3
  70. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Big_Owl/14_logging.param +3 -3
  71. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Chimera7/14_logging.param +3 -3
  72. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/FETtec-5/14_logging.param +3 -3
  73. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/GazeboIrisWithTargetFollow/14_logging.param +3 -3
  74. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X500/14_logging.param +3 -3
  75. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X500_V2/14_logging.param +3 -3
  76. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X650_LTE/14_logging.param +3 -3
  77. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Hoverit_X11+/14_logging.param +3 -3
  78. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Hoverit_X13/14_logging.param +3 -3
  79. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Marmotte5v2/14_logging.param +3 -3
  80. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/ReadyToSkyZD550/14_logging.param +3 -3
  81. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/TarotFY680Hexacopter/05_remote_controller.param +1 -1
  82. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/TarotFY680Hexacopter/14_logging.param +3 -3
  83. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/TarotFY680Hexacopter/47_position_controller.param +2 -2
  84. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Tarot_X4/14_logging.param +3 -3
  85. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/X11_plus/14_logging.param +3 -3
  86. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.3.8-params/14_logging.param +3 -3
  87. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.4.4-params/14_logging.param +3 -3
  88. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.5.x-params/14_logging.param +3 -3
  89. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.6.x-params/14_logging.param +3 -3
  90. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/empty_4.5.x/10_gnss.param +1 -1
  91. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/empty_4.6.x/10_gnss.param +1 -1
  92. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/METADATA +11 -6
  93. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/RECORD +106 -106
  94. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/WHEEL +0 -0
  95. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/entry_points.txt +0 -0
  96. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSE.md +0 -0
  97. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/Apache-2.0.txt +0 -0
  98. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/BSD-3-Clause.txt +0 -0
  99. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/GPL-3.0-or-later.txt +0 -0
  100. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/LGPL-3.0-or-later.txt +0 -0
  101. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/MIT-CMU.txt +0 -0
  102. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/MIT.txt +0 -0
  103. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/MPL-2.0.txt +0 -0
  104. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/PSF-2.0.txt +0 -0
  105. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/credits/CREDITS.md +0 -0
  106. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,7 @@
3
3
  """
4
4
  A combobox GUI with support for complex lists.
5
5
 
6
- This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
6
+ This file is part of ArduPilot Methodic Configurator. https://github.com/ArduPilot/MethodicConfigurator
7
7
 
8
8
  SPDX-FileCopyrightText: 2024-2025 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>
9
9
 
@@ -111,6 +111,12 @@ class PairTupleCombobox(ttk.Combobox): # pylint: disable=too-many-ancestors
111
111
  setup_combobox_mousewheel_handling(self)
112
112
 
113
113
  def set_entries_tuple(self, list_pair_tuple: list[tuple[str, str]], selected_element: Union[None, str]) -> None:
114
+ # Clear existing entries before setting new ones
115
+ self.list_keys.clear()
116
+ self.list_shows.clear()
117
+ self.append_entries_tuple(list_pair_tuple, selected_element)
118
+
119
+ def append_entries_tuple(self, list_pair_tuple: list[tuple[str, str]], selected_element: Union[None, str]) -> None:
114
120
  if isinstance(list_pair_tuple, list):
115
121
  for tpl in list_pair_tuple:
116
122
  self.list_keys.append(tpl[0])
@@ -3,7 +3,7 @@
3
3
  """
4
4
  Parameter editor GUI.
5
5
 
6
- This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
6
+ This file is part of ArduPilot Methodic Configurator. https://github.com/ArduPilot/MethodicConfigurator
7
7
 
8
8
  SPDX-FileCopyrightText: 2024-2025 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>
9
9
 
@@ -19,7 +19,6 @@ from argparse import ArgumentParser, Namespace
19
19
  from logging import basicConfig as logging_basicConfig
20
20
  from logging import error as logging_error
21
21
  from logging import getLevelName as logging_getLevelName
22
- from logging import info as logging_info
23
22
  from logging import warning as logging_warning
24
23
  from tkinter import filedialog, messagebox, ttk
25
24
  from typing import Literal, Optional, Union
@@ -36,6 +35,7 @@ from ardupilot_methodic_configurator.configuration_manager import ConfigurationM
36
35
  from ardupilot_methodic_configurator.frontend_tkinter_autoresize_combobox import AutoResizeCombobox
37
36
  from ardupilot_methodic_configurator.frontend_tkinter_base_window import (
38
37
  BaseWindow,
38
+ ask_retry_cancel_popup,
39
39
  ask_yesno_popup,
40
40
  show_error_popup,
41
41
  show_info_popup,
@@ -156,10 +156,7 @@ class ParameterEditorWindow(BaseWindow): # pylint: disable=too-many-instance-at
156
156
  def __init__(self, configuration_manager: ConfigurationManager) -> None:
157
157
  super().__init__()
158
158
  self.configuration_manager = configuration_manager
159
- # Maintain backward compatibility with existing code
160
- self.local_filesystem = configuration_manager.filesystem
161
159
 
162
- self.at_least_one_changed_parameter_written = False
163
160
  self.file_selection_combobox: AutoResizeCombobox
164
161
  self.show_only_differences: tk.BooleanVar
165
162
  self.annotate_params_into_files: tk.BooleanVar
@@ -193,20 +190,15 @@ class ParameterEditorWindow(BaseWindow): # pylint: disable=too-many-instance-at
193
190
 
194
191
  self.__create_conf_widgets(__version__)
195
192
 
196
- if self.local_filesystem.configuration_phases:
197
- # Get the first two characters of the last configuration step filename
198
- last_step_filename = next(reversed(self.local_filesystem.file_parameters.keys()))
199
- last_step_nr = int(last_step_filename[:2]) + 1 if len(last_step_filename) >= 2 else 1
193
+ last_step_nr = self.configuration_manager.get_last_configuration_step_number()
194
+ if last_step_nr is not None:
195
+ phases = self.configuration_manager.get_sorted_phases_with_end_and_weight(last_step_nr)
200
196
 
201
- self.stage_progress_bar = StageProgressBar(
202
- self.main_frame, self.local_filesystem.configuration_phases, last_step_nr, self.gui_complexity
203
- )
197
+ self.stage_progress_bar = StageProgressBar(self.main_frame, phases, last_step_nr, self.gui_complexity)
204
198
  self.stage_progress_bar.pack(side=tk.TOP, fill="x", expand=False, pady=(2, 2), padx=(4, 4))
205
199
 
206
200
  # Create a DocumentationFrame object for the Documentation Content
207
- self.documentation_frame = DocumentationFrame(
208
- self.main_frame, self.local_filesystem, self.configuration_manager.current_file
209
- )
201
+ self.documentation_frame = DocumentationFrame(self.main_frame, self.configuration_manager)
210
202
  self.documentation_frame.documentation_frame.pack(side=tk.TOP, fill="x", expand=False, pady=(2, 2), padx=(4, 4))
211
203
 
212
204
  self.__create_parameter_area_widgets()
@@ -231,7 +223,7 @@ class ParameterEditorWindow(BaseWindow): # pylint: disable=too-many-instance-at
231
223
  directory_selection_frame = VehicleDirectorySelectionWidgets(
232
224
  self,
233
225
  config_subframe,
234
- self.local_filesystem.vehicle_dir,
226
+ self.configuration_manager.get_vehicle_directory(),
235
227
  destroy_parent_on_open=False,
236
228
  )
237
229
  if self.gui_complexity != "simple":
@@ -250,7 +242,7 @@ class ParameterEditorWindow(BaseWindow): # pylint: disable=too-many-instance-at
250
242
  # Create Combobox for intermediate parameter file selection
251
243
  self.file_selection_combobox = AutoResizeCombobox(
252
244
  file_selection_frame,
253
- list(self.local_filesystem.file_parameters.keys()),
245
+ self.configuration_manager.parameter_files(),
254
246
  self.configuration_manager.current_file,
255
247
  _(
256
248
  "Select the intermediate parameter file from the list of available"
@@ -326,7 +318,7 @@ class ParameterEditorWindow(BaseWindow): # pylint: disable=too-many-instance-at
326
318
 
327
319
  # Create a Scrollable parameter editor table
328
320
  self.parameter_editor_table = ParameterEditorTable(self.main_frame, self.configuration_manager, self)
329
- self.repopulate_parameter_table()
321
+ self.repopulate_parameter_table(regenerate_from_disk=True)
330
322
  self.parameter_editor_table.pack(side="top", fill="both", expand=True)
331
323
 
332
324
  # Create a frame for the buttons
@@ -354,7 +346,7 @@ class ParameterEditorWindow(BaseWindow): # pylint: disable=too-many-instance-at
354
346
  annotate_params_checkbox = ttk.Checkbutton(
355
347
  checkboxes_frame,
356
348
  text=_("Annotate docs into .param files"),
357
- state="normal" if self.local_filesystem.doc_dict else "disabled",
349
+ state="normal" if self.configuration_manager.parameter_documentation_available() else "disabled",
358
350
  variable=self.annotate_params_into_files,
359
351
  command=lambda: ProgramSettings.set_setting(
360
352
  "annotate_docs_into_param_files", self.annotate_params_into_files.get()
@@ -419,7 +411,7 @@ class ParameterEditorWindow(BaseWindow): # pylint: disable=too-many-instance-at
419
411
  state=(
420
412
  "normal"
421
413
  if self.gui_complexity != "simple"
422
- or self.configuration_manager.is_configuration_step_optional(self.configuration_manager.current_file)
414
+ or self.configuration_manager.is_configuration_step_optional()
423
415
  or not self.configuration_manager.is_fc_connected
424
416
  else "disabled"
425
417
  )
@@ -505,7 +497,7 @@ class ParameterEditorWindow(BaseWindow): # pylint: disable=too-many-instance-at
505
497
 
506
498
  try:
507
499
  # Inject GUI callbacks into business logic workflow
508
- success = self.configuration_manager.handle_imu_temperature_calibration_workflow(
500
+ _success = self.configuration_manager.handle_imu_temperature_calibration_workflow(
509
501
  selected_file,
510
502
  ask_user_confirmation=ask_yesno_popup,
511
503
  select_file=select_file,
@@ -514,10 +506,6 @@ class ParameterEditorWindow(BaseWindow): # pylint: disable=too-many-instance-at
514
506
  progress_callback=self.tempcal_imu_progress_window.update_progress_bar_300_pct,
515
507
  )
516
508
 
517
- if success:
518
- # Force writing doc annotations to file
519
- self.parameter_editor_table.set_at_least_one_param_edited(True)
520
-
521
509
  finally:
522
510
  self.tempcal_imu_progress_window.destroy()
523
511
 
@@ -607,9 +595,7 @@ class ParameterEditorWindow(BaseWindow): # pylint: disable=too-many-instance-at
607
595
  response = result[-1] if len(result) > 1 else None
608
596
 
609
597
  if response is True: # Yes option
610
- params_copied = self.configuration_manager.copy_fc_values_to_file(selected_file, relevant_fc_params)
611
- if params_copied:
612
- self.parameter_editor_table.set_at_least_one_param_edited(True)
598
+ _params_copied = self.configuration_manager.copy_fc_values_to_file(selected_file, relevant_fc_params)
613
599
  elif response is None: # Close option
614
600
  sys.exit(0)
615
601
  # If response is False (No option), do nothing and continue
@@ -657,22 +643,22 @@ class ParameterEditorWindow(BaseWindow): # pylint: disable=too-many-instance-at
657
643
  self.__do_tempcal_imu(selected_file)
658
644
  # open the documentation of the next step in the browser,
659
645
  # before giving the user the option to close the SW in the __should_copy_fc_values_to_file method
660
- self.documentation_frame.open_documentation_in_browser(selected_file)
646
+ if self.documentation_frame.get_auto_open_documentation_in_browser() or self.gui_complexity == "simple":
647
+ self.configuration_manager.open_documentation_in_browser(selected_file)
661
648
  self.__should_copy_fc_values_to_file(selected_file)
662
649
  selected_file = self.__should_jump_to_file(selected_file)
663
650
  self.__should_download_file_from_url(selected_file)
664
651
  self.__should_upload_file_to_fc(selected_file)
665
652
 
666
- # Update the current_file attribute to the selected file
653
+ # current_file might have been changed by jump, so update again
667
654
  self.configuration_manager.current_file = selected_file
668
- self.at_least_one_changed_parameter_written = False
669
- self.documentation_frame.refresh_documentation_labels(selected_file)
670
- self.documentation_frame.update_why_why_now_tooltip(selected_file)
671
- self.repopulate_parameter_table()
655
+ self.documentation_frame.refresh_documentation_labels()
656
+ self.documentation_frame.update_why_why_now_tooltip()
657
+ self.repopulate_parameter_table(regenerate_from_disk=True)
672
658
  self._update_skip_button_state()
673
659
 
674
660
  def _update_progress_bar_from_file(self, selected_file: str) -> None:
675
- if self.local_filesystem.configuration_phases:
661
+ if self.configuration_manager.configuration_phases():
676
662
  try:
677
663
  step_nr = int(selected_file[:2])
678
664
  self.stage_progress_bar.update_progress(step_nr)
@@ -690,37 +676,14 @@ class ParameterEditorWindow(BaseWindow): # pylint: disable=too-many-instance-at
690
676
  if not redownload:
691
677
  self.on_param_file_combobox_change(None, forced=True) # the initial param read will trigger a table update
692
678
 
693
- def repopulate_parameter_table(self) -> None:
679
+ def repopulate_parameter_table(self, regenerate_from_disk: bool) -> None:
694
680
  if not self.configuration_manager.current_file:
695
681
  return # no file was yet selected, so skip it
696
682
  # Re-populate the table with the new parameters
697
- self.parameter_editor_table.repopulate(self.show_only_differences.get(), self.gui_complexity)
683
+ self.parameter_editor_table.repopulate(self.show_only_differences.get(), self.gui_complexity, regenerate_from_disk)
698
684
 
699
685
  def on_show_only_changed_checkbox_change(self) -> None:
700
- self.repopulate_parameter_table()
701
-
702
- def upload_params_that_require_reset(self, selected_params: dict) -> None:
703
- """
704
- Write only the selected parameters to the flight controller that require a reset.
705
-
706
- After the reset, the other parameters that do not require a reset must still be written to the flight controller.
707
- """
708
- self.reset_progress_window = ProgressWindow(
709
- self.root,
710
- _("Resetting Flight Controller"),
711
- _("Waiting for {} of {} seconds"),
712
- only_show_when_update_progress_called=True,
713
- )
714
-
715
- if self.configuration_manager.upload_parameters_that_require_reset_workflow(
716
- selected_params,
717
- ask_confirmation=ask_yesno_popup,
718
- show_error=show_error_popup,
719
- progress_callback=self.reset_progress_window.update_progress_bar,
720
- ):
721
- self.at_least_one_changed_parameter_written = True
722
-
723
- self.reset_progress_window.destroy() # for the case that we are doing a test and there is no real FC connected
686
+ self.repopulate_parameter_table(regenerate_from_disk=False)
724
687
 
725
688
  def on_upload_selected_click(self) -> None:
726
689
  self.write_changes_to_intermediate_parameter_file()
@@ -741,41 +704,31 @@ class ParameterEditorWindow(BaseWindow): # pylint: disable=too-many-instance-at
741
704
 
742
705
  # This function can recurse multiple times if there is an upload error
743
706
  def upload_selected_params(self, selected_params: dict) -> None:
744
- logging_info(
745
- _("Uploading %d selected %s parameters to flight controller..."),
746
- len(selected_params),
747
- self.configuration_manager.current_file,
707
+ # Create progress windows
708
+ self.reset_progress_window = ProgressWindow(
709
+ self.root,
710
+ _("Resetting Flight Controller"),
711
+ _("Waiting for {} of {} seconds"),
712
+ only_show_when_update_progress_called=True,
748
713
  )
749
-
750
- self.upload_params_that_require_reset(selected_params)
751
-
752
- # Use ConfigurationManager to handle the business logic
753
- nr_changed = self.configuration_manager.upload_selected_parameters_workflow(
754
- selected_params, show_error=show_error_popup
714
+ self.param_download_progress_window = ProgressWindow(
715
+ self.root,
716
+ _("Re-downloading FC parameters"),
717
+ _("Downloaded {} of {} parameters"),
755
718
  )
756
719
 
757
- # Update GUI state if any parameters were changed
758
- if nr_changed > 0:
759
- self.at_least_one_changed_parameter_written = True
760
-
761
- if self.at_least_one_changed_parameter_written:
762
- # Re-download all parameters, in case one of them changed, and validate that all uploads were successful
763
- self.download_flight_controller_parameters(redownload=True)
764
- param_upload_error = self.configuration_manager.validate_uploaded_parameters(selected_params)
765
-
766
- if param_upload_error:
767
- if messagebox.askretrycancel(
768
- _("Parameter upload error"),
769
- _("Failed to upload the following parameters to the flight controller:\n")
770
- + f"{(', ').join(param_upload_error)}",
771
- ):
772
- self.upload_selected_params(selected_params)
773
- else:
774
- logging_info(_("All parameters uploaded to the flight controller successfully"))
775
-
776
- self.configuration_manager.export_fc_params_missing_or_different()
777
-
778
- self.local_filesystem.write_last_uploaded_filename(self.configuration_manager.current_file)
720
+ try:
721
+ self.configuration_manager.upload_selected_params_workflow(
722
+ selected_params,
723
+ ask_confirmation=ask_yesno_popup,
724
+ ask_retry_cancel=ask_retry_cancel_popup,
725
+ show_error=show_error_popup,
726
+ progress_callback_for_reset=self.reset_progress_window.update_progress_bar,
727
+ progress_callback_for_download=self.param_download_progress_window.update_progress_bar,
728
+ )
729
+ finally:
730
+ self.reset_progress_window.destroy()
731
+ self.param_download_progress_window.destroy()
779
732
 
780
733
  def on_download_last_flight_log_click(self) -> None:
781
734
  """Handle the download last flight log button click."""
@@ -811,7 +764,7 @@ class ParameterEditorWindow(BaseWindow): # pylint: disable=too-many-instance-at
811
764
  skip_button_state = (
812
765
  "normal"
813
766
  if self.gui_complexity != "simple"
814
- or self.configuration_manager.is_configuration_step_optional(self.configuration_manager.current_file)
767
+ or self.configuration_manager.is_configuration_step_optional()
815
768
  or not self.configuration_manager.is_fc_connected
816
769
  else "disabled"
817
770
  )
@@ -821,7 +774,7 @@ class ParameterEditorWindow(BaseWindow): # pylint: disable=too-many-instance-at
821
774
  self.write_changes_to_intermediate_parameter_file()
822
775
 
823
776
  # Use ConfigurationManager to get the next non-optional file
824
- next_file = self.configuration_manager.get_next_non_optional_file(self.configuration_manager.current_file)
777
+ next_file = self.configuration_manager.get_next_non_optional_file()
825
778
 
826
779
  if next_file is None:
827
780
  # No more files to process, write summary and close
@@ -844,19 +797,14 @@ class ParameterEditorWindow(BaseWindow): # pylint: disable=too-many-instance-at
844
797
  # the parameter metadata might have changed, or not be present in the file.
845
798
  # In that situation, avoid asking multiple times to write the file, by checking the time last asked
846
799
  # But only if self.annotate_params_into_files.get()
847
- if self.parameter_editor_table.get_at_least_one_param_edited() or (
800
+ if self.configuration_manager.has_unsaved_changes() or (
848
801
  self.annotate_params_into_files.get() and elapsed_since_last_ask > 1.0
849
802
  ):
850
803
  msg = _("Do you want to write the changes to the {current_filename} file?").format(
851
804
  current_filename=self.configuration_manager.current_file
852
805
  )
853
806
  if messagebox.askyesno(_("One or more parameters have been edited"), msg.format(**locals())):
854
- self.local_filesystem.export_to_param(
855
- self.local_filesystem.file_parameters[self.configuration_manager.current_file],
856
- self.configuration_manager.current_file,
857
- annotate_doc=self.annotate_params_into_files.get(),
858
- )
859
- self.parameter_editor_table.set_at_least_one_param_edited(False)
807
+ self.configuration_manager.export_current_file(annotate_doc=self.annotate_params_into_files.get())
860
808
  self.last_time_asked_to_save = time.time()
861
809
 
862
810
  def close_connection_and_quit(self) -> None:
@@ -1,7 +1,7 @@
1
1
  """
2
2
  The documentation frame containing the documentation for the current configuration step.
3
3
 
4
- This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
4
+ This file is part of ArduPilot Methodic Configurator. https://github.com/ArduPilot/MethodicConfigurator
5
5
 
6
6
  SPDX-FileCopyrightText: 2024-2025 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>
7
7
 
@@ -11,11 +11,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
11
11
  import tkinter as tk
12
12
  from platform import system as platform_system
13
13
  from tkinter import ttk
14
- from webbrowser import open as webbrowser_open # to open the blog post documentation
14
+ from webbrowser import open as webbrowser_open # to open the web documentation
15
15
 
16
16
  from ardupilot_methodic_configurator import _
17
- from ardupilot_methodic_configurator.backend_filesystem import LocalFilesystem
18
17
  from ardupilot_methodic_configurator.backend_filesystem_program_settings import ProgramSettings
18
+ from ardupilot_methodic_configurator.configuration_manager import ConfigurationManager
19
19
  from ardupilot_methodic_configurator.frontend_tkinter_rich_text import get_widget_font_family_and_size
20
20
  from ardupilot_methodic_configurator.frontend_tkinter_show import show_tooltip
21
21
 
@@ -49,16 +49,16 @@ class DocumentationFrame:
49
49
  ),
50
50
  )
51
51
 
52
- def __init__(self, root: tk.Widget, local_filesystem: LocalFilesystem, current_file: str) -> None:
52
+ def __init__(self, root: tk.Widget, configuration_manager: ConfigurationManager) -> None:
53
53
  self.root = root
54
- self.local_filesystem = local_filesystem
54
+ self.configuration_manager = configuration_manager
55
55
  self.documentation_frame: ttk.LabelFrame
56
56
  self.documentation_labels: dict[str, ttk.Label] = {}
57
57
  self.mandatory_level: ttk.Progressbar
58
58
  self.auto_open_var = tk.BooleanVar(value=bool(ProgramSettings.get_setting("auto_open_doc_in_browser")))
59
- self._create_documentation_frame(current_file)
59
+ self._create_documentation_frame()
60
60
 
61
- def _create_documentation_frame(self, current_file: str) -> None:
61
+ def _create_documentation_frame(self) -> None:
62
62
  self.documentation_frame = ttk.LabelFrame(self.root, text=_("Documentation"))
63
63
 
64
64
  # Create a grid structure within the documentation_frame
@@ -86,8 +86,8 @@ class DocumentationFrame:
86
86
  documentation_grid.columnconfigure(1, weight=1)
87
87
 
88
88
  # Dynamically update the documentation text and URL links
89
- self.refresh_documentation_labels(current_file)
90
- self.update_why_why_now_tooltip(current_file)
89
+ self.refresh_documentation_labels()
90
+ self.update_why_why_now_tooltip()
91
91
 
92
92
  def _create_bottom_row(self, documentation_grid: ttk.Frame, row: int) -> None:
93
93
  bottom_frame = ttk.Frame(documentation_grid)
@@ -111,52 +111,28 @@ class DocumentationFrame:
111
111
  )
112
112
  auto_open_checkbox.pack(side=tk.LEFT, expand=False)
113
113
 
114
- def update_why_why_now_tooltip(self, current_file: str) -> None:
115
- why_tooltip_text = self.local_filesystem.get_seq_tooltip_text(current_file, "why")
116
- why_now_tooltip_text = self.local_filesystem.get_seq_tooltip_text(current_file, "why_now")
117
- tooltip_text = ""
118
- if why_tooltip_text:
119
- tooltip_text += _("Why: ") + _(why_tooltip_text) + "\n"
120
- if why_now_tooltip_text:
121
- tooltip_text += _("Why now: ") + _(why_now_tooltip_text)
114
+ def update_why_why_now_tooltip(self) -> None:
115
+ tooltip_text = self.configuration_manager.get_why_why_now_tooltip()
122
116
  if tooltip_text:
123
117
  show_tooltip(self.documentation_frame, tooltip_text, position_below=False)
124
118
 
125
- def open_documentation_in_browser(self, current_file: str) -> None:
126
- _blog_text, blog_url = self.local_filesystem.get_documentation_text_and_url(current_file, "blog")
127
- _wiki_text, wiki_url = self.local_filesystem.get_documentation_text_and_url(current_file, "wiki")
128
- _external_tool_text, external_tool_url = self.local_filesystem.get_documentation_text_and_url(
129
- current_file, "external_tool"
130
- )
119
+ def get_auto_open_documentation_in_browser(self) -> bool:
120
+ return self.auto_open_var.get()
131
121
 
132
- if self.auto_open_var.get() or ProgramSettings.get_setting("gui_complexity") == "simple":
133
- if wiki_url:
134
- webbrowser_open(url=wiki_url, new=0, autoraise=False)
135
- if external_tool_url:
136
- webbrowser_open(url=external_tool_url, new=0, autoraise=False)
137
- if blog_url:
138
- webbrowser_open(url=blog_url, new=0, autoraise=True)
139
-
140
- def refresh_documentation_labels(self, current_file: str) -> None:
141
- if current_file:
142
- title = _("{current_file} Documentation")
143
- frame_title = title.format(**locals())
144
- else:
145
- frame_title = _("Documentation")
122
+ def refresh_documentation_labels(self) -> None:
123
+ frame_title = self.configuration_manager.get_documentation_frame_title()
146
124
  self.documentation_frame.config(text=frame_title)
147
125
 
148
- blog_text, blog_url = self.local_filesystem.get_documentation_text_and_url(current_file, "blog")
126
+ blog_text, blog_url = self.configuration_manager.get_documentation_text_and_url("blog")
149
127
  self._refresh_documentation_label(self.BLOG_LABEL, _(blog_text) if blog_text else "", blog_url)
150
- wiki_text, wiki_url = self.local_filesystem.get_documentation_text_and_url(current_file, "wiki")
128
+ wiki_text, wiki_url = self.configuration_manager.get_documentation_text_and_url("wiki")
151
129
  self._refresh_documentation_label(self.WIKI_LABEL, _(wiki_text) if wiki_text else "", wiki_url)
152
- external_tool_text, external_tool_url = self.local_filesystem.get_documentation_text_and_url(
153
- current_file, "external_tool"
154
- )
130
+ external_tool_text, external_tool_url = self.configuration_manager.get_documentation_text_and_url("external_tool")
155
131
  self._refresh_documentation_label(
156
132
  self.EXTERNAL_TOOL_LABEL, _(external_tool_text) if external_tool_text else "", external_tool_url
157
133
  )
158
- mandatory_text, _mandatory_url = self.local_filesystem.get_documentation_text_and_url(current_file, "mandatory")
159
- self._refresh_mandatory_level(current_file, mandatory_text)
134
+ mandatory_text, _mandatory_url = self.configuration_manager.get_documentation_text_and_url("mandatory")
135
+ self._refresh_mandatory_level(mandatory_text)
160
136
 
161
137
  def _refresh_documentation_label(self, label_key: str, text: str, url: str, url_expected: bool = True) -> None:
162
138
  label = self.documentation_labels[label_key]
@@ -175,17 +151,7 @@ class DocumentationFrame:
175
151
  if url_expected:
176
152
  show_tooltip(label, _("Documentation URL not available"))
177
153
 
178
- def _refresh_mandatory_level(self, current_file: str, text: str) -> None:
179
- _used_indirectly_by_the_tooltip = current_file
180
- try:
181
- # Extract up to 3 digits from the start of the mandatory text
182
- percentage = int("".join([c for c in text[:3] if c.isdigit()]))
183
- if 0 <= percentage <= 100:
184
- self.mandatory_level.config(value=percentage)
185
- tooltip = _("This configuration step ({current_file} intermediate parameter file) is {percentage}% mandatory")
186
- else:
187
- raise ValueError
188
- except ValueError:
189
- self.mandatory_level.config(value=0)
190
- tooltip = _("Mandatory level not available for this configuration step ({current_file})")
191
- show_tooltip(self.mandatory_level, tooltip.format(**locals()))
154
+ def _refresh_mandatory_level(self, text: str) -> None:
155
+ percentage, tooltip = self.configuration_manager.parse_mandatory_level_percentage(text)
156
+ self.mandatory_level.config(value=percentage)
157
+ show_tooltip(self.mandatory_level, tooltip)