ardupilot-methodic-configurator 2.6.1__py3-none-any.whl → 2.7.1__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 (107) hide show
  1. ardupilot_methodic_configurator/__init__.py +2 -2
  2. ardupilot_methodic_configurator/__main__.py +34 -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_freedesktop.py +275 -0
  8. ardupilot_methodic_configurator/backend_filesystem_json_with_schema.py +3 -3
  9. ardupilot_methodic_configurator/backend_filesystem_program_settings.py +26 -8
  10. ardupilot_methodic_configurator/backend_filesystem_vehicle_components.py +3 -3
  11. ardupilot_methodic_configurator/backend_flightcontroller.py +37 -20
  12. ardupilot_methodic_configurator/backend_flightcontroller_info.py +1 -1
  13. ardupilot_methodic_configurator/backend_internet.py +1 -1
  14. ardupilot_methodic_configurator/backend_mavftp.py +1 -1
  15. ardupilot_methodic_configurator/battery_cell_voltages.py +1 -1
  16. ardupilot_methodic_configurator/common_arguments.py +1 -1
  17. ardupilot_methodic_configurator/configuration_manager.py +561 -121
  18. ardupilot_methodic_configurator/configuration_steps_ArduCopter.json +7 -5
  19. ardupilot_methodic_configurator/configuration_steps_ArduPlane.json +3 -1
  20. ardupilot_methodic_configurator/configuration_steps_Heli.json +3 -1
  21. ardupilot_methodic_configurator/configuration_steps_Rover.json +3 -1
  22. ardupilot_methodic_configurator/configuration_steps_strings.py +5 -3
  23. ardupilot_methodic_configurator/data_model_ardupilot_parameter.py +55 -2
  24. ardupilot_methodic_configurator/data_model_configuration_step.py +96 -46
  25. ardupilot_methodic_configurator/data_model_fc_ids.py +16 -7
  26. ardupilot_methodic_configurator/data_model_motor_test.py +1 -1
  27. ardupilot_methodic_configurator/data_model_par_dict.py +25 -11
  28. ardupilot_methodic_configurator/data_model_software_updates.py +1 -1
  29. ardupilot_methodic_configurator/data_model_template_overview.py +1 -1
  30. ardupilot_methodic_configurator/data_model_vehicle_components.py +1 -1
  31. ardupilot_methodic_configurator/data_model_vehicle_components_base.py +3 -2
  32. ardupilot_methodic_configurator/data_model_vehicle_components_display.py +1 -1
  33. ardupilot_methodic_configurator/data_model_vehicle_components_import.py +2 -1
  34. ardupilot_methodic_configurator/data_model_vehicle_components_json_schema.py +1 -1
  35. ardupilot_methodic_configurator/data_model_vehicle_components_templates.py +1 -1
  36. ardupilot_methodic_configurator/data_model_vehicle_components_validation.py +41 -1
  37. ardupilot_methodic_configurator/data_model_vehicle_project.py +1 -1
  38. ardupilot_methodic_configurator/data_model_vehicle_project_creator.py +1 -1
  39. ardupilot_methodic_configurator/data_model_vehicle_project_opener.py +1 -1
  40. ardupilot_methodic_configurator/extract_param_defaults.py +1 -1
  41. ardupilot_methodic_configurator/frontend_tkinter_autoresize_combobox.py +1 -1
  42. ardupilot_methodic_configurator/frontend_tkinter_base_window.py +6 -2
  43. ardupilot_methodic_configurator/frontend_tkinter_component_editor.py +56 -29
  44. ardupilot_methodic_configurator/frontend_tkinter_component_editor_base.py +18 -13
  45. ardupilot_methodic_configurator/frontend_tkinter_component_template_manager.py +1 -1
  46. ardupilot_methodic_configurator/frontend_tkinter_connection_selection.py +1 -1
  47. ardupilot_methodic_configurator/frontend_tkinter_directory_selection.py +1 -1
  48. ardupilot_methodic_configurator/frontend_tkinter_entry_dynamic.py +1 -1
  49. ardupilot_methodic_configurator/frontend_tkinter_flightcontroller_info.py +1 -1
  50. ardupilot_methodic_configurator/frontend_tkinter_font.py +1 -1
  51. ardupilot_methodic_configurator/frontend_tkinter_motor_test.py +1 -1
  52. ardupilot_methodic_configurator/frontend_tkinter_pair_tuple_combobox.py +7 -1
  53. ardupilot_methodic_configurator/frontend_tkinter_parameter_editor.py +107 -156
  54. ardupilot_methodic_configurator/frontend_tkinter_parameter_editor_documentation_frame.py +24 -58
  55. ardupilot_methodic_configurator/frontend_tkinter_parameter_editor_table.py +24 -56
  56. ardupilot_methodic_configurator/frontend_tkinter_progress_window.py +1 -1
  57. ardupilot_methodic_configurator/frontend_tkinter_project_creator.py +1 -1
  58. ardupilot_methodic_configurator/frontend_tkinter_project_opener.py +1 -1
  59. ardupilot_methodic_configurator/frontend_tkinter_rich_text.py +1 -1
  60. ardupilot_methodic_configurator/frontend_tkinter_scroll_frame.py +1 -1
  61. ardupilot_methodic_configurator/frontend_tkinter_show.py +3 -3
  62. ardupilot_methodic_configurator/frontend_tkinter_software_update.py +1 -1
  63. ardupilot_methodic_configurator/frontend_tkinter_stage_progress.py +19 -29
  64. ardupilot_methodic_configurator/frontend_tkinter_template_overview.py +1 -1
  65. ardupilot_methodic_configurator/frontend_tkinter_usage_popup_window.py +1 -1
  66. ardupilot_methodic_configurator/internationalization.py +1 -1
  67. ardupilot_methodic_configurator/param_pid_adjustment_update.py +43 -39
  68. ardupilot_methodic_configurator/tempcal_imu.py +1 -1
  69. ardupilot_methodic_configurator/vehicle_components.py +1 -1
  70. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/AirCar_v1/14_logging.param +3 -3
  71. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Big_Owl/14_logging.param +3 -3
  72. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Chimera7/14_logging.param +3 -3
  73. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/FETtec-5/14_logging.param +3 -3
  74. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/GazeboIrisWithTargetFollow/14_logging.param +3 -3
  75. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X500/14_logging.param +3 -3
  76. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X500_V2/14_logging.param +3 -3
  77. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X650_LTE/14_logging.param +3 -3
  78. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Hoverit_X11+/14_logging.param +3 -3
  79. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Hoverit_X13/14_logging.param +3 -3
  80. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Marmotte5v2/14_logging.param +3 -3
  81. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/ReadyToSkyZD550/14_logging.param +3 -3
  82. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/TarotFY680Hexacopter/05_remote_controller.param +1 -1
  83. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/TarotFY680Hexacopter/14_logging.param +3 -3
  84. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/TarotFY680Hexacopter/47_position_controller.param +2 -2
  85. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Tarot_X4/14_logging.param +3 -3
  86. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/X11_plus/14_logging.param +3 -3
  87. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.3.8-params/14_logging.param +3 -3
  88. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.4.4-params/14_logging.param +3 -3
  89. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.5.x-params/14_logging.param +3 -3
  90. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.6.x-params/14_logging.param +3 -3
  91. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/empty_4.5.x/10_gnss.param +1 -1
  92. ardupilot_methodic_configurator/vehicle_templates/ArduCopter/empty_4.6.x/10_gnss.param +1 -1
  93. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.1.dist-info}/METADATA +12 -7
  94. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.1.dist-info}/RECORD +107 -106
  95. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.1.dist-info}/licenses/credits/CREDITS.md +1 -0
  96. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.1.dist-info}/WHEEL +0 -0
  97. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.1.dist-info}/entry_points.txt +0 -0
  98. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.1.dist-info}/licenses/LICENSE.md +0 -0
  99. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.1.dist-info}/licenses/LICENSES/Apache-2.0.txt +0 -0
  100. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.1.dist-info}/licenses/LICENSES/BSD-3-Clause.txt +0 -0
  101. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.1.dist-info}/licenses/LICENSES/GPL-3.0-or-later.txt +0 -0
  102. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.1.dist-info}/licenses/LICENSES/LGPL-3.0-or-later.txt +0 -0
  103. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.1.dist-info}/licenses/LICENSES/MIT-CMU.txt +0 -0
  104. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.1.dist-info}/licenses/LICENSES/MIT.txt +0 -0
  105. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.1.dist-info}/licenses/LICENSES/MPL-2.0.txt +0 -0
  106. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.1.dist-info}/licenses/LICENSES/PSF-2.0.txt +0 -0
  107. {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.1.dist-info}/top_level.txt +0 -0
