Skip to content

Commit 38f6448

Browse files
Ryanf55peterbarker
authored andcommitted
Tools: Add airspeed test and SITL plane fixtures
Signed-off-by: Ryan Friedman <[email protected]>
1 parent 593ea27 commit 38f6448

File tree

2 files changed

+348
-0
lines changed

2 files changed

+348
-0
lines changed

Tools/ros2/ardupilot_dds_tests/test/ardupilot_dds_tests/conftest.py

+115
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,118 @@ def sitl_copter_dds_udp(micro_ros_agent_udp, mavproxy):
215215
actions.update(mp_actions)
216216
actions.update(sitl_actions)
217217
yield ld, actions
218+
219+
220+
@pytest.fixture
221+
def sitl_plane_dds_serial(device_dir, virtual_ports, micro_ros_agent_serial, mavproxy):
222+
"""Fixture to bring up ArduPilot SITL DDS."""
223+
tty1 = Path(device_dir, "dev", "tty1").resolve()
224+
225+
vp_ld, vp_actions = virtual_ports
226+
mra_ld, mra_actions = micro_ros_agent_serial
227+
mp_ld, mp_actions = mavproxy
228+
sitl_ld, sitl_actions = SITLLaunch.generate_launch_description_with_actions()
229+
230+
sitl_ld_args = IncludeLaunchDescription(
231+
LaunchDescriptionSource(sitl_ld),
232+
launch_arguments={
233+
"command": "arduplane",
234+
"synthetic_clock": "True",
235+
# "wipe": "True",
236+
"wipe": "False",
237+
"model": "plane",
238+
"speedup": "10",
239+
"slave": "0",
240+
"instance": "0",
241+
"serial1": f"uart:{str(tty1)}",
242+
"defaults": str(
243+
Path(
244+
get_package_share_directory("ardupilot_sitl"),
245+
"config",
246+
"models",
247+
"plane.parm",
248+
)
249+
)
250+
+ ","
251+
+ str(
252+
Path(
253+
get_package_share_directory("ardupilot_sitl"),
254+
"config",
255+
"default_params",
256+
"dds_serial.parm",
257+
)
258+
),
259+
}.items(),
260+
)
261+
262+
ld = LaunchDescription(
263+
[
264+
vp_ld,
265+
mra_ld,
266+
mp_ld,
267+
sitl_ld_args,
268+
]
269+
)
270+
actions = {}
271+
actions.update(vp_actions)
272+
actions.update(mra_actions)
273+
actions.update(mp_actions)
274+
actions.update(sitl_actions)
275+
yield ld, actions
276+
277+
278+
@pytest.fixture
279+
def sitl_plane_dds_udp(device_dir, virtual_ports, micro_ros_agent_udp, mavproxy):
280+
"""Fixture to bring up ArduPilot SITL DDS."""
281+
tty1 = Path(device_dir, "dev", "tty1").resolve()
282+
283+
vp_ld, vp_actions = virtual_ports
284+
mra_ld, mra_actions = micro_ros_agent_udp
285+
mp_ld, mp_actions = mavproxy
286+
sitl_ld, sitl_actions = SITLLaunch.generate_launch_description_with_actions()
287+
288+
sitl_ld_args = IncludeLaunchDescription(
289+
LaunchDescriptionSource(sitl_ld),
290+
launch_arguments={
291+
"command": "arduplane",
292+
"synthetic_clock": "True",
293+
# "wipe": "True",
294+
"wipe": "False",
295+
"model": "plane",
296+
"speedup": "10",
297+
"slave": "0",
298+
"instance": "0",
299+
"defaults": str(
300+
Path(
301+
get_package_share_directory("ardupilot_sitl"),
302+
"config",
303+
"models",
304+
"plane.parm",
305+
)
306+
)
307+
+ ","
308+
+ str(
309+
Path(
310+
get_package_share_directory("ardupilot_sitl"),
311+
"config",
312+
"default_params",
313+
"dds_udp.parm",
314+
)
315+
),
316+
}.items(),
317+
)
318+
319+
ld = LaunchDescription(
320+
[
321+
vp_ld,
322+
mra_ld,
323+
mp_ld,
324+
sitl_ld_args,
325+
]
326+
)
327+
actions = {}
328+
actions.update(vp_actions)
329+
actions.update(mra_actions)
330+
actions.update(mp_actions)
331+
actions.update(sitl_actions)
332+
yield ld, actions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# Copyright 2025 ArduPilot.org.
2+
#
3+
# This program is free software: you can redistribute it and/or modify
4+
# it under the terms of the GNU General Public License as published by
5+
# the Free Software Foundation, either version 3 of the License, or
6+
# (at your option) any later version.
7+
#
8+
# This program is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
15+
16+
"""
17+
Bring up ArduPilot SITL and check the airspeed commands result in changed airspeed.
18+
19+
colcon test --executor sequential --parallel-workers 0 \
20+
--packages-select ardupilot_dds_tests \
21+
--event-handlers=console_cohesion+ --pytest-args -k test_plane_airspeed
22+
"""
23+
24+
25+
import launch_pytest
26+
import pytest
27+
import rclpy
28+
import rclpy.node
29+
import threading
30+
31+
from launch import LaunchDescription
32+
33+
from launch_pytest.tools import process as process_tools
34+
35+
from rclpy.qos import QoSProfile
36+
from rclpy.qos import QoSReliabilityPolicy
37+
from rclpy.qos import QoSHistoryPolicy
38+
39+
from ardupilot_msgs.msg import Airspeed
40+
41+
TOPIC = "/ap/airspeed"
42+
AIRSPEED_RECV_TIMEOUT = 20.0
43+
44+
45+
class AirspeedTester(rclpy.node.Node):
46+
"""Subscribe to Airspeed messages."""
47+
48+
def __init__(self):
49+
"""Initialise the node."""
50+
super().__init__("airspeed_listener")
51+
self.msg_event_object = threading.Event()
52+
53+
def start_subscriber(self):
54+
"""Start the subscriber."""
55+
qos_profile = QoSProfile(
56+
reliability=QoSReliabilityPolicy.BEST_EFFORT,
57+
history=QoSHistoryPolicy.KEEP_LAST,
58+
depth=1,
59+
)
60+
61+
self.subscription = self.create_subscription(Airspeed, TOPIC, self.airspeed_data_callback, qos_profile)
62+
63+
# Add a spin thread.
64+
self.ros_spin_thread = threading.Thread(target=lambda node: rclpy.spin(node), args=(self,))
65+
self.ros_spin_thread.start()
66+
67+
def airspeed_data_callback(self, msg):
68+
"""Process a airspeed message from the autopilot."""
69+
self.msg_event_object.set()
70+
71+
self.get_logger().info(f"From AP : airspeed {msg}")
72+
73+
74+
@launch_pytest.fixture
75+
def launch_sitl_copter_dds_serial(sitl_copter_dds_serial):
76+
"""Fixture to create the launch description."""
77+
sitl_ld, sitl_actions = sitl_copter_dds_serial
78+
79+
ld = LaunchDescription(
80+
[
81+
sitl_ld,
82+
launch_pytest.actions.ReadyToTest(),
83+
]
84+
)
85+
actions = sitl_actions
86+
yield ld, actions
87+
88+
89+
@launch_pytest.fixture
90+
def launch_sitl_copter_dds_udp(sitl_copter_dds_udp):
91+
"""Fixture to create the launch description."""
92+
sitl_ld, sitl_actions = sitl_copter_dds_udp
93+
94+
ld = LaunchDescription(
95+
[
96+
sitl_ld,
97+
launch_pytest.actions.ReadyToTest(),
98+
]
99+
)
100+
actions = sitl_actions
101+
yield ld, actions
102+
103+
104+
@launch_pytest.fixture
105+
def launch_sitl_plane_dds_serial(sitl_plane_dds_serial):
106+
"""Fixture to create the launch description."""
107+
sitl_ld, sitl_actions = sitl_plane_dds_serial
108+
109+
ld = LaunchDescription(
110+
[
111+
sitl_ld,
112+
launch_pytest.actions.ReadyToTest(),
113+
]
114+
)
115+
actions = sitl_actions
116+
yield ld, actions
117+
118+
119+
@launch_pytest.fixture
120+
def launch_sitl_plane_dds_udp(sitl_plane_dds_udp):
121+
"""Fixture to create the launch description."""
122+
sitl_ld, sitl_actions = sitl_plane_dds_udp
123+
124+
ld = LaunchDescription(
125+
[
126+
sitl_ld,
127+
launch_pytest.actions.ReadyToTest(),
128+
]
129+
)
130+
actions = sitl_actions
131+
yield ld, actions
132+
133+
134+
@pytest.mark.launch(fixture=launch_sitl_copter_dds_serial)
135+
def test_dds_serial_airspeed_msg_recv_copter(launch_context, launch_sitl_copter_dds_serial):
136+
"""Test airspeed messages are published by AP_DDS."""
137+
_, actions = launch_sitl_copter_dds_serial
138+
virtual_ports = actions["virtual_ports"].action
139+
micro_ros_agent = actions["micro_ros_agent"].action
140+
mavproxy = actions["mavproxy"].action
141+
sitl = actions["sitl"].action
142+
143+
# Wait for process to start.
144+
process_tools.wait_for_start_sync(launch_context, virtual_ports, timeout=2)
145+
process_tools.wait_for_start_sync(launch_context, micro_ros_agent, timeout=2)
146+
process_tools.wait_for_start_sync(launch_context, mavproxy, timeout=2)
147+
process_tools.wait_for_start_sync(launch_context, sitl, timeout=2)
148+
149+
rclpy.init()
150+
try:
151+
node = AirspeedTester()
152+
node.start_subscriber()
153+
msgs_received_flag = node.msg_event_object.wait(timeout=AIRSPEED_RECV_TIMEOUT)
154+
assert msgs_received_flag, f"Did not receive '{TOPIC}' msgs."
155+
finally:
156+
rclpy.shutdown()
157+
yield
158+
159+
160+
@pytest.mark.launch(fixture=launch_sitl_copter_dds_udp)
161+
def test_dds_udp_airspeed_msg_recv_copter(launch_context, launch_sitl_copter_dds_udp):
162+
"""Test airspeed messages are published by AP_DDS."""
163+
_, actions = launch_sitl_copter_dds_udp
164+
micro_ros_agent = actions["micro_ros_agent"].action
165+
mavproxy = actions["mavproxy"].action
166+
sitl = actions["sitl"].action
167+
168+
# Wait for process to start.
169+
process_tools.wait_for_start_sync(launch_context, micro_ros_agent, timeout=2)
170+
process_tools.wait_for_start_sync(launch_context, mavproxy, timeout=2)
171+
process_tools.wait_for_start_sync(launch_context, sitl, timeout=2)
172+
173+
rclpy.init()
174+
try:
175+
node = AirspeedTester()
176+
node.start_subscriber()
177+
msgs_received_flag = node.msg_event_object.wait(timeout=AIRSPEED_RECV_TIMEOUT)
178+
assert msgs_received_flag, f"Did not receive '{TOPIC}' msgs."
179+
180+
finally:
181+
rclpy.shutdown()
182+
yield
183+
184+
185+
@pytest.mark.launch(fixture=launch_sitl_plane_dds_serial)
186+
def test_dds_serial_airspeed_msg_recv_plane(launch_context, launch_sitl_plane_dds_serial):
187+
"""Test airspeed messages are published by AP_DDS."""
188+
_, actions = launch_sitl_plane_dds_serial
189+
virtual_ports = actions["virtual_ports"].action
190+
micro_ros_agent = actions["micro_ros_agent"].action
191+
mavproxy = actions["mavproxy"].action
192+
sitl = actions["sitl"].action
193+
194+
# Wait for process to start.
195+
process_tools.wait_for_start_sync(launch_context, virtual_ports, timeout=2)
196+
process_tools.wait_for_start_sync(launch_context, micro_ros_agent, timeout=2)
197+
process_tools.wait_for_start_sync(launch_context, mavproxy, timeout=2)
198+
process_tools.wait_for_start_sync(launch_context, sitl, timeout=2)
199+
200+
rclpy.init()
201+
try:
202+
node = AirspeedTester()
203+
node.start_subscriber()
204+
msgs_received_flag = node.msg_event_object.wait(timeout=AIRSPEED_RECV_TIMEOUT)
205+
assert msgs_received_flag, f"Did not receive '{TOPIC}' msgs."
206+
finally:
207+
rclpy.shutdown()
208+
yield
209+
210+
211+
@pytest.mark.launch(fixture=launch_sitl_plane_dds_udp)
212+
def test_dds_udp_airspeed_msg_recv_plane(launch_context, launch_sitl_plane_dds_udp):
213+
"""Test airspeed messages are published by AP_DDS."""
214+
_, actions = launch_sitl_plane_dds_udp
215+
micro_ros_agent = actions["micro_ros_agent"].action
216+
mavproxy = actions["mavproxy"].action
217+
sitl = actions["sitl"].action
218+
219+
# Wait for process to start.
220+
process_tools.wait_for_start_sync(launch_context, micro_ros_agent, timeout=2)
221+
process_tools.wait_for_start_sync(launch_context, mavproxy, timeout=2)
222+
process_tools.wait_for_start_sync(launch_context, sitl, timeout=2)
223+
224+
rclpy.init()
225+
try:
226+
node = AirspeedTester()
227+
node.start_subscriber()
228+
msgs_received_flag = node.msg_event_object.wait(timeout=AIRSPEED_RECV_TIMEOUT)
229+
assert msgs_received_flag, f"Did not receive '{TOPIC}' msgs."
230+
231+
finally:
232+
rclpy.shutdown()
233+
yield

0 commit comments

Comments
 (0)