Skip to content

Commit ee9f38c

Browse files
AP_DDS: Rally Interface to get-set rally points.
1 parent 3ed6ada commit ee9f38c

File tree

12 files changed

+504
-2
lines changed

12 files changed

+504
-2
lines changed
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
# Copyright 2023 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+
Tests the Rally Point interface.
18+
19+
Clear the list, adds points and verifies that they are correctly updated.
20+
21+
colcon test --packages-select ardupilot_dds_tests \
22+
--event-handlers=console_cohesion+ --pytest-args -k test_rally_point
23+
24+
"""
25+
26+
import rclpy
27+
import time
28+
import launch_pytest
29+
from rclpy.node import Node
30+
from builtin_interfaces.msg import Time
31+
from ardupilot_msgs.msg import Rally
32+
from ardupilot_msgs.srv import RallyGet
33+
from ardupilot_msgs.srv import RallySet
34+
import pytest
35+
from launch_pytest.tools import process as process_tools
36+
from launch import LaunchDescription
37+
import threading
38+
39+
RALLY_0 = Rally()
40+
RALLY_0.point.latitude =-35.0
41+
RALLY_0.point.longitude = 149.0
42+
RALLY_0.point.altitude = 400.0
43+
RALLY_0.altitude_frame = 2
44+
45+
46+
class RallyControl(Node):
47+
"""Push/Pull Rally points."""
48+
49+
def __init__(self):
50+
"""Initialise the node."""
51+
super().__init__("rally_service")
52+
self.clear_rally_event = threading.Event()
53+
self.add_rally_event = threading.Event()
54+
self.get_rally_event = threading.Event()
55+
56+
self._client_rally_get = self.create_client(RallyGet, "/ap/rally_get")
57+
self._client_rally_set = self.create_client(RallySet, "/ap/rally_set")
58+
# Add a spin thread.
59+
self.ros_spin_thread = threading.Thread(target=lambda node: rclpy.spin(node), args=(self,))
60+
self.ros_spin_thread.start()
61+
62+
def _clear_list(self):
63+
req = RallySet.Request()
64+
req.clear = True
65+
self.future = self._client_rally_set.call_async(req)
66+
time.sleep(1.0)
67+
if self.future.result() is None:
68+
return False
69+
else:
70+
print(self.future)
71+
print(self.future.result())
72+
return self.future.result().size == 0
73+
74+
def _send_rally(self, rally):
75+
req = RallySet.Request()
76+
req.clear = False
77+
req.rally = rally
78+
self.future = self._client_rally_set.call_async(req)
79+
time.sleep(1.0)
80+
return self.future.result()
81+
82+
def _get_rally(self):
83+
req = RallyGet.Request()
84+
req.index = 0
85+
self.future = self._client_rally_get.call_async(req)
86+
time.sleep(1.0)
87+
return self.future.result()
88+
89+
#-------------- PROCESSES -----------------
90+
def process_clear(self):
91+
print("---> Process start")
92+
while True:
93+
if self._clear_list():
94+
print("---> List Cleared")
95+
self.clear_rally_event.set()
96+
return
97+
time.sleep(1)
98+
99+
def clear_list(self):
100+
try:
101+
self.clear_thread.stop()
102+
except:
103+
print("---> Starting thread")
104+
self.clear_thread = threading.Thread(target=self.process_clear)
105+
self.clear_thread.start()
106+
107+
def process_send(self, rally):
108+
while True:
109+
response = self._send_rally(rally)
110+
if response is not None:
111+
if response.success:
112+
self.add_rally_event.set()
113+
return
114+
time.sleep(1)
115+
116+
def send_rally(self, rally):
117+
try:
118+
self.send_thread.stop()
119+
except:
120+
self.send_thread = threading.Thread(target=self.process_send, args=[rally])
121+
self.send_thread.start()
122+
123+
def process_compare(self, rally):
124+
while True:
125+
response = self._get_rally()
126+
if response is not None:
127+
if response.success:
128+
if (abs(response.rally.point.latitude - rally.point.latitude) < 1e-7
129+
and abs(response.rally.point.longitude - rally.point.longitude) < 1e-7
130+
and response.rally.altitude_frame == rally.altitude_frame):
131+
self.get_rally_event.set()
132+
return
133+
time.sleep(1)
134+
135+
def get_and_compare_rally(self, rally):
136+
try:
137+
self.get_thread.stop()
138+
except:
139+
self.get_thread = threading.Thread(target=self.process_compare, args=[rally])
140+
self.get_thread.start()
141+
142+
@launch_pytest.fixture
143+
def launch_sitl_copter_dds_serial(sitl_copter_dds_serial):
144+
"""Fixture to create the launch description."""
145+
sitl_ld, sitl_actions = sitl_copter_dds_serial
146+
147+
ld = LaunchDescription(
148+
[
149+
sitl_ld,
150+
launch_pytest.actions.ReadyToTest(),
151+
]
152+
)
153+
actions = sitl_actions
154+
yield ld, actions
155+
156+
@launch_pytest.fixture
157+
def launch_sitl_copter_dds_udp(sitl_copter_dds_udp):
158+
"""Fixture to create the launch description."""
159+
sitl_ld, sitl_actions = sitl_copter_dds_udp
160+
161+
ld = LaunchDescription(
162+
[
163+
sitl_ld,
164+
launch_pytest.actions.ReadyToTest(),
165+
]
166+
)
167+
actions = sitl_actions
168+
yield ld, actions
169+
170+
@pytest.mark.launch(fixture=launch_sitl_copter_dds_udp)
171+
def test_dds_udp_rally(launch_context, launch_sitl_copter_dds_udp):
172+
"""Test Rally Points with AP_DDS."""
173+
_, actions = launch_sitl_copter_dds_udp
174+
micro_ros_agent = actions["micro_ros_agent"].action
175+
mavproxy = actions["mavproxy"].action
176+
sitl = actions["sitl"].action
177+
178+
# Wait for process to start.
179+
process_tools.wait_for_start_sync(launch_context, micro_ros_agent, timeout=2)
180+
process_tools.wait_for_start_sync(launch_context, mavproxy, timeout=2)
181+
process_tools.wait_for_start_sync(launch_context, sitl, timeout=2)
182+
183+
rclpy.init()
184+
time.sleep(5)
185+
try:
186+
node = RallyControl()
187+
node.clear_list()
188+
clear_flag = node.clear_rally_event.wait(20)
189+
assert clear_flag, "Could not clear"
190+
node.send_rally(RALLY_0)
191+
192+
send_flag = node.add_rally_event.wait(10)
193+
assert send_flag, "Could not send Rally"
194+
195+
node.get_and_compare_rally(RALLY_0)
196+
send_flag = node.get_rally_event.wait(10)
197+
assert send_flag, "Wrong Rally point back"
198+
finally:
199+
rclpy.shutdown()
200+
yield

Tools/ros2/ardupilot_msgs/CMakeLists.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ project(ardupilot_msgs)
55
# Find dependencies.
66

77
find_package(ament_cmake REQUIRED)
8+
find_package(geographic_msgs REQUIRED)
89
find_package(geometry_msgs REQUIRED)
910
find_package(std_msgs REQUIRED)
1011
find_package(rosidl_default_generators REQUIRED)
@@ -14,11 +15,14 @@ find_package(rosidl_default_generators REQUIRED)
1415

1516
rosidl_generate_interfaces(${PROJECT_NAME}
1617
"msg/GlobalPosition.msg"
18+
"msg/Rally.msg"
1719
"msg/Status.msg"
1820
"srv/ArmMotors.srv"
1921
"srv/ModeSwitch.srv"
2022
"srv/Takeoff.srv"
21-
DEPENDENCIES geometry_msgs std_msgs
23+
"srv/RallyGet.srv"
24+
"srv/RallySet.srv"
25+
DEPENDENCIES geographic_msgs geometry_msgs std_msgs
2226
ADD_LINTER_TESTS
2327
)
2428

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Rally message provides a geographic point and
2+
# its altitude reference frame.
3+
4+
geographic_msgs/GeoPoint point
5+
6+
# Altitude reference frame
7+
uint8 ALTITUDE_FRAME_ABSOLUTE = 0
8+
uint8 ALTITUDE_FRAME_HOME = 1
9+
uint8 ALTITUDE_FRAME_ORIGIN = 2
10+
uint8 ALTITUDE_FRAME_TERRAIN = 3
11+
uint8 altitude_frame
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# This service requests a Rally point from its ID.
2+
3+
# Rally Point ID.
4+
uint8 index
5+
---
6+
# True is the retrieved point is valid.
7+
bool success
8+
# Size of Rally Points list.
9+
uint8 size
10+
# Rally point.
11+
ardupilot_msgs/Rally rally
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# This service appends a Rally Point to the list or clears the whole list.
2+
3+
# Rally Point.
4+
ardupilot_msgs/Rally rally
5+
6+
# True to clear the whole list (rally entry is ignored).
7+
bool clear
8+
---
9+
# True if the action is successful.
10+
bool success
11+
# Size of Rally Points list.
12+
uint8 size

libraries/AP_DDS/AP_DDS_Client.cpp

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
#if AP_DDS_VTOL_TAKEOFF_SERVER_ENABLED
3333
#include "ardupilot_msgs/srv/Takeoff.h"
3434
#endif // AP_DDS_VTOL_TAKEOFF_SERVER_ENABLED
35+
#if AP_DDS_RALLY_SERVER_ENABLED
36+
#include <AP_Rally/AP_Rally.h>
37+
#include "ardupilot_msgs/srv/RallyGet.h"
38+
#include "ardupilot_msgs/srv/RallySet.h"
39+
#endif // AP_DDS_RALLY_SERVER_ENABLED
3540

3641
#if AP_EXTERNAL_CONTROL_ENABLED
3742
#include "AP_DDS_ExternalControl.h"
@@ -976,6 +981,110 @@ void AP_DDS_Client::on_request(uxrSession* uxr_session, uxrObjectId object_id, u
976981
break;
977982
}
978983
#endif //AP_DDS_ARM_CHECK_SERVER_ENABLED
984+
#if AP_DDS_RALLY_SERVER_ENABLED
985+
case services[to_underlying(ServiceIndex::GET_RALLY)].rep_id: {
986+
ardupilot_msgs_srv_RallyGet_Request rally_get_request;
987+
ardupilot_msgs_srv_RallyGet_Response rally_get_response;
988+
const bool deserialize_success = ardupilot_msgs_srv_RallyGet_Request_deserialize_topic(ub, &rally_get_request);
989+
if (deserialize_success == false) {
990+
break;
991+
}
992+
993+
const uxrObjectId replier_id = {
994+
.id = services[to_underlying(ServiceIndex::GET_RALLY)].rep_id,
995+
.type = UXR_REPLIER_ID
996+
};
997+
998+
RallyLocation rally_location {};
999+
const AP_Rally *rally = AP::rally();
1000+
if (rally->get_rally_point_with_index(rally_get_request.index, rally_location)) {
1001+
rally_get_response.success = true;
1002+
rally_get_response.size = rally->get_rally_total();
1003+
rally_get_response.rally.point.latitude = rally_location.lat * 1e-7;
1004+
rally_get_response.rally.point.longitude = rally_location.lng * 1e-7;
1005+
rally_get_response.rally.point.altitude = rally_location.alt;
1006+
bool alt_frame_valid = rally_location.flags & (1 << 2);
1007+
if (alt_frame_valid) {
1008+
rally_get_response.rally.altitude_frame = (rally_location.flags >> 3) & ((1 << 2) - 1);
1009+
} else {
1010+
rally_get_response.rally.altitude_frame = static_cast<uint8_t>(Location::AltFrame::ABOVE_HOME);
1011+
}
1012+
} else {
1013+
rally_get_response.success = false;
1014+
}
1015+
1016+
uint8_t reply_buffer[ardupilot_msgs_srv_RallyGet_Response_size_of_topic(&rally_get_response, 0)] {};
1017+
ucdrBuffer reply_ub;
1018+
1019+
ucdr_init_buffer(&reply_ub, reply_buffer, sizeof(reply_buffer));
1020+
const bool serialize_success = ardupilot_msgs_srv_RallyGet_Response_serialize_topic(&reply_ub, &rally_get_response);
1021+
if (serialize_success == false) {
1022+
break;
1023+
}
1024+
1025+
uxr_buffer_reply(uxr_session, reliable_out, replier_id, sample_id, reply_buffer, ucdr_buffer_length(&reply_ub));
1026+
1027+
break;
1028+
}
1029+
case services[to_underlying(ServiceIndex::SET_RALLY)].rep_id: {
1030+
1031+
ardupilot_msgs_srv_RallySet_Request rally_set_request;
1032+
ardupilot_msgs_srv_RallySet_Response rally_set_response;
1033+
const bool deserialize_success = ardupilot_msgs_srv_RallySet_Request_deserialize_topic(ub, &rally_set_request);
1034+
if (deserialize_success == false) {
1035+
break;
1036+
}
1037+
1038+
const uxrObjectId replier_id = {
1039+
.id = services[to_underlying(ServiceIndex::SET_RALLY)].rep_id,
1040+
.type = UXR_REPLIER_ID
1041+
};
1042+
1043+
RallyLocation rally_location{};
1044+
AP_Rally *rally = AP::rally();
1045+
1046+
if (rally_set_request.clear) {
1047+
rally->truncate(0);
1048+
rally_set_response.success = rally->get_rally_total() == 0;
1049+
} else {
1050+
rally_location.lat = static_cast<int32_t>(rally_set_request.rally.point.latitude * 1e7);
1051+
rally_location.lng = static_cast<int32_t>(rally_set_request.rally.point.longitude * 1e7);
1052+
rally_location.alt = static_cast<int16_t>(rally_set_request.rally.point.altitude);
1053+
rally_location.flags = 0;
1054+
rally_location.flags |= (1 << 2); // Use the provided frame.
1055+
rally_location.flags |= (rally_set_request.rally.altitude_frame << 3);
1056+
1057+
if (rally_location.lat != 0 && rally_location.lng != 0
1058+
&& (rally_set_request.rally.altitude_frame == static_cast<uint8_t>(Location::AltFrame::ABOVE_HOME)
1059+
|| rally_set_request.rally.altitude_frame == static_cast<uint8_t>(Location::AltFrame::ABOVE_ORIGIN)
1060+
|| rally_set_request.rally.altitude_frame == static_cast<uint8_t>(Location::AltFrame::ABOVE_TERRAIN)
1061+
|| rally_set_request.rally.altitude_frame == static_cast<uint8_t>(Location::AltFrame::ABSOLUTE))) {
1062+
if (rally->append(rally_location)) {
1063+
rally_set_response.success = true;
1064+
} else {
1065+
rally_set_response.success = false;
1066+
}
1067+
} else {
1068+
rally_set_response.success = false;
1069+
}
1070+
}
1071+
1072+
rally_set_response.size = rally->get_rally_total();
1073+
1074+
uint8_t reply_buffer[ardupilot_msgs_srv_RallySet_Response_size_of_topic(&rally_set_response, 0)] {};
1075+
ucdrBuffer reply_ub;
1076+
1077+
ucdr_init_buffer(&reply_ub, reply_buffer, sizeof(reply_buffer));
1078+
const bool serialize_success = ardupilot_msgs_srv_RallySet_Response_serialize_topic(&reply_ub, &rally_set_response);
1079+
if (serialize_success == false) {
1080+
break;
1081+
}
1082+
1083+
uxr_buffer_reply(uxr_session, reliable_out, replier_id, sample_id, reply_buffer, ucdr_buffer_length(&reply_ub));
1084+
1085+
break;
1086+
}
1087+
#endif // AP_DDS_RALLY_SERVER_ENABLED
9791088
#if AP_DDS_PARAMETER_SERVER_ENABLED
9801089
case services[to_underlying(ServiceIndex::SET_PARAMETERS)].rep_id: {
9811090
const bool deserialize_success = rcl_interfaces_srv_SetParameters_Request_deserialize_topic(ub, &set_parameter_request);

0 commit comments

Comments
 (0)