15#include "auto_apms_px4/node/send_vehicle_command.hpp"
19#include "px4_ros2/utils/message_version.hpp"
25#ifndef AUTO_APMS_PX4_SOURCE_COMPONENT_GLOBAL_KEY
26#error "AUTO_APMS_PX4_SOURCE_COMPONENT_GLOBAL_KEY must be defined (see CMakeLists.txt)"
28#ifndef AUTO_APMS_PX4_MODE_EXECUTOR_ACTIVE_GLOBAL_KEY
29#error "AUTO_APMS_PX4_MODE_EXECUTOR_ACTIVE_GLOBAL_KEY must be defined (see CMakeLists.txt)"
35SendVehicleCommand::AckSubscription::AckSubscription(
36 rclcpp::Node::SharedPtr node, rclcpp::CallbackGroup::SharedPtr group,
const std::string & topic_name)
39 rclcpp::SubscriptionOptions options;
40 options.callback_group = group;
43 subscription = node->create_subscription<Ack>(
44 topic_name, rclcpp::SensorDataQoS(), [
this](Ack::SharedPtr msg) { broadcast(msg); }, options);
47void SendVehicleCommand::AckSubscription::addListener(
48 const void * owner, std::function<
void(
const Ack::SharedPtr &)> callback)
50 listeners.emplace_back(owner, std::move(callback));
53void SendVehicleCommand::AckSubscription::removeListener(
const void * owner)
56 std::remove_if(listeners.begin(), listeners.end(), [owner](
const auto & pair) { return pair.first == owner; }),
60void SendVehicleCommand::AckSubscription::broadcast(
const Ack::SharedPtr & msg)
62 for (
const auto & [owner, callback] : listeners) callback(msg);
65SendVehicleCommand::SendVehicleCommand(
66 const std::string & instance_name,
const Config & config,
const Context & context)
67: RosActionNodeBase(instance_name, config, context)
70 const rclcpp::Node::SharedPtr node = context_.getRosNode();
71 const rclcpp::CallbackGroup::SharedPtr group = context_.getWaitablesCallbackGroup();
77 const std::string command_topic = resolveCommandTopicName();
78 command_pub_ = getSharedEntity<rclcpp::Publisher<px4_msgs::msg::VehicleCommand>>(
79 command_topic, [&] {
return node->create_publisher<px4_msgs::msg::VehicleCommand>(command_topic, 10); });
87 const std::string ack_topic =
88 "fmu/out/vehicle_command_ack" + px4_ros2::getMessageNameVersion<px4_msgs::msg::VehicleCommandAck>();
89 ack_sub_ = getSharedEntity<AckSubscription>(
90 ack_topic, [&] {
return std::make_shared<AckSubscription>(node, group, ack_topic); });
91 ack_sub_->addListener(
this, [
this](
const px4_msgs::msg::VehicleCommandAck::SharedPtr & msg) {
92 if (waiting_for_ack_ && msg->command == tx_msg_.command) last_ack_ = msg;
96 ack_timeout_s_ = std::chrono::duration<double>(context_.getRegistrationOptions().request_timeout).count();
99 logger_,
"%s - Using command publisher on '%s' and acknowledgement subscription on '%s'.",
100 context_.getFullyQualifiedTreeNodeName(
this).c_str(), command_pub_->get_topic_name(),
101 ack_sub_->subscription->get_topic_name());
104SendVehicleCommand::~SendVehicleCommand()
107 if (ack_sub_) ack_sub_->removeListener(
this);
110std::string SendVehicleCommand::resolveCommandTopicName()
114 if (
const BT::Expected<std::string> expected = context_.getTopicName(
this); expected && !expected.value().empty()) {
115 return expected.value();
121 bool via_mode_executor =
false;
122 (void)config().blackboard->get<
bool>(AUTO_APMS_PX4_MODE_EXECUTOR_ACTIVE_GLOBAL_KEY, via_mode_executor);
123 const std::string base = via_mode_executor ?
"fmu/in/vehicle_command_mode_executor" :
"fmu/in/vehicle_command";
126 return base + px4_ros2::getMessageNameVersion<px4_msgs::msg::VehicleCommand>();
129BT::PortsList SendVehicleCommand::providedPorts()
132 BT::InputPort<int>(PORT_KEY_COMMAND,
"VehicleCommand command id (see px4_msgs/msg/VehicleCommand)."),
133 BT::InputPort<double>(PORT_KEY_PARAM1, 0.0,
"Command parameter 1."),
134 BT::InputPort<double>(PORT_KEY_PARAM2, 0.0,
"Command parameter 2."),
135 BT::InputPort<double>(PORT_KEY_PARAM3, 0.0,
"Command parameter 3."),
136 BT::InputPort<double>(PORT_KEY_PARAM4, 0.0,
"Command parameter 4."),
137 BT::InputPort<double>(PORT_KEY_PARAM5, 0.0,
"Command parameter 5."),
138 BT::InputPort<double>(PORT_KEY_PARAM6, 0.0,
"Command parameter 6."),
139 BT::InputPort<double>(PORT_KEY_PARAM7, 0.0,
"Command parameter 7."),
140 BT::InputPort<int>(PORT_KEY_CONFIRMATION, 0,
"Confirmation count (0 = first transmission of this command)."),
141 BT::InputPort<int>(PORT_KEY_TARGET_SYSTEM, 0,
"System that should execute the command."),
142 BT::InputPort<int>(PORT_KEY_TARGET_COMPONENT, 0,
"Component that should execute the command (0 = all)."),
148 const BT::Expected<int> expected_command = getInput<int>(PORT_KEY_COMMAND);
149 if (!expected_command) {
151 logger_,
"%s - Missing required input '%s': %s", context_.getFullyQualifiedTreeNodeName(
this).c_str(),
152 PORT_KEY_COMMAND, expected_command.error().c_str());
156 msg = px4_msgs::msg::VehicleCommand{};
157 msg.command =
static_cast<uint32_t
>(expected_command.value());
158 msg.param1 =
static_cast<float>(getInput<double>(PORT_KEY_PARAM1).value_or(0.0));
159 msg.param2 =
static_cast<float>(getInput<double>(PORT_KEY_PARAM2).value_or(0.0));
160 msg.param3 =
static_cast<float>(getInput<double>(PORT_KEY_PARAM3).value_or(0.0));
161 msg.param4 =
static_cast<float>(getInput<double>(PORT_KEY_PARAM4).value_or(0.0));
162 msg.param5 = getInput<double>(PORT_KEY_PARAM5).value_or(0.0);
163 msg.param6 = getInput<double>(PORT_KEY_PARAM6).value_or(0.0);
164 msg.param7 =
static_cast<float>(getInput<double>(PORT_KEY_PARAM7).value_or(0.0));
165 msg.confirmation =
static_cast<uint8_t
>(getInput<int>(PORT_KEY_CONFIRMATION).value_or(0));
166 msg.target_system =
static_cast<uint8_t
>(getInput<int>(PORT_KEY_TARGET_SYSTEM).value_or(0));
167 msg.target_component =
static_cast<uint8_t
>(getInput<int>(PORT_KEY_TARGET_COMPONENT).value_or(0));
168 msg.source_system = 0;
170 msg.from_external =
true;
180 int source_component = 0;
181 (void)config().blackboard->get<
int>(AUTO_APMS_PX4_SOURCE_COMPONENT_GLOBAL_KEY, source_component);
182 return static_cast<uint16_t
>(source_component);
185BT::NodeStatus SendVehicleCommand::tick()
189 return BT::NodeStatus::FAILURE;
193 if (status() == BT::NodeStatus::IDLE) {
194 setStatus(BT::NodeStatus::RUNNING);
196 if (!setMessage(tx_msg_))
return BT::NodeStatus::FAILURE;
199 waiting_for_ack_ =
true;
200 send_time_ = context_.getCurrentTime();
201 command_pub_->publish(tx_msg_);
204 logger_,
"%s - Sent VehicleCommand %u and waiting for acknowledgement (timeout %.2fs).",
205 context_.getFullyQualifiedTreeNodeName(
this).c_str(), tx_msg_.command, ack_timeout_s_);
207 return BT::NodeStatus::RUNNING;
212 const uint8_t result = last_ack_->result;
213 waiting_for_ack_ =
false;
214 using Ack = px4_msgs::msg::VehicleCommandAck;
215 if (result == Ack::VEHICLE_CMD_RESULT_ACCEPTED || result == Ack::VEHICLE_CMD_RESULT_IN_PROGRESS) {
217 logger_,
"%s - VehicleCommand %u accepted (result %u).", context_.getFullyQualifiedTreeNodeName(
this).c_str(),
218 tx_msg_.command, result);
219 return BT::NodeStatus::SUCCESS;
222 logger_,
"%s - VehicleCommand %u was not accepted (result %u).",
223 context_.getFullyQualifiedTreeNodeName(
this).c_str(), tx_msg_.command, result);
224 return BT::NodeStatus::FAILURE;
227 if ((context_.getCurrentTime() - send_time_).seconds() > ack_timeout_s_) {
228 waiting_for_ack_ =
false;
230 logger_,
"%s - Timed out after %.2fs waiting for acknowledgement of VehicleCommand %u.",
231 context_.getFullyQualifiedTreeNodeName(
this).c_str(), ack_timeout_s_, tx_msg_.command);
232 return BT::NodeStatus::FAILURE;
235 return BT::NodeStatus::RUNNING;
238void SendVehicleCommand::halt()
241 waiting_for_ack_ =
false;
254#ifdef AUTO_APMS_PX4_EXPORT_NODE_PLUGIN
Generic behavior tree node that publishes a PX4 VehicleCommand and waits for its acknowledgement.
uint16_t resolveSourceComponent()
Transitively resolve the in-charge mode executor's source component from the global blackboard.
virtual bool setMessage(px4_msgs::msg::VehicleCommand &msg)
Populate the VehicleCommand message to publish.
#define AUTO_APMS_BEHAVIOR_TREE_REGISTER_NODE(type)
Macro for registering a behavior tree node plugin.
Implementation of PX4 mode peers offered by px4_ros2_cpp enabling integration with AutoAPMS.