Skip to content

Commit 5983593

Browse files
Custom connection strings to settings.json
1 parent dcbd034 commit 5983593

File tree

5 files changed

+137
-1
lines changed

5 files changed

+137
-1
lines changed

ardupilot_methodic_configurator/backend_filesystem_program_settings.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def __init__(self) -> None:
7575
pass
7676

7777
@classmethod
78-
def _get_settings_defaults(cls) -> dict[str, Union[int, bool, str, float, dict]]:
78+
def _get_settings_defaults(cls) -> dict[str, Union[int, bool, str, float, dict, list]]:
7979
"""
8080
Get the default settings dictionary with dynamically computed paths.
8181
@@ -89,6 +89,7 @@ def _get_settings_defaults(cls) -> dict[str, Union[int, bool, str, float, dict]]
8989
return {
9090
"Format version": 1,
9191
"display_usage_popup": dict.fromkeys(USAGE_POPUP_WINDOWS, True),
92+
"connection_history": [], # New Connection list
9293
"directory_selection": {
9394
"template_dir": os_path.join(cls.get_templates_base_dir(), "ArduCopter", "empty_4.6.x"),
9495
"new_base_dir": os_path.join(settings_directory, "vehicles"),
@@ -455,3 +456,34 @@ def motor_diagram_exists(frame_class: int, frame_type: int) -> bool:
455456
"""
456457
filepath, _error_msg = ProgramSettings.motor_diagram_filepath(frame_class, frame_type)
457458
return filepath != "" and os_path.exists(filepath)
459+
460+
@staticmethod
461+
def get_connection_history() -> list[str]:
462+
"""Get the list of previously used connection strings."""
463+
settings = ProgramSettings._get_settings_as_dict()
464+
history: Any = settings.get("connection_history", [])
465+
466+
if not isinstance(history, list):
467+
return []
468+
469+
return [item for item in history if isinstance(item, str)]
470+
471+
@staticmethod
472+
def store_connection(connection_string: str) -> None:
473+
"""Save a new connection string to history."""
474+
if not connection_string:
475+
return
476+
477+
settings = ProgramSettings._get_settings_as_dict()
478+
history = settings.get("connection_history", [])
479+
480+
if connection_string in history:
481+
history.remove(connection_string)
482+
483+
history.insert(0, connection_string)
484+
485+
if len(history) > 10:
486+
history = history[:10]
487+
488+
settings["connection_history"] = history
489+
ProgramSettings._set_settings_from_dict(settings)

ardupilot_methodic_configurator/frontend_tkinter_connection_selection.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from typing import Union
2323

2424
from ardupilot_methodic_configurator import _, __version__
25+
from ardupilot_methodic_configurator.backend_filesystem_program_settings import ProgramSettings
2526
from ardupilot_methodic_configurator.backend_flightcontroller import SUPPORTED_BAUDRATES, FlightController
2627
from ardupilot_methodic_configurator.common_arguments import add_common_arguments
2728
from ardupilot_methodic_configurator.frontend_tkinter_base_window import BaseWindow
@@ -73,6 +74,9 @@ def __init__( # pylint: disable=too-many-arguments, too-many-positional-argumen
7374
# Create a label for port
7475
port_label = ttk.Label(selection_frame, text=_("Port:"))
7576
port_label.pack(side=tk.LEFT, padx=(0, 5))
77+
# Load saved connection history from ProgramSettings
78+
for conn in ProgramSettings.get_connection_history():
79+
self.flight_controller.add_connection(conn)
7680

7781
# Create a read-only combobox for flight controller connection selection
7882
self.conn_selection_combobox = PairTupleCombobox(
@@ -157,6 +161,7 @@ def add_connection(self) -> str:
157161
),
158162
)
159163
if selected_connection:
164+
ProgramSettings.store_connection(selected_connection)
160165
error_msg = _("Will add new connection: {selected_connection} if not duplicated")
161166
logging_debug(error_msg.format(**locals()))
162167
self.flight_controller.add_connection(selected_connection)

tests/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# SPDX-FileCopyrightText: 2024-2026 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>
2+
#
3+
# SPDX-License-Identifier: GPL-3.0-or-later
4+
5+
"""Tests for the ArduPilot Methodic Configurator."""

