Skip to content

Commit 32fa1d2

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

File tree

5 files changed

+134
-2
lines changed

5 files changed

+134
-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: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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+
20+
@pytest.fixture
21+
def mock_settings_file() -> dict[str, Any]:
22+
"""Fixture providing realistic settings data for testing."""
23+
return {
24+
"Format version": 1,
25+
"display_usage_popup": {
26+
"workflow_explanation": True,
27+
"component_editor": False,
28+
},
29+
"connection_history": ["COM3", "tcp:127.0.0.1:5760"],
30+
"directory_selection": {
31+
"template_dir": "/path/to/template",
32+
"new_base_dir": "/path/to/base",
33+
"vehicle_dir": "/path/to/vehicle",
34+
},
35+
"auto_open_doc_in_browser": True,
36+
"gui_complexity": "simple",
37+
"motor_test": {
38+
"duration": 2,
39+
"throttle_pct": 10,
40+
},
41+
}
42+
43+
44+
class TestConnectionHistoryManagement:
45+
"""Test user connection history management workflows."""
46+
47+
def test_user_can_store_new_connection(self, mock_settings_file: dict[str, Any]) -> None:
48+
"""
49+
User can save a new connection string to history.
50+
51+
GIVEN: A user successfully connects to a device
52+
WHEN: The connection is stored
53+
THEN: It should appear at the top of the connection history
54+
"""
55+
# Arrange: Mock settings and file operations (GIVEN)
56+
with (
57+
patch.object(ProgramSettings, "_get_settings_as_dict", return_value=mock_settings_file),
58+
patch.object(ProgramSettings, "_set_settings_from_dict") as mock_set,
59+
):
60+
# Act: User stores a new connection (WHEN)
61+
ProgramSettings.store_connection("udp:192.168.1.100:14550")
62+
63+
# Assert: New connection added to top of history (THEN)
64+
mock_set.assert_called_once()
65+
saved_settings = mock_set.call_args[0][0]
66+
assert saved_settings["connection_history"][0] == "udp:192.168.1.100:14550"
67+
68+
def test_user_can_store_multiple_connections_in_order(self, mock_settings_file: dict[str, Any]) -> None:
69+
"""
70+
User can store multiple connections and they maintain order.
71+
72+
GIVEN: A clean connection history
73+
WHEN: The user stores multiple connection strings
74+
THEN: The connections should be stored in most-recent-first order
75+
"""
76+
# Arrange: Start with empty history (GIVEN)
77+
mock_settings_file["connection_history"] = []
78+
79+
with (
80+
patch.object(ProgramSettings, "_get_settings_as_dict", return_value=mock_settings_file),
81+
patch.object(ProgramSettings, "_set_settings_from_dict") as mock_set,
82+
):
83+
# Act: User stores multiple connections (WHEN)
84+
ProgramSettings.store_connection("COM1")
85+
ProgramSettings.store_connection("COM2")
86+
ProgramSettings.store_connection("COM3")
87+
88+
# Assert: Connections in correct order (THEN)
89+
# We check the very last call to see the final state of history
90+
final_settings = mock_set.call_args[0][0]
91+
history = final_settings["connection_history"]
92+
assert history[0] == "COM3" # Most recent first
93+
assert history[1] == "COM2"
94+
assert history[2] == "COM1"

0 commit comments

Comments
 (0)