Skip to content

Commit 62a201f

Browse files
OmkarSarkar204amilcarlucas
authored andcommitted
feat(port save): Custom connection strings to settings.json
closes #1108 Signed-off-by: Omkar Sarkar <omkarsarkar24@gmail.com>
1 parent 41ebf93 commit 62a201f

File tree

5 files changed

+136
-2
lines changed

5 files changed

+136
-2
lines changed

.github/instructions/pytest_testing_instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ class TestTemplateSelection:
297297

298298
1. **Run tests**: `pytest tests/ -v`
299299
2. **Check coverage**: `pytest --cov=ardupilot_methodic_configurator --cov-report=term-missing`
300-
3. **Format with ruff**: `ruff formal`
300+
3. **Format with ruff**: `ruff format`
301301
4. **Lint with ruff**: `ruff check --fix`
302302
5. **Type check with mypy**: `mypy`
303303
6. **Advanced type check with pyright**: `pyright`

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

0 commit comments

Comments
 (0)