@@ -104,7 +104,7 @@ class ParDict(dict[str, Par]):
104
104
  """
105
105
  parameter_dict = ParDict()
106
106
  try:
107
- with open(param_file, encoding="utf-8") as f_handle:
107
+ with open(param_file, encoding="utf-8-sig") as f_handle:
108
108
  for i, f_line in enumerate(f_handle, start=1):
109
109
  original_line = f_line
110
110
  line = f_line.strip()
@@ -125,14 +125,18 @@ class ParDict(dict[str, Par]):
125
125
  elif "\t" in line:
126
126
  parameter, value = line.split("\t", 1)
127
127
  else:
128
- msg = f"Missing parameter-value separator: {line} in {param_file} line {i}"
128
+ msg = _("Missing parameter-value separator: {line} in {param_file} line {i}").format(
129
+ line=line, param_file=param_file, i=i
130
+ )
129
131
  raise SystemExit(msg)
130
132
  # Strip whitespace from both parameter name and value immediately after splitting
131
133
  parameter = parameter.strip()
132
134
  value = value.strip()
133
135
  ParDict._validate_parameter(param_file, parameter_dict, i, original_line, comment, parameter, value)
134
136
  except UnicodeDecodeError as exp:
135
- msg = f"Fatal error reading {param_file}: {exp}"
137
+ msg = _("Fatal error reading {param_file}, file must be UTF-8 encoded: {exp}").format(
138
+ param_file=param_file, exp=exp
139
+ )
136
140
  raise SystemExit(msg) from exp
137
141
  return parameter_dict
138
142
 
@@ -147,26 +151,34 @@ class ParDict(dict[str, Par]):
147
151
  value: str,
148
152
  ) -> None:
149
153
  if len(parameter_name) > PARAM_NAME_MAX_LEN:
150
- msg = f"Too long parameter name: {parameter_name} in {param_file} line {i}"
154
+ msg = _("Too long parameter name: {parameter_name} in {param_file} line {i}").format(
155
+ parameter_name=parameter_name, param_file=param_file, i=i
156
+ )
151
157
  raise SystemExit(msg)
152
158
  if not re.match(PARAM_NAME_REGEX, parameter_name):
153
- msg = f"Invalid characters in parameter name {parameter_name} in {param_file} line {i}"
159
+ msg = _("Invalid characters in parameter name {parameter_name} in {param_file} line {i}").format(
160
+ parameter_name=parameter_name, param_file=param_file, i=i
161
+ )
154
162
  raise SystemExit(msg)
155
163
  if parameter_name in parameter_dict:
156
- msg = f"Duplicated parameter {parameter_name} in {param_file} line {i}"
164
+ msg = _("Duplicated parameter {parameter_name} in {param_file} line {i}").format(
165
+ parameter_name=parameter_name, param_file=param_file, i=i
166
+ )
157
167
  raise SystemExit(msg)
158
168
  try:
159
169
  fvalue = float(value)
160
170
  parameter_dict[parameter_name] = Par(fvalue, comment)
161
171
  except ValueError as exc:
162
- msg = f"Invalid parameter value {value} in {param_file} line {i}"
172
+ msg = _("Invalid parameter value {value} in {param_file} line {i}").format(value=value, param_file=param_file, i=i)
163
173
  raise SystemExit(msg) from exc
164
174
  except OSError as exc:
165
175
  _exc_type, exc_value, exc_traceback = sys_exc_info()
166
176
  if isinstance(exc_traceback, TracebackType):
167
177
  fname = os_path.split(exc_traceback.tb_frame.f_code.co_filename)[1]
168
178
  logging.critical("in line %s of file %s: %s", exc_traceback.tb_lineno, fname, exc_value)
169
- msg = f"Caused by line {i} of file {param_file}: {original_line}"
179
+ msg = _("Caused by line {i} of file {param_file}: {original_line}").format(
180
+ i=i, param_file=param_file, original_line=original_line
181
+ )
170
182
  raise SystemExit(msg) from exc
171
183
 
172
184
  @staticmethod
@@ -205,7 +217,7 @@ class ParDict(dict[str, Par]):
205
217
  elif file_format == "mavproxy":
206
218
  sorted_dict = ParDict(dict(sorted(self.items())))
207
219
  else:
208
- msg = f"ERROR: Unsupported file format {file_format}"
220
+ msg = _("ERROR: Unsupported file format {file_format}").format(file_format=file_format)
209
221
  raise SystemExit(msg)
210
222
 
211
223
  if file_format == "missionplanner":
@@ -277,10 +289,12 @@ class ParDict(dict[str, Par]):
277
289
  rows = int(rows_str) - 2 # -2 for the next print and the input line
278
290
 
279
291
  # Convert rows
280
- print(f"\n{name} has {len(formatted_params)} parameters:") # noqa: T201
292
+ print( # noqa: T201
293
+ _("\n{name} has {len_formatted_params} parameters:").format(name=name, len_formatted_params=len(formatted_params))
294
+ )
281
295
  for i, line in enumerate(formatted_params):
282
296
  if i % rows == 0 and __name__ == "__main__":
283
- input(f"\n{name} list is long hit enter to continue")
297
+ input(_("\n{name} list is long hit enter to continue").format(name=name))
284
298
  rows_str, _columns = os_popen("stty size", "r").read().split() # noqa: S605, S607
285
299
  rows = int(rows_str) - 2 # -2 for the next print and the input line
286
300
  print(line) # noqa: T201
@@ -3,7 +3,7 @@
3
3
  """
