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.
- 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.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/METADATA +11 -6
- {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/RECORD +106 -106
- {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/WHEEL +0 -0
- {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/entry_points.txt +0 -0
- {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSE.md +0 -0
- {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/Apache-2.0.txt +0 -0
- {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/BSD-3-Clause.txt +0 -0
- {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
- {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
- {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/MIT-CMU.txt +0 -0
- {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/MIT.txt +0 -0
- {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/MPL-2.0.txt +0 -0
- {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/LICENSES/PSF-2.0.txt +0 -0
- {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/licenses/credits/CREDITS.md +0 -0
- {ardupilot_methodic_configurator-2.6.1.dist-info → ardupilot_methodic_configurator-2.7.0.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Python package initialization file. Loads translations and declares version information.
|
|
3
3
|
|
|
4
|
-
This file is part of
|
|
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
|
|
|
@@ -12,4 +12,4 @@ from ardupilot_methodic_configurator.internationalization import load_translatio
|
|
|
12
12
|
|
|
13
13
|
_ = load_translation()
|
|
14
14
|
|
|
15
|
-
__version__ = "2.
|
|
15
|
+
__version__ = "2.7.0"
|
|
@@ -11,7 +11,7 @@ Calls five sub-applications in sequence:
|
|
|
11
11
|
4. Component and connection editor
|
|
12
12
|
5. Parameter editor and uploader
|
|
13
13
|
|
|
14
|
-
This file is part of
|
|
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
|
|
|
@@ -19,6 +19,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
import argparse
|
|
22
|
+
import os
|
|
22
23
|
from logging import basicConfig as logging_basicConfig
|
|
23
24
|
from logging import debug as logging_debug
|
|
24
25
|
from logging import error as logging_error
|
|
@@ -497,6 +498,9 @@ def main() -> None:
|
|
|
497
498
|
"""
|
|
498
499
|
args = create_argument_parser().parse_args()
|
|
499
500
|
|
|
501
|
+
# Create desktop icon if needed (only on first run in venv)
|
|
502
|
+
ProgramSettings.create_desktop_icon_if_needed()
|
|
503
|
+
|
|
500
504
|
state = ApplicationState(args)
|
|
501
505
|
|
|
502
506
|
setup_logging(state)
|
|
@@ -517,6 +521,26 @@ def main() -> None:
|
|
|
517
521
|
if not files:
|
|
518
522
|
vehicle_directory_selection(state)
|
|
519
523
|
|
|
524
|
+
if (
|
|
525
|
+
state.flight_controller.fc_parameters
|
|
526
|
+
and state.flight_controller.info.flight_sw_version.startswith("4.6.")
|
|
527
|
+
and state.local_filesystem.doc_dict
|
|
528
|
+
and "FSTRATE_ENABLE" in state.local_filesystem.doc_dict
|
|
529
|
+
):
|
|
530
|
+
show_error_message(
|
|
531
|
+
_("Incompatible parameter definition file detected"),
|
|
532
|
+
_(
|
|
533
|
+
"The parameter definition file 'apm.pdef.xml' is incompatible with ArduPilot 4.6.x firmware. "
|
|
534
|
+
"It appears to be from the master branch. The file will be deleted. "
|
|
535
|
+
"Please restart the ArduPilot Methodic Configurator."
|
|
536
|
+
),
|
|
537
|
+
)
|
|
538
|
+
# delete apm.pdef.xml file as it is from master and not from 4.6.x
|
|
539
|
+
file_path = os.path.join(state.local_filesystem.vehicle_dir, "apm.pdef.xml")
|
|
540
|
+
if os.path.exists(file_path):
|
|
541
|
+
os.remove(file_path)
|
|
542
|
+
sys_exit(1)
|
|
543
|
+
|
|
520
544
|
# Run component editor workflow
|
|
521
545
|
component_editor(state)
|
|
522
546
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/python3
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
2
|
# PYTHON_ARGCOMPLETE_OK
|
|
3
3
|
|
|
4
4
|
"""
|
|
@@ -133,7 +133,9 @@ def parse_arguments() -> argparse.Namespace:
|
|
|
133
133
|
return args
|
|
134
134
|
|
|
135
135
|
|
|
136
|
-
def get_xml_data(
|
|
136
|
+
def get_xml_data( # pylint: disable=too-many-locals, too-many-statements # noqa: PLR0915
|
|
137
|
+
base_url: str, directory: str, filename: str, vehicle_type: str, fallback_xml_url: Optional[str] = None
|
|
138
|
+
) -> ET.Element:
|
|
137
139
|
"""
|
|
138
140
|
Fetches XML data from a local file or a URL.
|
|
139
141
|
|
|
@@ -142,6 +144,7 @@ def get_xml_data(base_url: str, directory: str, filename: str, vehicle_type: str
|
|
|
142
144
|
directory (str): The directory where the XML file is expected.
|
|
143
145
|
filename (str): The name of the XML file.
|
|
144
146
|
vehicle_type (str): The type of the vehicle.
|
|
147
|
+
fallback_xml_url (Optional[str]): Fallback URL if the main URL fails.
|
|
145
148
|
|
|
146
149
|
Returns:
|
|
147
150
|
ET.Element: The root element of the parsed XML data.
|
|
@@ -183,19 +186,32 @@ def get_xml_data(base_url: str, directory: str, filename: str, vehicle_type: str
|
|
|
183
186
|
logging.warning("Unable to fetch XML data: %s", e)
|
|
184
187
|
# Send a GET request to the URL to the fallback (DEV) URL
|
|
185
188
|
try:
|
|
186
|
-
|
|
187
|
-
|
|
189
|
+
if fallback_xml_url is None:
|
|
190
|
+
msg = "No fallback XML URL provided."
|
|
191
|
+
raise ValueError(msg) from e
|
|
192
|
+
url = fallback_xml_url
|
|
193
|
+
logging.warning("Falling back to the latest stable release XML file: %s", url)
|
|
188
194
|
response = requests_get(url, timeout=5, proxies=proxies)
|
|
189
195
|
if response.status_code != 200:
|
|
190
|
-
logging.
|
|
196
|
+
logging.warning("Remote URL: %s", url)
|
|
191
197
|
msg = f"HTTP status code {response.status_code}"
|
|
192
198
|
raise requests_exceptions.RequestException(msg)
|
|
193
|
-
except requests_exceptions.RequestException as
|
|
194
|
-
logging.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
+
except (ValueError, requests_exceptions.RequestException) as ex:
|
|
200
|
+
logging.warning("Unable to fetch XML data: %s", ex)
|
|
201
|
+
try:
|
|
202
|
+
url = BASE_URL + vehicle_type + "/" + PARAM_DEFINITION_XML_FILE
|
|
203
|
+
logging.warning("Falling back to the DEV XML file: %s", url)
|
|
204
|
+
response = requests_get(url, timeout=5, proxies=proxies)
|
|
205
|
+
if response.status_code != 200:
|
|
206
|
+
logging.critical("Remote URL: %s", url)
|
|
207
|
+
msg = f"HTTP status code {response.status_code}"
|
|
208
|
+
raise requests_exceptions.RequestException(msg)
|
|
209
|
+
except requests_exceptions.RequestException as exp:
|
|
210
|
+
logging.critical("Unable to fetch XML data: %s", exp)
|
|
211
|
+
msg = "Unable to fetch online XML documentation."
|
|
212
|
+
msg += f"\nDownload it manually from {url} and"
|
|
213
|
+
msg += f"\nplace it in the {directory} directory"
|
|
214
|
+
raise SystemExit(msg) from exp
|
|
199
215
|
# Get the text content of the response
|
|
200
216
|
xml_data = response.text
|
|
201
217
|
try:
|
|
@@ -599,10 +615,29 @@ def get_xml_url(vehicle_type: str, firmware_version: str) -> str:
|
|
|
599
615
|
return xml_url
|
|
600
616
|
|
|
601
617
|
|
|
602
|
-
def
|
|
603
|
-
|
|
618
|
+
def get_fallback_xml_url(vehicle_type: str, firmware_version: str) -> str:
|
|
619
|
+
vehicle_parm_subdir = {
|
|
620
|
+
"ArduCopter": "Copter-",
|
|
621
|
+
"ArduPlane": "Plane-",
|
|
622
|
+
"Rover": "Rover-",
|
|
623
|
+
"ArduSub": "Sub-",
|
|
624
|
+
}
|
|
625
|
+
try:
|
|
626
|
+
vehicle_subdir = vehicle_parm_subdir[vehicle_type] + firmware_version[0:3]
|
|
627
|
+
except KeyError as e:
|
|
628
|
+
msg = f"Vehicle type '{vehicle_type}' is not supported."
|
|
629
|
+
raise ValueError(msg) from e
|
|
630
|
+
|
|
631
|
+
xml_url = "https://raw.githubusercontent.com/ArduPilot/ParameterRepository/refs/heads/main/"
|
|
632
|
+
xml_url += vehicle_subdir if firmware_version else vehicle_type
|
|
633
|
+
xml_url += "/" + PARAM_DEFINITION_XML_FILE
|
|
634
|
+
return xml_url
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def parse_parameter_metadata( # pylint: disable=too-many-arguments, too-many-positional-arguments
|
|
638
|
+
xml_url: str, xml_dir: str, xml_file: str, vehicle_type: str, max_line_length: int, fallback_xml_url: Optional[str] = None
|
|
604
639
|
) -> dict[str, Any]:
|
|
605
|
-
xml_root = get_xml_data(xml_url, xml_dir, xml_file, vehicle_type)
|
|
640
|
+
xml_root = get_xml_data(xml_url, xml_dir, xml_file, vehicle_type, fallback_xml_url)
|
|
606
641
|
return create_doc_dict(xml_root, vehicle_type, max_line_length)
|
|
607
642
|
|
|
608
643
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Check the range of an Argparse parameter.
|
|
3
3
|
|
|
4
|
-
This file is part of
|
|
4
|
+
This file is part of ArduPilot Methodic Configurator. https://github.com/ArduPilot/MethodicConfigurator
|
|
5
5
|
|
|
6
6
|
SPDX-FileCopyrightText: 2024 Dmitriy Kovalev
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Filesystem operations.
|
|
3
3
|
|
|
4
|
-
This file is part of
|
|
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
|
|
|
@@ -38,6 +38,7 @@ from ardupilot_methodic_configurator import _
|
|
|
38
38
|
from ardupilot_methodic_configurator.annotate_params import (
|
|
39
39
|
PARAM_DEFINITION_XML_FILE,
|
|
40
40
|
format_columns,
|
|
41
|
+
get_fallback_xml_url,
|
|
41
42
|
get_xml_dir,
|
|
42
43
|
get_xml_url,
|
|
43
44
|
load_default_param_file,
|
|
@@ -122,8 +123,11 @@ class LocalFilesystem(VehicleComponents, ConfigurationSteps, ProgramSettings):
|
|
|
122
123
|
|
|
123
124
|
# Read ArduPilot parameter documentation
|
|
124
125
|
xml_url = get_xml_url(vehicle_type, self.fw_version)
|
|
126
|
+
fallback_xml_url = get_fallback_xml_url(vehicle_type, self.fw_version)
|
|
125
127
|
xml_dir = get_xml_dir(vehicle_dir)
|
|
126
|
-
self.doc_dict = parse_parameter_metadata(
|
|
128
|
+
self.doc_dict = parse_parameter_metadata(
|
|
129
|
+
xml_url, xml_dir, PARAM_DEFINITION_XML_FILE, vehicle_type, TOOLTIP_MAX_LENGTH, fallback_xml_url
|
|
130
|
+
)
|
|
127
131
|
self.param_default_dict = load_default_param_file(vehicle_dir)
|
|
128
132
|
|
|
129
133
|
# Extend parameter documentation metadata if <parameter_file>.pdef.xml exists
|
|
@@ -575,7 +579,7 @@ class LocalFilesystem(VehicleComponents, ConfigurationSteps, ProgramSettings):
|
|
|
575
579
|
dest = os_path.join(new_vehicle_dir, item)
|
|
576
580
|
if blank_change_reason and item.endswith(".param"):
|
|
577
581
|
# Blank the change reason in the template files, strip the comments that start with #
|
|
578
|
-
with open(source, encoding="utf-8") as file:
|
|
582
|
+
with open(source, encoding="utf-8-sig") as file:
|
|
579
583
|
lines = file.readlines()
|
|
580
584
|
with open(dest, "w", encoding="utf-8") as file:
|
|
581
585
|
file.writelines(line.split("#")[0].strip() + "\n" for line in lines)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Manages configuration steps at the filesystem level.
|
|
3
3
|
|
|
4
|
-
This file is part of
|
|
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,14 +10,14 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
10
10
|
|
|
11
11
|
from json import JSONDecodeError
|
|
12
12
|
from json import load as json_load
|
|
13
|
-
|
|
14
|
-
# from sys import exit as sys_exit
|
|
15
|
-
# from logging import debug as logging_debug
|
|
16
13
|
from logging import error as logging_error
|
|
17
14
|
from logging import info as logging_info
|
|
18
15
|
from logging import warning as logging_warning
|
|
19
16
|
from os import path as os_path
|
|
17
|
+
from typing import TypedDict
|
|
20
18
|
|
|
19
|
+
# from sys import exit as sys_exit
|
|
20
|
+
# from logging import debug as logging_debug
|
|
21
21
|
from jsonschema import validate as json_validate
|
|
22
22
|
from jsonschema.exceptions import ValidationError
|
|
23
23
|
|
|
@@ -25,6 +25,26 @@ from ardupilot_methodic_configurator import _
|
|
|
25
25
|
from ardupilot_methodic_configurator.data_model_par_dict import Par, ParDict
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
class PhaseData(TypedDict, total=False):
|
|
29
|
+
"""
|
|
30
|
+
Type definition for configuration phase data.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
start: The starting file number for this phase
|
|
34
|
+
end: The ending file number for this phase (computed)
|
|
35
|
+
weight: The weight for UI layout proportions (computed)
|
|
36
|
+
description: Human-readable description of the phase
|
|
37
|
+
optional: Whether this phase is optional
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
start: int
|
|
42
|
+
end: int
|
|
43
|
+
weight: int
|
|
44
|
+
description: str
|
|
45
|
+
optional: bool
|
|
46
|
+
|
|
47
|
+
|
|
28
48
|
class ConfigurationSteps:
|
|
29
49
|
"""
|
|
30
50
|
A class to manage configuration steps for the ArduPilot methodic configurator.
|
|
@@ -41,7 +61,7 @@ class ConfigurationSteps:
|
|
|
41
61
|
def __init__(self, _vehicle_dir: str, vehicle_type: str) -> None:
|
|
42
62
|
self.configuration_steps_filename = "configuration_steps_" + vehicle_type + ".json"
|
|
43
63
|
self.configuration_steps: dict[str, dict] = {}
|
|
44
|
-
self.configuration_phases: dict[str,
|
|
64
|
+
self.configuration_phases: dict[str, PhaseData] = {}
|
|
45
65
|
self.forced_parameters: dict[str, ParDict] = {}
|
|
46
66
|
self.derived_parameters: dict[str, ParDict] = {}
|
|
47
67
|
self.log_loaded_file = False
|
|
@@ -56,7 +76,7 @@ class ConfigurationSteps:
|
|
|
56
76
|
json_content = {}
|
|
57
77
|
for i, directory in enumerate(search_directories):
|
|
58
78
|
try:
|
|
59
|
-
with open(os_path.join(directory, self.configuration_steps_filename), encoding="utf-8") as file:
|
|
79
|
+
with open(os_path.join(directory, self.configuration_steps_filename), encoding="utf-8-sig") as file:
|
|
60
80
|
json_content = json_load(file)
|
|
61
81
|
file_found = True
|
|
62
82
|
if self.log_loaded_file:
|
|
@@ -246,3 +266,30 @@ class ConfigurationSteps:
|
|
|
246
266
|
text = _("No documentation available for {selected_file} in the {self.configuration_steps_filename} file")
|
|
247
267
|
text = documentation.get(tooltip_key, text.format(**locals()))
|
|
248
268
|
return text
|
|
269
|
+
|
|
270
|
+
def get_sorted_phases_with_end_and_weight(self, total_files: int) -> dict[str, PhaseData]:
|
|
271
|
+
"""
|
|
272
|
+
Get sorted phases with added 'end' and 'weight' information.
|
|
273
|
+
|
|
274
|
+
Returns phases sorted by start position, with each phase containing:
|
|
275
|
+
- 'end': The end file number (start of next phase or total_files)
|
|
276
|
+
- 'weight': Weight for UI layout (max(2, end - start))
|
|
277
|
+
"""
|
|
278
|
+
active_phases = {k: v for k, v in self.configuration_phases.items() if "start" in v}
|
|
279
|
+
|
|
280
|
+
# Sort phases by start position
|
|
281
|
+
sorted_phases: dict[str, PhaseData] = dict(sorted(active_phases.items(), key=lambda x: x[1].get("start", 0)))
|
|
282
|
+
|
|
283
|
+
# Add the end information to each phase using the start of the next phase
|
|
284
|
+
phase_names = list(sorted_phases.keys())
|
|
285
|
+
for i, phase_name in enumerate(phase_names):
|
|
286
|
+
if i < len(phase_names) - 1:
|
|
287
|
+
next_phase_name = phase_names[i + 1]
|
|
288
|
+
sorted_phases[phase_name]["end"] = sorted_phases[next_phase_name].get("start", total_files)
|
|
289
|
+
else:
|
|
290
|
+
sorted_phases[phase_name]["end"] = total_files
|
|
291
|
+
phase_start = sorted_phases[phase_name].get("start", 0)
|
|
292
|
+
phase_end = sorted_phases[phase_name].get("end", total_files)
|
|
293
|
+
sorted_phases[phase_name]["weight"] = max(2, phase_end - phase_start)
|
|
294
|
+
|
|
295
|
+
return sorted_phases
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Manages JSON files at the filesystem level.
|
|
3
3
|
|
|
4
|
-
This file is part of
|
|
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
|
|
|
@@ -46,7 +46,7 @@ class FilesystemJSONWithSchema:
|
|
|
46
46
|
schema_path = os_path.join(os_path.dirname(__file__), self.schema_filename)
|
|
47
47
|
|
|
48
48
|
try:
|
|
49
|
-
with open(schema_path, encoding="utf-8") as file:
|
|
49
|
+
with open(schema_path, encoding="utf-8-sig") as file:
|
|
50
50
|
loaded_schema: dict[Any, Any] = json_load(file)
|
|
51
51
|
|
|
52
52
|
# Validate the schema itself against the JSON Schema meta-schema
|
|
@@ -90,7 +90,7 @@ class FilesystemJSONWithSchema:
|
|
|
90
90
|
data: dict[Any, Any] = {}
|
|
91
91
|
filepath = os_path.join(data_dir, self.json_filename)
|
|
92
92
|
try:
|
|
93
|
-
with open(filepath, encoding="utf-8") as file:
|
|
93
|
+
with open(filepath, encoding="utf-8-sig") as file:
|
|
94
94
|
data = json_load(file)
|
|
95
95
|
|
|
96
96
|
# Validate the loaded data against the schema
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Manages program settings at the filesystem level.
|
|
3
3
|
|
|
4
|
-
This file is part of
|
|
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,13 +9,18 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
# from sys import exit as sys_exit
|
|
12
|
-
import
|
|
12
|
+
import subprocess
|
|
13
|
+
from contextlib import suppress as contextlib_suppress
|
|
14
|
+
from glob import glob as glob_glob
|
|
13
15
|
from importlib.resources import files as importlib_files
|
|
14
16
|
from json import dump as json_dump
|
|
15
17
|
from json import load as json_load
|
|
16
18
|
from logging import debug as logging_debug
|
|
17
19
|
from logging import error as logging_error
|
|
20
|
+
from os import chmod as os_chmod
|
|
21
|
+
from os import environ as os_environ
|
|
18
22
|
from os import makedirs as os_makedirs
|
|
23
|
+
from os import name as os_name
|
|
19
24
|
from os import path as os_path
|
|
20
25
|
from os import sep as os_sep
|
|
21
26
|
from pathlib import Path
|
|
@@ -23,6 +28,8 @@ from platform import system as platform_system
|
|
|
23
28
|
from re import escape as re_escape
|
|
24
29
|
from re import match as re_match
|
|
25
30
|
from re import sub as re_sub
|
|
31
|
+
from shutil import which as shutil_which
|
|
32
|
+
from sys import platform as sys_platform
|
|
26
33
|
from typing import Any, Optional, Union
|
|
27
34
|
|
|
28
35
|
from platformdirs import site_config_dir, user_config_dir
|
|
@@ -108,8 +115,18 @@ class ProgramSettings:
|
|
|
108
115
|
|
|
109
116
|
@staticmethod
|
|
110
117
|
def application_icon_filepath() -> str:
|
|
111
|
-
|
|
112
|
-
|
|
118
|
+
"""Get the application icon path, with fallback options."""
|
|
119
|
+
try:
|
|
120
|
+
package_path = importlib_files("ardupilot_methodic_configurator")
|
|
121
|
+
except (ImportError, FileNotFoundError):
|
|
122
|
+
# Fallback: try to find icon relative to the script
|
|
123
|
+
package_path = Path(os_path.dirname(os_path.abspath(__file__)))
|
|
124
|
+
|
|
125
|
+
icon_path = str(package_path / "images" / "ArduPilot_icon.png")
|
|
126
|
+
if os_path.exists(icon_path):
|
|
127
|
+
return icon_path
|
|
128
|
+
# If no icon found, return empty string (GUI will handle the error)
|
|
129
|
+
return ""
|
|
113
130
|
|
|
114
131
|
@staticmethod
|
|
115
132
|
def application_logo_filepath() -> str:
|
|
@@ -152,9 +169,12 @@ class ProgramSettings:
|
|
|
152
169
|
@staticmethod
|
|
153
170
|
def _user_config_dir() -> str:
|
|
154
171
|
user_config_directory = user_config_dir(
|
|
155
|
-
".ardupilot_methodic_configurator", appauthor=False, roaming=True, ensure_exists=
|
|
172
|
+
".ardupilot_methodic_configurator", appauthor=False, roaming=True, ensure_exists=False
|
|
156
173
|
)
|
|
157
174
|
|
|
175
|
+
if not os_path.exists(user_config_directory):
|
|
176
|
+
os_makedirs(user_config_directory, exist_ok=True)
|
|
177
|
+
|
|
158
178
|
if not os_path.exists(user_config_directory):
|
|
159
179
|
error_msg = _("The user configuration directory '{user_config_directory}' does not exist.")
|
|
160
180
|
raise FileNotFoundError(error_msg.format(**locals()))
|
|
@@ -167,9 +187,13 @@ class ProgramSettings:
|
|
|
167
187
|
@staticmethod
|
|
168
188
|
def _site_config_dir() -> str:
|
|
169
189
|
site_config_directory = site_config_dir(
|
|
170
|
-
".ardupilot_methodic_configurator", appauthor=False, version=None, multipath=False, ensure_exists=
|
|
190
|
+
".ardupilot_methodic_configurator", appauthor=False, version=None, multipath=False, ensure_exists=False
|
|
171
191
|
)
|
|
172
192
|
|
|
193
|
+
if not os_path.exists(site_config_directory):
|
|
194
|
+
with contextlib_suppress(OSError):
|
|
195
|
+
os_makedirs(site_config_directory, exist_ok=True)
|
|
196
|
+
|
|
173
197
|
if not os_path.exists(site_config_directory):
|
|
174
198
|
error_msg = _("The site configuration directory '{site_config_directory}' does not exist.")
|
|
175
199
|
raise FileNotFoundError(error_msg.format(**locals()))
|
|
@@ -189,7 +213,7 @@ class ProgramSettings:
|
|
|
189
213
|
|
|
190
214
|
"""
|
|
191
215
|
try:
|
|
192
|
-
with open(settings_path, encoding="utf-8") as settings_file:
|
|
216
|
+
with open(settings_path, encoding="utf-8-sig") as settings_file:
|
|
193
217
|
loaded_settings: dict[str, Any] = json_load(settings_file)
|
|
194
218
|
return loaded_settings
|
|
195
219
|
except FileNotFoundError:
|
|
@@ -365,7 +389,7 @@ class ProgramSettings:
|
|
|
365
389
|
filename = f"m_{frame_class:02d}_{frame_type:02d}_*.png"
|
|
366
390
|
|
|
367
391
|
# Search for matching PNG file (since exact naming varies)
|
|
368
|
-
matching_files =
|
|
392
|
+
matching_files = glob_glob(str(images_dir / filename))
|
|
369
393
|
|
|
370
394
|
err_msg = (
|
|
371
395
|
""
|
|
@@ -398,3 +422,116 @@ class ProgramSettings:
|
|
|
398
422
|
"""
|
|
399
423
|
filepath, _error_msg = ProgramSettings.motor_diagram_filepath(frame_class, frame_type)
|
|
400
424
|
return filepath != "" and os_path.exists(filepath)
|
|
425
|
+
|
|
426
|
+
@staticmethod
|
|
427
|
+
def _is_linux_system() -> bool:
|
|
428
|
+
"""Check if running on a Linux system."""
|
|
429
|
+
return os_name == "posix" and sys_platform.startswith("linux")
|
|
430
|
+
|
|
431
|
+
@staticmethod
|
|
432
|
+
def _get_desktop_file_path() -> str:
|
|
433
|
+
"""Get the path where the desktop file should be created."""
|
|
434
|
+
return os_path.expanduser("~/.local/share/applications/ardupilot_methodic_configurator.desktop")
|
|
435
|
+
|
|
436
|
+
@staticmethod
|
|
437
|
+
def _desktop_icon_exists(desktop_file_path: str) -> bool:
|
|
438
|
+
"""Check if the desktop icon already exists."""
|
|
439
|
+
return os_path.exists(desktop_file_path)
|
|
440
|
+
|
|
441
|
+
@staticmethod
|
|
442
|
+
def _get_virtual_env_path() -> Optional[str]:
|
|
443
|
+
"""Get the virtual environment path from environment variables."""
|
|
444
|
+
return os_environ.get("VIRTUAL_ENV")
|
|
445
|
+
|
|
446
|
+
@staticmethod
|
|
447
|
+
def _create_desktop_entry_content(venv_path: str, icon_path: str) -> str:
|
|
448
|
+
"""Create the desktop entry file content."""
|
|
449
|
+
# Try to use python executable directly for better compatibility
|
|
450
|
+
python_exe = os_path.join(venv_path, "bin", "python")
|
|
451
|
+
if os_path.exists(python_exe):
|
|
452
|
+
# Use python executable directly
|
|
453
|
+
exec_cmd = f"{python_exe} -m ardupilot_methodic_configurator"
|
|
454
|
+
else:
|
|
455
|
+
# Fallback to bash -c method
|
|
456
|
+
bash_path = shutil_which("bash") or "/bin/bash"
|
|
457
|
+
activate_cmd = f"source {venv_path}/bin/activate && ardupilot_methodic_configurator"
|
|
458
|
+
exec_cmd = f'{bash_path} -c "{activate_cmd}"'
|
|
459
|
+
|
|
460
|
+
return f"""[Desktop Entry]
|
|
461
|
+
Version=1.0
|
|
462
|
+
Name=ArduPilot Methodic Configurator
|
|
463
|
+
Comment=A clear ArduPilot configuration sequence
|
|
464
|
+
Exec={exec_cmd}
|
|
465
|
+
Icon={icon_path}
|
|
466
|
+
Terminal=true
|
|
467
|
+
Type=Application
|
|
468
|
+
Categories=Development;
|
|
469
|
+
Keywords=ardupilot;arducopter;drone;parameters;configuration;scm
|
|
470
|
+
"""
|
|
471
|
+
|
|
472
|
+
@staticmethod
|
|
473
|
+
def _ensure_applications_dir_exists(desktop_file_path: str) -> str:
|
|
474
|
+
"""Ensure the applications directory exists and return it."""
|
|
475
|
+
apps_dir = os_path.dirname(desktop_file_path)
|
|
476
|
+
os_makedirs(apps_dir, exist_ok=True)
|
|
477
|
+
return apps_dir
|
|
478
|
+
|
|
479
|
+
@staticmethod
|
|
480
|
+
def _write_desktop_file(desktop_file_path: str, content: str) -> None:
|
|
481
|
+
"""Write the desktop file content to disk."""
|
|
482
|
+
with open(desktop_file_path, "w", encoding="utf-8") as f:
|
|
483
|
+
f.write(content)
|
|
484
|
+
|
|
485
|
+
@staticmethod
|
|
486
|
+
def _set_desktop_file_permissions(desktop_file_path: str) -> None:
|
|
487
|
+
"""Set appropriate permissions on the desktop file."""
|
|
488
|
+
os_chmod(desktop_file_path, 0o644)
|
|
489
|
+
|
|
490
|
+
@staticmethod
|
|
491
|
+
def _update_desktop_database(apps_dir: str) -> None:
|
|
492
|
+
"""Update the desktop database if the command is available."""
|
|
493
|
+
update_desktop_db_cmd = shutil_which("update-desktop-database")
|
|
494
|
+
if update_desktop_db_cmd:
|
|
495
|
+
subprocess.run([update_desktop_db_cmd, apps_dir], check=False, capture_output=True) # noqa: S603
|
|
496
|
+
|
|
497
|
+
@staticmethod
|
|
498
|
+
def create_desktop_icon_if_needed() -> None:
|
|
499
|
+
"""
|
|
500
|
+
Create a desktop icon for the application if running in a virtual environment and icon doesn't exist.
|
|
501
|
+
|
|
502
|
+
This function detects if we're running in a virtual environment and creates a desktop
|
|
503
|
+
entry that activates the venv and runs the application with the correct icon.
|
|
504
|
+
"""
|
|
505
|
+
# Only create desktop icon on Linux systems
|
|
506
|
+
if not ProgramSettings._is_linux_system():
|
|
507
|
+
return
|
|
508
|
+
|
|
509
|
+
# Check if desktop icon already exists
|
|
510
|
+
desktop_file_path = ProgramSettings._get_desktop_file_path()
|
|
511
|
+
if ProgramSettings._desktop_icon_exists(desktop_file_path):
|
|
512
|
+
return
|
|
513
|
+
|
|
514
|
+
# Check if we're in a virtual environment
|
|
515
|
+
venv_path = ProgramSettings._get_virtual_env_path()
|
|
516
|
+
if not venv_path:
|
|
517
|
+
return
|
|
518
|
+
|
|
519
|
+
# Find the icon path
|
|
520
|
+
icon_path = ProgramSettings.application_icon_filepath()
|
|
521
|
+
if not icon_path:
|
|
522
|
+
return
|
|
523
|
+
|
|
524
|
+
# Create the desktop entry content
|
|
525
|
+
desktop_entry = ProgramSettings._create_desktop_entry_content(venv_path, icon_path)
|
|
526
|
+
|
|
527
|
+
# Ensure the applications directory exists
|
|
528
|
+
apps_dir = ProgramSettings._ensure_applications_dir_exists(desktop_file_path)
|
|
529
|
+
|
|
530
|
+
# Write the desktop file
|
|
531
|
+
try:
|
|
532
|
+
ProgramSettings._write_desktop_file(desktop_file_path, desktop_entry)
|
|
533
|
+
ProgramSettings._set_desktop_file_permissions(desktop_file_path)
|
|
534
|
+
ProgramSettings._update_desktop_database(apps_dir)
|
|
535
|
+
|
|
536
|
+
except (OSError, subprocess.SubprocessError):
|
|
537
|
+
logging_error("Failed to create application launch desktop icon")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Manages vehicle components at the filesystem level.
|
|
3
3
|
|
|
4
|
-
This file is part of
|
|
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
|
|
|
@@ -99,7 +99,7 @@ class VehicleComponents:
|
|
|
99
99
|
|
|
100
100
|
templates = {}
|
|
101
101
|
try:
|
|
102
|
-
with open(filepath, encoding="utf-8") as file:
|
|
102
|
+
with open(filepath, encoding="utf-8-sig") as file:
|
|
103
103
|
templates = json_load(file)
|
|
104
104
|
except FileNotFoundError:
|
|
105
105
|
logging_debug(_("System component templates file '%s' not found."), filepath)
|
|
@@ -119,7 +119,7 @@ class VehicleComponents:
|
|
|
119
119
|
|
|
120
120
|
templates = {}
|
|
121
121
|
try:
|
|
122
|
-
with open(filepath, encoding="utf-8") as file:
|
|
122
|
+
with open(filepath, encoding="utf-8-sig") as file:
|
|
123
123
|
templates = json_load(file)
|
|
124
124
|
except FileNotFoundError:
|
|
125
125
|
logging_debug(_("User component templates file '%s' not found."), filepath)
|