Skip to content

Commit 093ed0b

Browse files
committed
tests(flightcontroller): Update the tests after refactoring
1 parent 7ad78c3 commit 093ed0b

16 files changed

Lines changed: 3065 additions & 998 deletions

.github/instructions/SITL_TESTING.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ The SITL testing setup consists of:
1414
1. **Direct Download**: Tests download pre-built ArduCopter SITL binaries directly from the official ArduPilot firmware server (`firmware.ardupilot.org`)
1515
2. **Pytest Fixtures**: Session-scoped SITLManager class manages SITL process lifecycle
1616
3. **TCP Connection**: SITL runs on TCP port 5760 with MAVLink protocol
17-
4. **Parameter Configuration**: SITL uses `sitl/copter.param` with battery monitoring enabled
17+
4. **Parameter Configuration**: SITL uses `sitl/copter.parm` with battery monitoring enabled
1818

1919
## Prerequisites
2020

@@ -120,7 +120,7 @@ SITL tests cover:
120120
SITL runs with the following command line parameters:
121121

122122
```bash
123-
arducopter --model quad --home "40.071374,-105.229930,1440,0" --defaults ./sitl/copter.param --sysid 1 --speedup 10
123+
arducopter --model quad --home "40.071374,-105.229930,1440,0" --defaults sitl/copter.parm --sysid 1 --speedup 10
124124
```
125125

126126
### Connection Details
@@ -133,7 +133,7 @@ arducopter --model quad --home "40.071374,-105.229930,1440,0" --defaults ./sitl/
133133

134134
### Parameter Requirements
135135

136-
Some tests require specific parameters to be set in `sitl/copter.param`:
136+
Some tests require specific parameters to be set in `sitl/copter.parm`:
137137

138138
- `BATT_MONITOR = 4` (Analog voltage and current)
139139
- `BATT_VOLT_PIN = 1`

ARCHITECTURE_2_flight_controller_communication.md

Lines changed: 347 additions & 54 deletions
Large diffs are not rendered by default.

SetupDeveloperPC.sh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,42 @@ ConfigureVSCode() {
154154
fi
155155
}
156156

157+
SetupSITL() {
158+
echo ""
159+
read -r -p "Do you want to download ArduCopter SITL for integration testing? (y/N) " response
160+
response=${response,,}
161+
162+
if [[ $response =~ ^y(es)?$ ]]; then
163+
echo "Downloading ArduCopter SITL from official firmware server..."
164+
165+
# Use the run_sitl_tests.sh script if available
166+
if [ -f "scripts/run_sitl_tests.sh" ]; then
167+
bash scripts/run_sitl_tests.sh download
168+
else
169+
# Fallback to direct download if script not found
170+
mkdir -p sitl
171+
curl -L -o sitl/arducopter https://firmware.ardupilot.org/Copter/latest/SITL_x86_64_linux_gnu/arducopter
172+
curl -L -o sitl/firmware-version.txt https://firmware.ardupilot.org/Copter/latest/SITL_x86_64_linux_gnu/firmware-version.txt
173+
curl -L -o sitl/git-version.txt https://firmware.ardupilot.org/Copter/latest/SITL_x86_64_linux_gnu/git-version.txt
174+
curl -L -o sitl/copter.parm https://raw.githubusercontent.com/ArduPilot/ardupilot/master/Tools/autotest/default_params/copter.parm
175+
chmod +x sitl/arducopter
176+
echo "✓ ArduCopter SITL downloaded successfully"
177+
fi
178+
179+
# Set environment variable in shell profile
180+
echo ""
181+
echo "To enable SITL tests permanently, add this to your ~/.bashrc or ~/.zshrc:"
182+
echo " export SITL_BINARY=$(pwd)/sitl/arducopter"
183+
else
184+
echo "Skipping SITL download. You can download it later with: ./scripts/run_sitl_tests.sh download"
185+
fi
186+
}
187+
157188
# Call configuration functions
158189
InstallDependencies
159190
ConfigureGit
160191
ConfigureVSCode
192+
SetupSITL
161193

162194
activate-global-python-argcomplete
163195

@@ -176,6 +208,11 @@ echo ""
176208
echo "source .venv/bin/activate"
177209
echo "python3 -m ardupilot_methodic_configurator"
178210
echo ""
211+
echo "To run SITL integration tests (if SITL was downloaded):"
212+
echo ""
213+
echo "export SITL_BINARY=\$(pwd)/sitl/arducopter"
214+
echo "pytest -m sitl -v"
215+
echo ""
179216
echo "For more detailed usage instructions, please refer to the USERMANUAL.md file."
180217
echo ""
181218