4
4
  Check for software updates and install them if available.
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 Lucas
9
9
 
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Vehicle template configuration overview information data model.
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
 
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Data model for vehicle components.
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
 
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Data model for vehicle components.
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
 
@@ -9,6 +9,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
9
9
  """
10
10
 
11
11
  # from logging import debug as logging_debug
12
+ from copy import deepcopy
12
13
  from logging import error as logging_error
13
14
  from logging import warning as logging_warning
14
15
  from typing import Any, Optional, Union
@@ -34,7 +35,7 @@ class ComponentDataModelBase:
34
35
  def __init__(
35
36
  self, initial_data: ComponentData, component_datatypes: dict[str, Any], schema: VehicleComponentsJsonSchema
36
37
  ) -> None:
37
- self._data: ComponentData = initial_data if initial_data else {"Components": {}, "Format version": 1}
38
+ self._data: ComponentData = deepcopy(initial_data) if initial_data else {"Components": {}, "Format version": 1}
38
39
  self._battery_chemistry: str = ""
39
40
  self._possible_choices: dict[ComponentPath, tuple[str, ...]] = {}
40
41
  self._mot_pwm_types: tuple[str, ...] = ()
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Data model for vehicle component display logic.
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
 
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Data model for vehicle components import from FC parameters.
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
 
@@ -199,6 +199,7 @@ class ComponentDataModelImport(ComponentDataModelBase):
199
199
 
200
200
  if component == "RC Receiver" and rc == 1:
201
201
  self.set_component_value(("RC Receiver", "FC Connection", "Type"), serial)
202
+ # Note: Protocol is set by RC_PROTOCOLS processing, not SERIAL_PROTOCOLS
202
203
  rc += 1
203
204
  elif component == "Telemetry" and telem == 1:
204
205
  self.set_component_value(("Telemetry", "FC Connection", "Type"), serial)
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Data model for vehicle components JSON schema.
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
 
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Data model interface for vehicle components templates.
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
 
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Data model for vehicle components.
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
 
@@ -36,6 +36,46 @@ RC_PORTS = ["RCin/SBUS"]
36
36
  SPI_PORTS = ["SPI"]
37
37
  OTHER_PORTS = ["other"]
38
38
 
39
+ # Bus labels for SERIAL ports - maps SERIAL port names to their common bus labels
40
+ # These labels help users identify ports by their typical usage on flight controllers:
41
+ # - Telem1/Telem2: Commonly used for telemetry connections
42
+ # - GPS1/GPS2: Commonly used for GNSS receiver connections
43
+ # - SERIAL5-8: No standard labels, use port name as label
44
+ SERIAL_BUS_LABELS: dict[str, str] = {
45
+ "SERIAL1": "Telem1 (SERIAL1)",
46
+ "SERIAL2": "Telem2 (SERIAL2)",
47
+ "SERIAL3": "GPS1 (SERIAL3)",
48
+ "SERIAL4": "GPS2 (SERIAL4)",
49
+ "SERIAL5": "SERIAL5",
50
+ "SERIAL6": "SERIAL6",
51
+ "SERIAL7": "SERIAL7",
52
+ "SERIAL8": "SERIAL8",
53
+ }
54
+
55
+
56
+ def get_connection_type_tuples_with_labels(connection_types: tuple[str, ...]) -> list[tuple[str, str]]:
57
+ """
58
+ Convert connection type values to tuples with bus labels for display.
59
+
60
+ Args:
61
+ connection_types: Tuple of connection type values (e.g., ("SERIAL1", "SERIAL2", ...))
62
+
63
+ Returns:
64
+ List of tuples where first element is the value and second is the display string.
65
+ For SERIAL ports, returns (value, "Label (value)"), e.g., ("SERIAL3", "GPS1 (SERIAL3)")
66
+ For other ports, returns (value, value), e.g., ("CAN1", "CAN1")
67
+
68
+ """
69
+ result = []
70
+ for conn_type in connection_types:
71
+ if conn_type in SERIAL_BUS_LABELS:
72
+ label = SERIAL_BUS_LABELS[conn_type]
73
+ result.append((conn_type, label))
74
+ else:
75
+ result.append((conn_type, conn_type))
76
+ return result
77
+
78
+
39
79
  # Map paths to component names for unified protocol update
40
80
  FC_CONNECTION_TYPE_PATHS: list[ComponentPath] = [
41
81
  ("RC Receiver", "FC Connection", "Type"),
@@ -5,7 +5,7 @@ This file contains the unified interface for all vehicle project operations,
5
5
  acting as a facade that coordinates between different data models and provides
6
6
  a single point of contact for the frontend layer.
7
7
 
8
- This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
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
 
@@ -4,7 +4,7 @@ Data model for vehicle project creation.
4
4
  This file contains the business logic for creating new vehicle configurations
5
5
  from templates, separated from the GUI layer.
6
6
 
7
- This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
7
+ This file is part of ArduPilot Methodic Configurator. https://github.com/ArduPilot/MethodicConfigurator
8
8
 
9
9
  SPDX-FileCopyrightText: 2024-2025 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>
10
10
 
@@ -4,7 +4,7 @@ Data model for vehicle project opening.
4
4
  This file contains the business logic for opening existing vehicle configurations,
5
5
  separated from the GUI layer.
6
6
 
7
- This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
7
+ This file is part of ArduPilot Methodic Configurator. https://github.com/ArduPilot/MethodicConfigurator
8
8
 
9
9
  SPDX-FileCopyrightText: 2024-2025 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>
10
10
 
@@ -1,4 +1,4 @@
1
- #!/usr/bin/python3
1
+ #!/usr/bin/env python3
2
2
  # PYTHON_ARGCOMPLETE_OK
3
3
 
4
4
  """
