SMACC2
Loading...
Searching...
No Matches
cp_tf_broadcaster.cpp
Go to the documentation of this file.
1// Copyright 2026 RobosoftAI Inc.
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/*****************************************************************************************************************
16 *
17 * Authors: Brett Aldrich
18 *
19 ******************************************************************************************************************/
20
23
24#include <geometry_msgs/msg/pose_stamped.hpp>
25#include <geometry_msgs/msg/transform_stamped.hpp>
26
27#include <tf2/LinearMath/Quaternion.h>
28#include <cmath>
29
30#include <vector>
31
32namespace cl_px4_mr
33{
34
35namespace
36{
37// px4_ros_com frame_transforms constants (tf2 order x, y, z, w):
38// NED <-> ENU is RotZ(+90 deg) * RotX(180 deg), its own inverse.
39const tf2::Quaternion kNedEnuQ(0.70710678118654752, 0.70710678118654752, 0.0, 0.0);
40// FRD <-> FLU is RotX(180 deg), its own inverse.
41const tf2::Quaternion kFrdFluQ(1.0, 0.0, 0.0, 0.0);
42} // namespace
43
45
47
49{
51 if (localPosition_ == nullptr)
52 {
53 RCLCPP_ERROR(
54 getLogger(), "CpTfBroadcaster: CpVehicleLocalPosition not found - no TF will be published");
55 return;
56 }
57
58 auto node = this->getNode();
59 broadcaster_ = std::make_shared<tf2_ros::TransformBroadcaster>(node);
60 staticBroadcaster_ = std::make_shared<tf2_ros::StaticTransformBroadcaster>(node);
61
63 {
64 geometry_msgs::msg::TransformStamped t;
65 t.header.stamp = node->now();
66 t.header.frame_id = params_.baseFrame;
67 t.child_frame_id = params_.sensorFrame;
68 t.transform.translation.x = params_.sensorXyz[0];
69 t.transform.translation.y = params_.sensorXyz[1];
70 t.transform.translation.z = params_.sensorXyz[2];
71 tf2::Quaternion q;
73 t.transform.rotation.x = q.x();
74 t.transform.rotation.y = q.y();
75 t.transform.rotation.z = q.z();
76 t.transform.rotation.w = q.w();
77 staticBroadcaster_->sendTransform(t);
78 RCLCPP_INFO(
79 getLogger(), "CpTfBroadcaster: static %s -> %s at FLU (%.3f, %.3f, %.3f)",
82 }
83
85 {
86 trailPub_ = node->create_publisher<nav_msgs::msg::Path>(
87 params_.trailTopic, rclcpp::QoS(1).transient_local());
88 trail_.header.frame_id = params_.mapFrame;
89 RCLCPP_INFO(
90 getLogger(), "CpTfBroadcaster: publishing the flown trail on %s (every %.2f m, <= %.0f Hz)",
92 }
93
94 attitudeSub_ = node->create_subscription<px4_msgs::msg::VehicleAttitude>(
95 "/fmu/out/vehicle_attitude", rclcpp::SensorDataQoS(),
96 std::bind(&CpTfBroadcaster::onAttitude, this, std::placeholders::_1));
97
98 RCLCPP_INFO(
99 getLogger(),
100 "CpTfBroadcaster: broadcasting %s -> %s (and %s) from /fmu/out/vehicle_attitude at <= %.0f Hz",
101 params_.mapFrame.c_str(), params_.baseFrame.c_str(), params_.followFrame.c_str(),
103}
104
105void CpTfBroadcaster::onAttitude(const px4_msgs::msg::VehicleAttitude::SharedPtr msg)
106{
107 const auto now = std::chrono::steady_clock::now();
108 if (firstSent_ && params_.maxRateHz > 0.0)
109 {
110 const double elapsed = std::chrono::duration<double>(now - lastSent_).count();
111 if (elapsed < 1.0 / params_.maxRateHz)
112 {
113 return;
114 }
115 }
116 if (!localPosition_->isValid())
117 {
118 return;
119 }
120
121 // PX4 quaternion is (w, x, y, z), rotation FRD body -> NED earth
122 const tf2::Quaternion qNedFrd(msg->q[1], msg->q[2], msg->q[3], msg->q[0]);
123 tf2::Quaternion qEnuFlu = kNedEnuQ * qNedFrd * kFrdFluQ;
124 qEnuFlu.normalize();
125
126 const auto stamp = this->getNode()->now();
127
128 geometry_msgs::msg::TransformStamped base;
129 base.header.stamp = stamp;
130 base.header.frame_id = params_.mapFrame;
131 base.child_frame_id = params_.baseFrame;
132 base.transform.translation.x = localPosition_->getY(); // east
133 base.transform.translation.y = localPosition_->getX(); // north
134 base.transform.translation.z = -localPosition_->getZ(); // up
135 base.transform.rotation.x = qEnuFlu.x();
136 base.transform.rotation.y = qEnuFlu.y();
137 base.transform.rotation.z = qEnuFlu.z();
138 base.transform.rotation.w = qEnuFlu.w();
139
140 geometry_msgs::msg::TransformStamped follow = base;
141 follow.child_frame_id = params_.followFrame;
142 follow.transform.rotation.x = 0.0;
143 follow.transform.rotation.y = 0.0;
144 follow.transform.rotation.z = 0.0;
145 follow.transform.rotation.w = 1.0;
146
147 broadcaster_->sendTransform(std::vector<geometry_msgs::msg::TransformStamped>{base, follow});
148 lastSent_ = now;
149
150 if (trailPub_)
151 {
152 const auto & t = base.transform.translation;
153 bool append = trail_.poses.empty();
154 if (!append)
155 {
156 const auto & l = trail_.poses.back().pose.position;
157 const double dx = t.x - l.x, dy = t.y - l.y, dz = t.z - l.z;
158 append = std::sqrt(dx * dx + dy * dy + dz * dz) >= params_.trailMinStepM;
159 }
160 if (append)
161 {
162 geometry_msgs::msg::PoseStamped ps;
163 ps.header = base.header;
164 ps.pose.position.x = t.x;
165 ps.pose.position.y = t.y;
166 ps.pose.position.z = t.z;
167 ps.pose.orientation = base.transform.rotation;
168 trail_.poses.push_back(ps);
169 if (trail_.poses.size() > params_.trailMaxPoses)
170 {
171 trail_.poses.erase(trail_.poses.begin());
172 }
173 const double sinceTrail = std::chrono::duration<double>(now - lastTrailSent_).count();
174 if (params_.trailRateHz <= 0.0 || sinceTrail >= 1.0 / params_.trailRateHz)
175 {
176 trail_.header.stamp = stamp;
177 trailPub_->publish(trail_);
178 lastTrailSent_ = now;
179 }
180 }
181 }
182 if (!firstSent_)
183 {
184 firstSent_ = true;
185 RCLCPP_INFO(
186 getLogger(), "CpTfBroadcaster: first %s -> %s sent (ENU %.2f, %.2f, %.2f)",
187 params_.mapFrame.c_str(), params_.baseFrame.c_str(), base.transform.translation.x,
188 base.transform.translation.y, base.transform.translation.z);
189 }
190}
191
192} // namespace cl_px4_mr
std::chrono::steady_clock::time_point lastTrailSent_
std::chrono::steady_clock::time_point lastSent_
rclcpp::Subscription< px4_msgs::msg::VehicleAttitude >::SharedPtr attitudeSub_
rclcpp::Publisher< nav_msgs::msg::Path >::SharedPtr trailPub_
std::shared_ptr< tf2_ros::StaticTransformBroadcaster > staticBroadcaster_
void onAttitude(const px4_msgs::msg::VehicleAttitude::SharedPtr msg)
std::shared_ptr< tf2_ros::TransformBroadcaster > broadcaster_
CpVehicleLocalPosition * localPosition_
CpTfBroadcaster(TfBroadcasterParams params={})
rclcpp::Logger getLogger() const
void requiresComponent(TComponent *&requiredComponentStorage, ComponentRequirement requirementType=ComponentRequirement::SOFT)
rclcpp::Node::SharedPtr getNode()
const tf2::Quaternion kFrdFluQ(1.0, 0.0, 0.0, 0.0)
const tf2::Quaternion kNedEnuQ(0.70710678118654752, 0.70710678118654752, 0.0, 0.0)