ardupilot_methodic_configurator/backend_flightcontroller.py

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from os import path as os_path
1515
from pathlib import Path
1616
from time import sleep as time_sleep
17-
from typing import Callable, Optional, Union
17+
from typing import TYPE_CHECKING, Any, Callable, Optional, Union, cast
1818

1919
import serial.tools.list_ports_common
2020
from pymavlink import mavutil
@@ -39,11 +39,12 @@
3939
from ardupilot_methodic_configurator.data_model_flightcontroller_info import FlightControllerInfo
4040
from ardupilot_methodic_configurator.data_model_par_dict import ParDict
4141

42+
if TYPE_CHECKING:
43+
from pymavlink.dialects.v20.ardupilotmega import MAVLink_autopilot_version_message
44+
4245
DEFAULT_REBOOT_TIME: int = 7
4346

4447
# Re-export constants for backwards compatibility
45-
# DEFAULT_BAUDRATE and SUPPORTED_BAUDRATES are from connection module
46-
# DEFAULT_REBOOT_TIME is defined in this module
4748
__all__ = [
4849
"DEFAULT_BAUDRATE",
4950
"DEFAULT_REBOOT_TIME",
@@ -139,9 +140,13 @@ def __init__( # pylint: disable=too-many-arguments, too-many-positional-argumen
139140
fc_parameters=None, # Let params_manager create its own fc_parameters dict
140141
)
141142

142-
self._commands_manager: FlightControllerCommandsProtocol = commands_manager or FlightControllerCommands(
143-
params_manager=self._params_manager,
144-
connection_manager=self._connection_manager,
143+
self._commands_manager: FlightControllerCommandsProtocol = cast(
144+
"FlightControllerCommandsProtocol",
145+
commands_manager
146+
or FlightControllerCommands(
147+
params_manager=self._params_manager,
148+
connection_manager=self._connection_manager,
149+
),
145150
)
146151

147152
self._files_manager: FlightControllerFilesProtocol = files_manager or FlightControllerFiles(
@@ -208,6 +213,26 @@ def baudrate(self) -> int:
208213
"""Get the baudrate setting."""
209214
return self._baudrate
210215

216+
@property
217+
def PARAM_FETCH_POLL_DELAY(self) -> float: # noqa: N802 # pylint: disable=invalid-name
218+
"""Get parameter fetch poll delay - delegates to params manager."""
219+
return self._params_manager.PARAM_FETCH_POLL_DELAY
220+
221+
@property
222+
def BATTERY_STATUS_CACHE_TIME(self) -> float: # noqa: N802 # pylint: disable=invalid-name
223+
"""Get battery status cache time - delegates to commands manager."""
224+
return self._commands_manager.BATTERY_STATUS_CACHE_TIME
225+
226+
@property
227+
def BATTERY_STATUS_TIMEOUT(self) -> float: # noqa: N802 # pylint: disable=invalid-name
228+
"""Get battery status timeout - delegates to commands manager."""
229+
return self._commands_manager.BATTERY_STATUS_TIMEOUT
230+
231+
@property
232+
def COMMAND_ACK_TIMEOUT(self) -> float: # noqa: N802 # pylint: disable=invalid-name
233+
"""Get command acknowledgment timeout - delegates to commands manager."""
234+
return self._commands_manager.COMMAND_ACK_TIMEOUT
235+
211236
@property
212237
def fc_parameters(self) -> dict[str, float]:
213238
"""Get flight controller parameters - delegates to params manager."""
@@ -281,6 +306,33 @@ def add_connection(self, connection_string: str) -> bool:
281306
"""Add a new connection to the list of available connections - delegates to connection manager."""
282307
return self._connection_manager.add_connection(connection_string)
283308

309+
# Testing-only methods (protected methods exposed for SITL integration tests)
310+
def _detect_vehicles_from_heartbeats(self, timeout: int) -> dict[tuple[int, int], Any]:
311+
"""Detect vehicles from heartbeats - delegates to connection manager (testing only)."""
312+
return self._connection_manager._detect_vehicles_from_heartbeats(timeout) # noqa: SLF001 # pylint: disable=protected-access
313+
314+
def _extract_firmware_type_from_banner(self, banner_msgs: list[str], os_custom_version_index: Optional[int]) -> str:
315+
"""Extract firmware type from banner - delegates to connection manager (testing only)."""
316+
return self._connection_manager._extract_firmware_type_from_banner( # noqa: SLF001 # pylint: disable=protected-access
317+
banner_msgs, os_custom_version_index
318+
)
319+
320+
def _extract_chibios_version_from_banner(self, banner_msgs: list[str]) -> tuple[str, Optional[int]]:
321+
"""Extract ChibiOS version from banner - delegates to connection manager (testing only)."""
322+
return self._connection_manager._extract_chibios_version_from_banner(banner_msgs) # noqa: SLF001 # pylint: disable=protected-access
323+
324+
def _select_supported_autopilot(self, detected_vehicles: dict[tuple[int, int], Any]) -> str:
325+
"""Select supported autopilot from detected vehicles - delegates to connection manager (testing only)."""
326+
return self._connection_manager._select_supported_autopilot(detected_vehicles) # noqa: SLF001 # pylint: disable=protected-access
327+
328+
def _populate_flight_controller_info(self, m: "MAVLink_autopilot_version_message") -> None:
329+
"""Populate flight controller info from autopilot version - delegates to connection manager (testing only)."""
330+
self._connection_manager._populate_flight_controller_info(m) # noqa: SLF001 # pylint: disable=protected-access
331+
332+
def _retrieve_autopilot_version_and_banner(self, timeout: int) -> str:
333+
"""Retrieve autopilot version and banner - delegates to connection manager (testing only)."""
334+
return self._connection_manager._retrieve_autopilot_version_and_banner(timeout) # noqa: SLF001 # pylint: disable=protected-access
335+
284336
def register_and_try_connect(
285337
self,
286338
comport: Union[mavutil.SerialPort, serial.tools.list_ports_common.ListPortInfo],

ardupilot_methodic_configurator/backend_flightcontroller_connection.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,9 +451,13 @@ def _request_message(self, message_id: int) -> None:
451451
if self.master is not None:
452452
# Note: Don't wait for ACK here as this is used internally for autopilot version requests
453453
# and the response comes as the requested message itself
454+
# Convert system_id and component_id from string to int for MAVLink
455+
system_id = int(self.info.system_id) if self.info.system_id else 0
456+
component_id = int(self.info.component_id) if self.info.component_id else 0
457+
454458
self.master.mav.command_long_send(
455-
self.info.system_id,
456-
self.info.component_id,
459+
system_id,
460+
component_id,
457461
mavutil.mavlink.MAV_CMD_REQUEST_MESSAGE,
458462
0, # confirmation
459463
message_id,

ardupilot_methodic_configurator/backend_flightcontroller_protocols.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"""
2626

2727
from pathlib import Path
28-
from typing import TYPE_CHECKING, Callable, Optional, Protocol, Union
28+
from typing import TYPE_CHECKING, Any, Callable, Optional, Protocol, Union
2929

3030
import serial.tools.list_ports_common
3131

@@ -38,6 +38,7 @@
3838
if TYPE_CHECKING:
3939
# During type checking, import the actual mavutil module for better type hints
4040
from pymavlink import mavutil
41+
from pymavlink.dialects.v20.ardupilotmega import MAVLink_autopilot_version_message
4142

4243
# Use a union of known connection types for better type safety
4344
# Note: mavutil.mavlink_connection() returns different types based on the connection string
@@ -140,6 +141,18 @@ def set_master_for_testing(self, master: Optional[MavlinkConnection]) -> None:
140141
141142
"""
142143

144+
def _detect_vehicles_from_heartbeats(self, timeout: int) -> dict[tuple[int, int], Any]: ...
145+
146+
def _extract_firmware_type_from_banner(self, banner_msgs: list[str], os_custom_version_index: Optional[int]) -> str: ...
147+
148+
def _extract_chibios_version_from_banner(self, banner_msgs: list[str]) -> tuple[str, Optional[int]]: ...
149+
150+
def _select_supported_autopilot(self, detected_vehicles: dict[tuple[int, int], Any]) -> str: ...
151+
152+
def _populate_flight_controller_info(self, m: "MAVLink_autopilot_version_message") -> None: ...
153+
154+
def _retrieve_autopilot_version_and_banner(self, timeout: int) -> str: ...
155+
143156

144157
class FlightControllerParamsProtocol(Protocol):
145158
"""
@@ -152,6 +165,9 @@ class FlightControllerParamsProtocol(Protocol):
152165
- fc_parameters: dict[str, float] (shared parameter dictionary, created if not provided)
153166
"""
154167

168+
# Class constant exposed as property for backward compatibility
169+
PARAM_FETCH_POLL_DELAY: float
170+
155171
@property
156172
def fc_parameters(self) -> dict[str, float]:
157173
"""Get the parameter dictionary."""
@@ -189,6 +205,11 @@ class FlightControllerCommandsProtocol(Protocol):
189205
- connection_manager: FlightControllerConnectionProtocol (to access master)
190206
"""
191207

208+
# Class constants exposed for testing
209+
COMMAND_ACK_TIMEOUT: float
210+
BATTERY_STATUS_CACHE_TIME: float
211+
BATTERY_STATUS_TIMEOUT: float
212+
192213
def send_command_and_wait_ack( # pylint: disable=too-many-arguments, too-many-positional-arguments
193214
self,
194215
command: int,

0 commit comments

Comments
 (0)