@@ -1,7 +1,7 @@
1
1
  """
2
2
  TKinter base classes reused in multiple parts of the code.
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,7 +11,7 @@ Key Features:
11
11
  - Graceful icon loading with fallback for test environments
12
12
  - Utility methods for window positioning and image handling
13
13
 
14
- This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
14
+ This file is part of ArduPilot Methodic Configurator. https://github.com/ArduPilot/MethodicConfigurator
15
15
 
16
16
  SPDX-FileCopyrightText: 2024-2025 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>
17
17
 
@@ -101,7 +101,7 @@ class BaseWindow:
101
101
  if root_tk:
102
102
  self.root = tk.Toplevel(root_tk)
103
103
  else:
104
- self.root = tk.Tk()
104
+ self.root = tk.Tk(className="ArduPilotMethodicConfigurator")
105
105
  # Only set icon for main windows, and only outside test environments
106
106
  self._setup_application_icon()
107
107
 
@@ -421,3 +421,7 @@ def show_error_popup(title: str, message: str) -> None:
421
421
 
422
422
  def ask_yesno_popup(title: str, message: str) -> bool:
423
423
  return messagebox.askyesno(title, message)
424
+
425
+
426
+ def ask_retry_cancel_popup(title: str, message: str) -> bool:
427
+ return messagebox.askretrycancel(title, message)
@@ -3,7 +3,7 @@
3
3
  """
