AutoAPMS
Streamlining behaviors in ROS 2
Loading...
Searching...
No Matches
vehicle_command_client.cpp
1// Copyright 2024 Robin Müller
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "auto_apms_px4/vehicle_command_client.hpp"
16
17#include <functional>
18
19#include "px4_ros2/utils/message_version.hpp"
20
21namespace auto_apms_px4
22{
23
24std::string toStr(VehicleCommandClient::SendCommandResult result)
25{
26 switch (result) {
27 case VehicleCommandClient::SendCommandResult::ACCEPTED:
28 return "ACCEPTED";
29 case VehicleCommandClient::SendCommandResult::REJECTED:
30 return "REJECTED";
31 case VehicleCommandClient::SendCommandResult::TIMEOUT:
32 return "TIMEOUT";
33 default:
34 return "undefined";
35 }
36}
37
38VehicleCommandClient::VehicleCommandClient(rclcpp::Node & node, const std::chrono::milliseconds & command_timeout)
39: node_{node}, logger_{node.get_logger().get_child("vehicle_command_client")}, command_timeout_{command_timeout}
40{
41 // Create vehicle command publisher and acknowledgement signal subscriber
42 vehicle_command_pub_ = node_.create_publisher<px4_msgs::msg::VehicleCommand>(
43 "fmu/in/vehicle_command" + px4_ros2::getMessageNameVersion<px4_msgs::msg::VehicleCommand>(), 10);
44
45 vehicle_command_ack_sub_ = node_.create_subscription<px4_msgs::msg::VehicleCommandAck>(
46 "fmu/out/vehicle_command_ack" + px4_ros2::getMessageNameVersion<px4_msgs::msg::VehicleCommandAck>(),
47 rclcpp::QoS(1).best_effort(), [](px4_msgs::msg::VehicleCommandAck::UniquePtr msg) { (void)msg; });
48
49 // Used by syncActivateFlightModeAndWait() to confirm that a commanded mode change actually took effect. Like the
50 // acknowledgement subscription, messages are drained synchronously via a wait set (see below), so the callback is a
51 // no-op.
52 vehicle_status_sub_ = node_.create_subscription<px4_msgs::msg::VehicleStatus>(
53 "fmu/out/vehicle_status" + px4_ros2::getMessageNameVersion<px4_msgs::msg::VehicleStatus>(), rclcpp::SensorDataQoS{},
54 [](px4_msgs::msg::VehicleStatus::UniquePtr msg) { (void)msg; });
55}
56
57VehicleCommandClient::SendCommandResult VehicleCommandClient::syncSendVehicleCommand(
58 uint32_t command, float param1, float param2, float param3, float param4, float param5, float param6,
59 float param7) const
60{
61 px4_msgs::msg::VehicleCommand cmd{};
62 cmd.command = command;
63 cmd.param1 = param1;
64 cmd.param2 = param2;
65 cmd.param3 = param3;
66 cmd.param4 = param4;
67 cmd.param5 = param5;
68 cmd.param6 = param6;
69 cmd.param7 = param7;
70 cmd.timestamp = node_.get_clock()->now().nanoseconds() / 1000;
71
72 return syncSendVehicleCommand(cmd);
73}
74
75VehicleCommandClient::SendCommandResult VehicleCommandClient::syncSendVehicleCommand(
76 const px4_msgs::msg::VehicleCommand & cmd) const
77{
78 // Default to rejected, will be set to accepted if acknowledgement received
79 SendCommandResult result = SendCommandResult::REJECTED;
80 using AckWaitSet = rclcpp::StaticWaitSet<1, 0, 0, 0, 0, 0>;
81 AckWaitSet wait_set({{{vehicle_command_ack_sub_}}});
82
83 bool got_reply = false;
84 auto start_time = std::chrono::steady_clock::now();
85 vehicle_command_pub_->publish(cmd);
86
87 while (!got_reply) {
88 auto now = std::chrono::steady_clock::now();
89
90 if (now >= start_time + command_timeout_) {
91 break;
92 }
93
94 rclcpp::WaitResult<AckWaitSet> wait_ret = wait_set.wait(command_timeout_ - (now - start_time));
95
96 if (wait_ret.kind() == rclcpp::WaitResultKind::Ready) {
97 px4_msgs::msg::VehicleCommandAck ack;
98 rclcpp::MessageInfo info;
99
100 if (vehicle_command_ack_sub_->take(ack, info)) {
101 if (ack.command == cmd.command && ack.target_component == cmd.source_component) {
102 RCLCPP_DEBUG(
103 logger_, "syncSendVehicleCommand: Command %i - Received acknowledgement %i", cmd.command, ack.result);
104 if (ack.result == px4_msgs::msg::VehicleCommandAck::VEHICLE_CMD_RESULT_ACCEPTED) {
105 result = SendCommandResult::ACCEPTED;
106 }
107 got_reply = true;
108 }
109 } else {
110 RCLCPP_DEBUG(logger_, "syncSendVehicleCommand: Command %i - Acknowledgement message not valid", cmd.command);
111 }
112 }
113 }
114
115 if (!got_reply) {
116 result = SendCommandResult::TIMEOUT;
117 RCLCPP_WARN(logger_, "syncSendVehicleCommand: Command %i - Timeout, no acknowledgement received", cmd.command);
118 }
119
120 RCLCPP_DEBUG(logger_, "syncSendVehicleCommand: Command %i - Result code: %s", cmd.command, toStr(result).c_str());
121
122 return result;
123}
124
125bool VehicleCommandClient::arm() const
126{
127 return !syncSendVehicleCommand(VehicleCommand::VEHICLE_CMD_COMPONENT_ARM_DISARM, 1);
128}
129
130bool VehicleCommandClient::disarm() const
131{
132 return !syncSendVehicleCommand(VehicleCommand::VEHICLE_CMD_COMPONENT_ARM_DISARM, 0);
133}
134
136{
137 return !syncSendVehicleCommand(VehicleCommand::VEHICLE_CMD_MISSION_START, 0);
138}
139
140bool VehicleCommandClient::takeoff(float altitude_amsl_m, float heading_rad) const
141{
142 RCLCPP_DEBUG(logger_, "Takeoff parameters: altitude_amsl_m=%f heading_rad=%f", altitude_amsl_m, heading_rad);
143 return !syncSendVehicleCommand(
144 VehicleCommand::VEHICLE_CMD_NAV_TAKEOFF, NAN, NAN, NAN, heading_rad, NAN, NAN, altitude_amsl_m);
145}
146
147bool VehicleCommandClient::land() const { return !syncSendVehicleCommand(VehicleCommand::VEHICLE_CMD_NAV_LAND); }
148
149bool VehicleCommandClient::syncActivateFlightMode(uint8_t mode_id) const
150{
151 return !syncSendVehicleCommand(VehicleCommand::VEHICLE_CMD_SET_NAV_STATE, mode_id);
152}
153
154bool VehicleCommandClient::syncActivateFlightMode(const FlightMode & mode) const
155{
156 return syncActivateFlightMode(static_cast<uint8_t>(mode));
157}
158
159bool VehicleCommandClient::syncActivateFlightMode(const px4_ros2::ModeBase * const mode_ptr) const
160{
161 return syncActivateFlightMode(mode_ptr->id());
162}
163
165 uint8_t mode_id, const std::chrono::milliseconds & timeout) const
166{
167 // First request the mode change and make sure the FMU accepts the command at all.
168 if (syncSendVehicleCommand(VehicleCommand::VEHICLE_CMD_SET_NAV_STATE, mode_id) != SendCommandResult::ACCEPTED) {
169 RCLCPP_WARN(logger_, "syncActivateFlightModeAndWait: Command to switch to nav_state %u was not accepted", mode_id);
170 return false;
171 }
172
173 // Then wait for the vehicle to actually report the requested nav_state as active. Drain the status subscription
174 // synchronously via a wait set, mirroring syncSendVehicleCommand(), so this does not depend on the node being spun
175 // by an external executor.
176 using StatusWaitSet = rclcpp::StaticWaitSet<1, 0, 0, 0, 0, 0>;
177 StatusWaitSet wait_set({{{vehicle_status_sub_}}});
178
179 const auto start_time = std::chrono::steady_clock::now();
180 while (true) {
181 const auto now = std::chrono::steady_clock::now();
182 if (now >= start_time + timeout) {
183 RCLCPP_WARN(logger_, "syncActivateFlightModeAndWait: Timeout waiting for nav_state %u to become active", mode_id);
184 return false;
185 }
186
187 const rclcpp::WaitResult<StatusWaitSet> wait_ret = wait_set.wait(timeout - (now - start_time));
188 if (wait_ret.kind() != rclcpp::WaitResultKind::Ready) continue;
189
190 px4_msgs::msg::VehicleStatus status;
191 rclcpp::MessageInfo info;
192 if (vehicle_status_sub_->take(status, info) && status.nav_state == mode_id) {
193 RCLCPP_DEBUG(logger_, "syncActivateFlightModeAndWait: nav_state %u is now active", mode_id);
194 return true;
195 }
196 }
197}
198
200 const px4_ros2::ModeBase * const mode_ptr, const std::chrono::milliseconds & timeout) const
201{
202 return syncActivateFlightModeAndWait(mode_ptr->id(), timeout);
203}
204
205} // namespace auto_apms_px4
bool syncActivateFlightModeAndWait(uint8_t mode_id, const std::chrono::milliseconds &timeout) const
Activate a flight mode and block until the vehicle actually reports it as active.
bool startMission() const
(Re)starts the uploaded mission plan.
const char * toStr(const ActionNodeErrorCode &err)
Convert the action error code to string.
Implementation of PX4 mode peers offered by px4_ros2_cpp enabling integration with AutoAPMS.