tests/test_backend_filesystem_program_settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ def test_user_can_load_existing_settings_file(self, mock_user_config, sample_pro
403403
expected_result["annotate_docs_into_param_files"] = False # Added by default
404404
expected_result["gui_complexity"] = "simple" # Added by default
405405
expected_result["motor_test"] = {"duration": 2, "throttle_pct": 10} # Added by default
406+
expected_result["connection_history"] = [] # Added for port connection string to save
406407
expected_result["display_usage_popup"]["component_editor_validation"] = True # Added by default
407408
expected_result["display_usage_popup"]["workflow_explanation"] = True # Added by default
408409
expected_result["display_usage_popup"]["bitmask_parameter_editor"] = True # Added by default
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""
2+
BDD-style tests for the backend_filesystem_program_settings.py file.
3+
4+
This file is part of ArduPilot Methodic Configurator. https://github.com/ArduPilot/MethodicConfigurator
5+
6+
SPDX-FileCopyrightText: 2024-2026 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>
7+
8+
SPDX-License-Identifier: GPL-3.0-or-later
9+
"""
10+
11+
from typing import Any
12+
from unittest.mock import patch
13+
14+
import pytest
15+
16+
from ardupilot_methodic_configurator.backend_filesystem_program_settings import ProgramSettings
17+
18+
19+
@pytest.fixture
20+
def mock_settings_file() -> dict[str, Any]:
21+
"""Fixture providing realistic settings data for testing."""
22+
return {
23+
"Format version": 1,
24+
"display_usage_popup": {
25+
"workflow_explanation": True,
26+
"component_editor": False,
27+
},
28+
"connection_history": ["COM3", "tcp:127.0.0.1:5760"],
29+
"directory_selection": {
30+
"template_dir": "/path/to/template",
31+
"new_base_dir": "/path/to/base",
32+
"vehicle_dir": "/path/to/vehicle",
33+
},
34+
"auto_open_doc_in_browser": True,
35+
"gui_complexity": "simple",
36+
"motor_test": {
37+
"duration": 2,
38+
"throttle_pct": 10,
39+
},
40+
}
41+
42+
43+
class TestConnectionHistoryManagement:
44+
"""Test user connection history management workflows."""
45+
46+
def test_user_can_store_new_connection(self, mock_settings_file: dict[str, Any]) -> None:
47+
"""
48+
User can save a new connection string to history.
49+
50+
GIVEN: A user successfully connects to a device
51+
WHEN: The connection is stored
52+
THEN: It should appear at the top of the connection history
53+
"""
54+
# Arrange: Mock settings and file operations (GIVEN)
55+
with (
56+
patch.object(ProgramSettings, "_get_settings_as_dict", return_value=mock_settings_file),
57+
patch.object(ProgramSettings, "_set_settings_from_dict") as mock_set,
58+
):
59+
# Act: User stores a new connection (WHEN)
60+
ProgramSettings.store_connection("udp:192.168.1.100:14550")
61+
62+
# Assert: New connection added to top of history (THEN)
63+
mock_set.assert_called_once()
64+
saved_settings = mock_set.call_args[0][0]
65+
assert saved_settings["connection_history"][0] == "udp:192.168.1.100:14550"
66+
67+
def test_user_can_store_multiple_connections_in_order(self, mock_settings_file: dict[str, Any]) -> None:
68+
"""
69+
User can store multiple connections and they maintain order.
70+
71+
GIVEN: A clean connection history
72+
WHEN: The user stores multiple connection strings
73+
THEN: The connections should be stored in most-recent-first order
74+
"""
75+
# Arrange: Start with empty history (GIVEN)
76+
mock_settings_file["connection_history"] = []
77+
78+
with (
79+
patch.object(ProgramSettings, "_get_settings_as_dict", return_value=mock_settings_file),
80+
patch.object(ProgramSettings, "_set_settings_from_dict") as mock_set,
81+
):
82+
# Act: User stores multiple connections (WHEN)
83+
ProgramSettings.store_connection("COM1")
84+
ProgramSettings.store_connection("COM2")
85+
ProgramSettings.store_connection("COM3")
86+
87+
# Assert: Connections in correct order (THEN)
88+
# We check the very last call to see the final state of history
89+
final_settings = mock_set.call_args[0][0]
90+
history = final_settings["connection_history"]
91+
assert history[0] == "COM3" # Most recent first
92+
assert history[1] == "COM2"
93+
assert history[2] == "COM1"

0 commit comments

Comments
 (0)