4
4
  Data-dependent part of the component 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
 
@@ -27,9 +27,10 @@ from ardupilot_methodic_configurator.data_model_vehicle_components_base import C
27
27
  from ardupilot_methodic_configurator.data_model_vehicle_components_validation import (
28
28
  BATTERY_CELL_VOLTAGE_PATHS,
29
29
  FC_CONNECTION_TYPE_PATHS,
30
+ get_connection_type_tuples_with_labels,
30
31
  )
31
32
  from ardupilot_methodic_configurator.frontend_tkinter_component_editor_base import ComponentEditorWindowBase
32
- from ardupilot_methodic_configurator.frontend_tkinter_pair_tuple_combobox import setup_combobox_mousewheel_handling
33
+ from ardupilot_methodic_configurator.frontend_tkinter_pair_tuple_combobox import PairTupleCombobox
33
34
 
34
35
  # from ardupilot_methodic_configurator.frontend_tkinter_show import show_tooltip
35
36
  from ardupilot_methodic_configurator.frontend_tkinter_show import show_error_message, show_warning_message
@@ -109,20 +110,30 @@ class ComponentEditorWindow(ComponentEditorWindowBase):
109
110
  err_msg = ""
110
111
  if protocol_path in self.entry_widgets:
111
112
  protocol_combobox = self.entry_widgets[protocol_path]
112
- protocol_combobox["values"] = protocols # Update the combobox entries
113
- selected_protocol = protocol_combobox.get()
114
- if selected_protocol not in protocols and isinstance(protocol_combobox, ttk.Combobox):
115
- protocol_combobox.set("")
116
- _component: str = " > ".join(protocol_path)
117
- err_msg = _(
118
- "On {_component} the selected\nprotocol '{selected_protocol}' "
119
- "is not available for the selected connection Type."
120
- )
121
- err_msg = err_msg.format(**locals())
122
- if err_msg:
123
- show_error_message(_("Error"), err_msg)
124
- protocol_combobox.configure(style="comb_input_invalid.TCombobox")
125
- protocol_combobox.update_idletasks() # re-draw the combobox ASAP
113
+ # Only update if this is actually a PairTupleCombobox (protocol comboboxes should be)
114
+ if isinstance(protocol_combobox, PairTupleCombobox):
115
+ # Rebuild the (key, display) pairs for PairTupleCombobox
116
+ protocol_tuples = [(p, p) for p in protocols]
117
+ # Get current selection and validate it against new protocols
118
+ current_selection = protocol_combobox.get_selected_key()
119
+ if current_selection and current_selection not in protocols:
120
+ # Current selection is not valid for new protocols, clear it
121
+ invalid_selection = current_selection
122
+ current_selection = None
123
+ component: str = " > ".join(protocol_path)
124
+ err_msg = _(
125
+ "On {component} the selected\nprotocol '{invalid_selection}' "
126
+ "is not available for the selected connection Type."
127
+ )
128
+ err_msg = err_msg.format(component=component, invalid_selection=invalid_selection)
129
+
130
+ # Update the combobox entries using set_entries_tuple with validated selection
131
+ protocol_combobox.set_entries_tuple(protocol_tuples, current_selection)
132
+
133
+ if err_msg:
134
+ show_error_message(_("Error"), err_msg)
135
+ protocol_combobox.configure(style="comb_input_invalid.TCombobox")
136
+ protocol_combobox.update_idletasks() # re-draw the combobox ASAP
126
137
  return err_msg
127
138
 
128
139
  def update_cell_voltage_limits_entries(self, component_path: ComponentPath, chemistry: str) -> str:
@@ -163,7 +174,7 @@ class ComponentEditorWindow(ComponentEditorWindowBase):
163
174
 
164
175
  def add_entry_or_combobox(
165
176
  self, value: Union[str, float], entry_frame: ttk.Frame, path: ComponentPath, is_optional: bool = False
166
- ) -> Union[ttk.Entry, ttk.Combobox]:
177
+ ) -> Union[ttk.Entry, PairTupleCombobox]:
167
178
  # Get combobox values from data model
168
179
  combobox_values = self.data_model.get_combobox_values_for_path(path)
169
180
 
@@ -174,15 +185,27 @@ class ComponentEditorWindow(ComponentEditorWindowBase):
174
185
  return self._validate_combobox(event, path)
175
186
 
176
187
  if combobox_values:
177
- cb = ttk.Combobox(entry_frame, values=combobox_values, foreground=fg_color)
188
+ # Convert all combobox values to tuples for PairTupleCombobox
189
+ # For FC Connection Type paths, add bus labels; for others, use value as both key and display
190
+ if path in FC_CONNECTION_TYPE_PATHS:
191
+ combobox_tuples = get_connection_type_tuples_with_labels(combobox_values)
192
+ else:
193
+ combobox_tuples = [(val, val) for val in combobox_values]
194
+
195
+ # Always use PairTupleCombobox for consistency and simplicity
196
+ cb = PairTupleCombobox(
197
+ entry_frame,
198
+ combobox_tuples,
199
+ str(value) if value else None,
200
+ f"{' > '.join(path)}",
201
+ )
202
+ cb.config(foreground=fg_color)
203
+
178
204
  cb.bind("<FocusOut>", on_validate_combobox)
179
205
  cb.bind("<KeyRelease>", on_validate_combobox)
180
206
  cb.bind("<Return>", on_validate_combobox)
181
207
  cb.bind("<ButtonRelease>", on_validate_combobox)
182
208
 
183
- # Set up mouse wheel handling to prevent unwanted value changes
184
- setup_combobox_mousewheel_handling(cb)
185
-
186
209
  # Override the FocusOut binding to also handle validation
187
210
  def combined_focus_out(event: tk.Event) -> None:
188
211
  # First handle the dropdown closing logic (already done by setup function)
@@ -192,9 +215,10 @@ class ComponentEditorWindow(ComponentEditorWindowBase):
192
215
  cb.bind("<FocusOut>", combined_focus_out, add="+")
193
216
 
194
217
  if path in FC_CONNECTION_TYPE_PATHS:
195
- cb.bind( # immediate update of Protocol combobox choices after changing connection Type selection
218
+ # immediate update of Protocol combobox choices after changing connection Type selection
219
+ cb.bind(
196
220
  "<<ComboboxSelected>>",
197
- lambda event: self.update_component_protocol_combobox_entries(path, cb.get()), # noqa: ARG005
221
+ lambda _event: self.update_component_protocol_combobox_entries(path, cb.get_selected_key() or ""),
198
222
  )
199
223
 
200
224
  # When battery chemistry changes, the max, low and crit voltages will change to the
@@ -202,10 +226,9 @@ class ComponentEditorWindow(ComponentEditorWindowBase):
202
226
  if path == ("Battery", "Specifications", "Chemistry"):
203
227
  cb.bind(
204
228
  "<<ComboboxSelected>>",
205
- lambda event: self.update_cell_voltage_limits_entries(path, cb.get()), # noqa: ARG005
229
+ lambda _event: self.update_cell_voltage_limits_entries(path, cb.get_selected_key() or ""),
206
230
  )
207
231
 
208
- cb.set(value)
209
232
  return cb
210
233
 
211
234
  entry = ttk.Entry(entry_frame, foreground=fg_color)
@@ -220,12 +243,16 @@ class ComponentEditorWindow(ComponentEditorWindowBase):
220
243
  return entry
221
244
 
222
245
  def _validate_combobox(self, event: tk.Event, path: ComponentPath) -> bool:
223
- """Validates the value of a combobox."""
246
+ """Validates the value of a PairTupleCombobox."""
224
247
  combobox = event.widget # Get the combobox widget that triggered the event
225
- if not isinstance(combobox, ttk.Combobox):
248
+ if not isinstance(combobox, PairTupleCombobox):
226
249
  return False
227
- value = combobox.get() # Get the current value of the combobox
228
- allowed_values = combobox.cget("values") # Get the list of allowed values
250
+
251
+ # Get the selected key (internal value)
252
+ value = combobox.get_selected_key() or ""
253
+
254
+ # Get allowed values from list_keys
255
+ allowed_values = combobox.list_keys
229
256
 
230
257
  # Events that should trigger data model update (when value is valid)
231
258
  should_update_data_model = event.type in {
@@ -3,7 +3,7 @@
3
3
  """
4
4
  Component editor GUI that is not data dependent.
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
 
@@ -33,6 +33,7 @@ from ardupilot_methodic_configurator.data_model_vehicle_components_json_schema i
33
33
  from ardupilot_methodic_configurator.frontend_tkinter_base_window import BaseWindow
34
34
  from ardupilot_methodic_configurator.frontend_tkinter_component_template_manager import ComponentTemplateManager
35
35
  from ardupilot_methodic_configurator.frontend_tkinter_font import create_scaled_font, get_safe_font_config
36
+ from ardupilot_methodic_configurator.frontend_tkinter_pair_tuple_combobox import PairTupleCombobox
36
37
  from ardupilot_methodic_configurator.frontend_tkinter_rich_text import RichText
37
38
  from ardupilot_methodic_configurator.frontend_tkinter_scroll_frame import ScrollFrame
38
39
  from ardupilot_methodic_configurator.frontend_tkinter_show import show_error_message, show_tooltip
@@ -63,7 +64,7 @@ def argument_parser() -> Namespace:
63
64
 
64
65
 
65
66
  # Type aliases to improve code readability
66
- EntryWidget = Union[ttk.Entry, ttk.Combobox]
67
+ EntryWidget = Union[ttk.Entry, PairTupleCombobox]
67
68
 
68
69
  WINDOW_WIDTH_PIX = 880
69
70
  VEHICLE_IMAGE_WIDTH_PIX = 100
@@ -293,6 +294,10 @@ class ComponentEditorWindowBase(BaseWindow): # pylint: disable=too-many-instanc
293
294
 
294
295
  def _display_component_editor_usage_instructions(self, parent: tk.Tk) -> None:
295
296
  """Display usage instructions for the component editor."""
297
+ # Check if parent window still exists before showing popup
298
+ if not parent.winfo_exists():
299
+ return
300
+
296
301
  # pylint: disable=duplicate-code
297
302
  usage_popup_window = BaseWindow(parent)
298
303
  style = ttk.Style()
@@ -482,8 +487,11 @@ class ComponentEditorWindowBase(BaseWindow): # pylint: disable=too-many-instanc
482
487
  def validate_data_and_highlight_errors_in_red(self) -> str:
483
488
  """Validate all data using the data model."""
484
489
  # Collect all entry values
490
+ # For PairTupleCombobox, get the selected key; for ttk.Entry, get the text
485
491
  entry_values = {
486
- path: entry.get() for path, entry in self.entry_widgets.items() if isinstance(entry, (ttk.Entry, ttk.Combobox))
492
+ path: entry.get_selected_key() or "" if isinstance(entry, PairTupleCombobox) else entry.get()
493
+ for path, entry in self.entry_widgets.items()
494
+ if isinstance(entry, (PairTupleCombobox, ttk.Entry))
487
495
  }
488
496
 
489
497
  # Use data model for validation
@@ -491,19 +499,16 @@ class ComponentEditorWindowBase(BaseWindow): # pylint: disable=too-many-instanc
491
499
 
492
500
  if not is_valid:
493
501
  # Update UI to show invalid states and display errors
494
- for path, entry in (
495
- (path, entry) for path, entry in self.entry_widgets.items() if isinstance(entry, (ttk.Entry, ttk.Combobox))
496
- ):
497
- value = entry.get()
498
-
499
- # Check combobox validation
500
- if isinstance(entry, ttk.Combobox):
501
- combobox_values = self.data_model.get_combobox_values_for_path(path)
502
- if combobox_values and value not in combobox_values:
502
+ for path, entry in self.entry_widgets.items():
503
+ if isinstance(entry, PairTupleCombobox):
504
+ value = entry.get_selected_key() or ""
505
+ # Check combobox validation
506
+ if value not in entry.list_keys:
503
507
  entry.configure(style="comb_input_invalid.TCombobox")
504
508
  else:
505
509
  entry.configure(style="comb_input_valid.TCombobox")
506
- else:
510
+ elif isinstance(entry, ttk.Entry):
511
+ value = entry.get()
507
512
  # Check entry validation
508
513
  error_message, _corr = self.data_model.validate_entry_limits(value, path)
509
514
  if error_message:
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Component template manager for the ArduPilot methodic configurator.
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>, 2025 Francisco Fonseca
7
7
 
@@ -3,7 +3,7 @@
3
3
  """
4
4
  GUI to select the connection to the FC.
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
 
@@ -1,7 +1,7 @@
1
1
  """
2
2
  GUI to select the directory to store the vehicle configuration files.
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
 
@@ -3,7 +3,7 @@
3
3
  """
4
4
  GUI entry Widget with autocompletion features.
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
  https://code.activestate.com/recipes/580770-combobox-autocomplete/
9
9
 
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Flight controller information GUI.
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
 
@@ -10,7 +10,7 @@ The module offers functions to safely retrieve font configurations, families,
10
10
  and sizes with appropriate platform-specific fallbacks. This ensures consistent
11
11
  text rendering across Windows, macOS, and Linux systems.
12
12
 
13
- This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
13
+ This file is part of ArduPilot Methodic Configurator. https://github.com/ArduPilot/MethodicConfigurator
14
14
 
15
15
  SPDX-FileCopyrightText: 2024-2025 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>
16
16
 
@@ -13,7 +13,7 @@ The MotorTestView class provides:
13
13
 
14
14
  MotorTestWindow class provides a standalone window for development/testing.
15
15
 
16
- This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
16
+ This file is part of ArduPilot Methodic Configurator. https://github.com/ArduPilot/MethodicConfigurator
17
17
 
18
18
  SPDX-FileCopyrightText: 2024-2025 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>
19
19