SMACC2
Loading...
Searching...
No Matches
cp_odom_tracker.cpp
Go to the documentation of this file.
1// Copyright 2021 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: Pablo Inigo Blasco, Brett Aldrich
18 *
19 ******************************************************************************************************************/
21
22#include <angles/angles.h>
23#include <tf2/utils.h>
25
26#include <boost/range/adaptor/reversed.hpp>
27
28namespace cl_nav2z
29{
30namespace odom_tracker
31{
32CpOdomTracker::CpOdomTracker(std::string odomTopicName, std::string odomFrame)
33{
35 publishMessages = true;
37 odomFrame_ = odomFrame;
38 odomTopicName_ = odomTopicName;
39}
40
41template <typename T>
43 rclcpp::Node::SharedPtr & node, std::string param_name, T & value)
44{
45 if (!node->get_parameter(param_name, value))
46 {
47 RCLCPP_INFO_STREAM(
48 node->get_logger(), "[CpOdomTracker] autoset " << param_name << ": " << value);
49 node->declare_parameter(param_name, value);
50 }
51 else
52 {
53 RCLCPP_INFO_STREAM(node->get_logger(), "[CpOdomTracker] " << param_name << ": " << value);
54 }
55}
56
58{
59 // default values
60 recordPointDistanceThreshold_ = 0.005; // 5 mm
61 recordAngularDistanceThreshold_ = 0.1; // radians
62 clearPointDistanceThreshold_ = 0.05; // 5 cm
64
65 auto nh = getNode();
66 RCLCPP_WARN(getLogger(), "[CpOdomTracker] Initializing Odometry Tracker");
67
68 parameterDeclareAndtryGetOrSet(nh, "odom_frame", this->odomFrame_);
70 nh, "record_point_distance_threshold", recordPointDistanceThreshold_);
72 nh, "record_angular_distance_threshold", recordAngularDistanceThreshold_);
74 nh, "clear_point_distance_threshold", clearPointDistanceThreshold_);
76 nh, "clear_angular_distance_threshold", clearAngularDistanceThreshold_);
77
79 {
80 RCLCPP_INFO_STREAM(
81 nh->get_logger(), "[CpOdomTracker] subscribing to odom topic: " << odomTopicName_);
82
83 rclcpp::SensorDataQoS qos;
84 odomSub_ = nh->create_subscription<nav_msgs::msg::Odometry>(
85 odomTopicName_, qos,
86 std::bind(&CpOdomTracker::processOdometryMessage, this, std::placeholders::_1));
87 }
88
90 nh->create_publisher<nav_msgs::msg::Path>("odom_tracker_path", rclcpp::QoS(1));
92 nh->create_publisher<nav_msgs::msg::Path>("odom_tracker_stacked_path", rclcpp::QoS(1));
93}
94
101{
102 std::lock_guard<std::mutex> lock(m_mutex_);
103
104 switch (workingMode)
105 {
107 RCLCPP_INFO_STREAM(
108 getLogger(),
109 "[CpOdomTracker] setting working mode to RECORD - record_point_distance_threshold: "
111 << ", record_angular_distance_threshold: " << recordAngularDistanceThreshold_);
112 break;
114 RCLCPP_INFO_STREAM(
115 getLogger(),
116 "[CpOdomTracker] setting working mode to CLEAR - clear_point_distance_threshold: "
118 << ", clear_angular_distance_threshold: " << clearAngularDistanceThreshold_);
119 break;
121 RCLCPP_INFO_STREAM(getLogger(), "[CpOdomTracker] setting working mode to IDLE");
122 break;
123 default:
124
125 RCLCPP_INFO_STREAM(getLogger(), "[CpOdomTracker] setting working mode to <UNKNOWN>");
126 }
127
128 if (this->odomSub_->get_publisher_count() == 0)
129 {
130 RCLCPP_ERROR_STREAM(
131 getLogger(),
132 "[CpOdomTracker] Odom tracker cannot record the path because there is no publisher to the "
133 "odometry topic");
134 }
135
136 workingMode_ = workingMode;
137 // RCLCPP_INFO(getLogger(),"odom_tracker m_mutex release");
138}
139
146{
147 // RCLCPP_INFO(getLogger(),"odom_tracker m_mutex acquire");
148 std::lock_guard<std::mutex> lock(m_mutex_);
149 publishMessages = value;
150 // RCLCPP_INFO(getLogger(),"odom_tracker m_mutex release");
152}
153
154void CpOdomTracker::pushPath() { this->pushPath(std::string()); }
155
156void CpOdomTracker::pushPath(std::string pathname)
157{
158 RCLCPP_INFO(getLogger(), "odom_tracker m_mutex acquire");
159 std::lock_guard<std::mutex> lock(m_mutex_);
160
161 this->logStateString(false);
162 if (this->odomSub_->get_publisher_count() == 0)
163 {
164 RCLCPP_ERROR_STREAM(
165 getLogger(),
166 "[CpOdomTracker] Odom tracker cannot record the path because there is no publisher to the "
167 "odometry topic");
168 }
169
171 pathStack_.push_back(baseTrajectory_);
172
173 geometry_msgs::msg::PoseStamped goalPose;
174 if (this->currentMotionGoal_) goalPose = *this->currentMotionGoal_;
175
176 RCLCPP_INFO_STREAM(
177 getLogger(), "[CpOdomTracker] currentPathName: " << pathname
178 << " size: " << baseTrajectory_.poses.size()
179 << " current motion goal: " << goalPose);
180
181 currentPathName_ = pathname;
182
183 // clean the current trajectory to start a new one
184 baseTrajectory_.poses.clear();
185
186 this->logStateString(false);
188
189 this->currentMotionGoal_.reset();
190}
191
192void CpOdomTracker::popPath(int popCount, bool keepPreviousPath)
193{
194 RCLCPP_INFO(getLogger(), "odom_tracker m_mutex acquire");
195 std::lock_guard<std::mutex> lock(m_mutex_);
196
197 RCLCPP_INFO(getLogger(), "POP PATH ENTRY");
198 this->logStateString();
199
200 if (this->odomSub_->get_publisher_count() == 0)
201 {
202 RCLCPP_ERROR_STREAM(
203 getLogger(),
204 "[CpOdomTracker] Odom tracker cannot record the path because there is no publisher to the "
205 "odometry topic");
206 }
207
208 if (!keepPreviousPath)
209 {
210 baseTrajectory_.poses.clear();
211 }
212
213 while (popCount > 0 && !pathStack_.empty())
214 {
215 auto & stackedPath = pathStack_.back().poses;
216 auto & stackedPathInfo = pathInfos_.back();
217
218 baseTrajectory_.poses.insert(
219 baseTrajectory_.poses.begin(), stackedPath.begin(), stackedPath.end());
220 this->currentMotionGoal_ = stackedPathInfo.goalPose;
221 pathStack_.pop_back();
222 pathInfos_.pop_back();
223 popCount--;
224
225 RCLCPP_INFO(getLogger(), "POP PATH Iteration ");
226 this->logStateString();
227 }
228
229 RCLCPP_INFO(getLogger(), "POP PATH EXITING");
230 this->logStateString();
231 RCLCPP_INFO(getLogger(), "odom_tracker m_mutex release");
233
234 this->currentMotionGoal_.reset();
235}
236
238{
239 std::stringstream ss;
240 ss << "--- odom tracker state ---" << std::endl;
241 ss
242 << " - path stack -" << currentPathName_ << " - size:"
243 << pathStack_.size()
244 //<< " goal: " << (getCurrentMotionGoal()? getCurrentMotionGoal()->pose: geometry_msgs::msg::Pose())
245 << std::endl;
246 ss << " - [STACK-HEAD active path size: " << baseTrajectory_.poses.size() << "]" << std::endl;
247 int i = 0;
248 for (auto & p : pathStack_ | boost::adaptors::reversed)
249 {
250 auto & pathinfo = pathInfos_[pathInfos_.size() - i - 1];
251 std::string goalstr = "[]";
252 if (pathinfo.goalPose)
253 {
254 std::stringstream ss;
255 ss << *(pathinfo.goalPose);
256 goalstr = ss.str();
257 }
258
259 ss << " - p " << i << "[" << p.header.stamp << "], size: " << p.poses.size() << " - "
260 << pathinfo.name << " - goal: " << goalstr << std::endl;
261 i++;
262 }
263 ss << "---";
264
265 if (this->odomSub_->get_publisher_count() == 0)
266 {
267 ss << std::endl
268 << "[CpOdomTracker] Odom tracker cannot record the path because there is no publisher to "
269 "the odometry topic"
270 << std::endl;
271 }
272
273 if (debug)
274 RCLCPP_DEBUG(getLogger(), ss.str().c_str());
275 else
276 RCLCPP_INFO(getLogger(), ss.str().c_str());
277}
278
280{
281 std::lock_guard<std::mutex> lock(m_mutex_);
282 baseTrajectory_.poses.clear();
283
284 rtPublishPaths(getNode()->now());
285 this->logStateString();
287
288 this->currentMotionGoal_.reset();
289}
290
291void CpOdomTracker::setCurrentPathName(const std::string & currentPathName)
292{
293 currentPathName_ = currentPathName;
294}
295
296void CpOdomTracker::setStartPoint(const geometry_msgs::msg::PoseStamped & pose)
297{
298 std::lock_guard<std::mutex> lock(m_mutex_);
299 RCLCPP_INFO_STREAM(getLogger(), "[CpOdomTracker] set current path starting point: " << pose);
300 if (baseTrajectory_.poses.size() > 0)
301 {
302 baseTrajectory_.poses[0] = pose;
303 }
304 else
305 {
306 baseTrajectory_.poses.push_back(pose);
307 }
309}
310
311void CpOdomTracker::setStartPoint(const geometry_msgs::msg::Pose & pose)
312{
313 std::lock_guard<std::mutex> lock(m_mutex_);
314 RCLCPP_INFO_STREAM(getLogger(), "[CpOdomTracker] set current path starting point: " << pose);
315 geometry_msgs::msg::PoseStamped posestamped;
316 posestamped.header.frame_id = this->odomFrame_;
317 posestamped.header.stamp = getNode()->now();
318 posestamped.pose = pose;
319
320 if (baseTrajectory_.poses.size() > 0)
321 {
322 baseTrajectory_.poses[0] = posestamped;
323 }
324 else
325 {
326 baseTrajectory_.poses.push_back(posestamped);
327 }
329}
330
331void CpOdomTracker::setCurrentMotionGoal(const geometry_msgs::msg::PoseStamped & pose)
332{
333 std::lock_guard<std::mutex> lock(m_mutex_);
334 this->currentMotionGoal_ = pose;
335}
336
337std::optional<geometry_msgs::msg::PoseStamped> CpOdomTracker::getCurrentMotionGoal()
338{
339 std::lock_guard<std::mutex> lock(m_mutex_);
340 return this->currentMotionGoal_;
341}
342
343nav_msgs::msg::Path CpOdomTracker::getPath()
344{
345 std::lock_guard<std::mutex> lock(m_mutex_);
346 return this->baseTrajectory_;
347}
348
354void CpOdomTracker::rtPublishPaths(rclcpp::Time timestamp)
355{
356 baseTrajectory_.header.stamp = timestamp;
358
359 aggregatedStackPathMsg_.header.stamp = timestamp;
361}
362
364{
365 aggregatedStackPathMsg_.poses.clear();
366 for (auto & p : pathStack_)
367 {
368 aggregatedStackPathMsg_.poses.insert(
369 aggregatedStackPathMsg_.poses.end(), p.poses.begin(), p.poses.end());
370 }
371
372 aggregatedStackPathMsg_.header.frame_id = this->odomFrame_;
373}
374
380bool CpOdomTracker::updateClearPath(const nav_msgs::msg::Odometry & odom)
381{
382 // we initially accept any message if the queue is empty
384 geometry_msgs::msg::PoseStamped base_pose;
385
386 base_pose.pose = odom.pose.pose;
387 base_pose.header = odom.header;
388 baseTrajectory_.header = odom.header;
389
390 bool acceptBackward = false;
391 bool clearingError = false;
392 bool finished = false;
393
394 while (!finished)
395 {
396 if (
397 baseTrajectory_.poses.size() <=
398 1) // we at least keep always the first point of the forward path when clearing
399 // (this is important for backwards planner replanning and not losing the
400 // last goal)
401 {
402 acceptBackward = false;
403 finished = true;
404 }
405 else
406 {
407 auto & carrotPose = baseTrajectory_.poses.back().pose;
408 auto & carrotPoint = carrotPose.position;
409
410 double carrotAngle = tf2::getYaw(carrotPose.orientation);
411
412 auto & currePose = base_pose.pose;
413 auto & currePoint = currePose.position;
414 double currentAngle = tf2::getYaw(currePose.orientation);
415
416 double lastpointdist = p2pDistance(carrotPoint, currePoint);
417 double goalAngleOffset = fabs(angles::shortest_angular_distance(carrotAngle, currentAngle));
418
419 acceptBackward = !baseTrajectory_.poses.empty() &&
420 lastpointdist < clearPointDistanceThreshold_ &&
421 goalAngleOffset < clearAngularDistanceThreshold_;
422
423 clearingError = lastpointdist > 2 * clearPointDistanceThreshold_;
424 RCLCPP_DEBUG_STREAM(
425 getLogger(), "[CpOdomTracker] clearing (accepted: " << acceptBackward
426 << ") linerr: " << lastpointdist
427 << ", anglerr: " << goalAngleOffset);
428 }
429
430 // RCLCPP_INFO(getLogger(),"Backwards, last distance: %lf < %lf accept: %d", dist,
431 // minPointDistanceBackwardThresh_, acceptBackward);
432 if (
433 acceptBackward &&
434 baseTrajectory_.poses.size() > 1) /* we always leave at least one item, specially interesting
435 for the backward local planner reach
436 the backwards goal with enough precision*/
437 {
438 baseTrajectory_.poses.pop_back();
439 }
440 else if (clearingError)
441 {
442 finished = true;
443 RCLCPP_WARN(getLogger(), "[CpOdomTracker] Incorrect odom clearing motion.");
444 }
445 else
446 {
447 finished = true;
449 }
450 }
451
452 return acceptBackward;
453}
459bool CpOdomTracker::updateRecordPath(const nav_msgs::msg::Odometry & odom)
460{
462 geometry_msgs::msg::PoseStamped base_pose;
463
464 base_pose.pose = odom.pose.pose;
465 base_pose.header = odom.header;
466 baseTrajectory_.header = odom.header;
467
468 bool enqueueOdomMessage = false;
469
470 double dist = -1;
471 if (baseTrajectory_.poses.empty())
472 {
473 enqueueOdomMessage = true;
474 }
475 else
476 {
477 const auto & prevPose = baseTrajectory_.poses.back().pose;
478 const geometry_msgs::msg::Point & prevPoint = prevPose.position;
479 double prevAngle = tf2::getYaw(prevPose.orientation);
480
481 const geometry_msgs::msg::Point & currePoint = base_pose.pose.position;
482 double currentAngle = tf2::getYaw(base_pose.pose.orientation);
483
484 dist = p2pDistance(prevPoint, currePoint);
485 double goalAngleOffset = fabs(angles::shortest_angular_distance(prevAngle, currentAngle));
486
487 // RCLCPP_WARN(getLogger(),"dist %lf vs min %lf", dist, recordPointDistanceThreshold_);
488
489 RCLCPP_WARN_THROTTLE(getLogger(), *getNode()->get_clock(), 2000, "odom received");
490
492 {
493 enqueueOdomMessage = true;
494 }
495 else
496 {
497 // RCLCPP_WARN(getLogger(),"skip odom, dist: %lf", dist);
498 enqueueOdomMessage = false;
499 }
500 }
501
502 if (enqueueOdomMessage)
503 {
504 RCLCPP_WARN_THROTTLE(
505 getLogger(), *getNode()->get_clock(), 2000, "enqueue odom tracker pose. dist %lf vs min %lf",
507 baseTrajectory_.poses.push_back(base_pose);
508 }
509
510 return enqueueOdomMessage;
511}
512
519{
520 if (!getNode()->get_parameter("odom_frame", this->odomFrame_))
521 {
522 }
523
524 if (!getNode()->get_parameter("record_point_distance_threshold", recordPointDistanceThreshold_))
525 {
526 }
527
528 if (!getNode()->get_parameter(
529 "record_angular_distance_threshold", recordAngularDistanceThreshold_))
530 {
531 }
532
533 if (!getNode()->get_parameter("clear_point_distance_threshold", clearPointDistanceThreshold_))
534 {
535 }
536
537 if (!getNode()->get_parameter("clear_angular_distance_threshold", clearAngularDistanceThreshold_))
538 {
539 }
540}
541
547void CpOdomTracker::processOdometryMessage(const nav_msgs::msg::Odometry::SharedPtr odom)
548{
549 RCLCPP_INFO_THROTTLE(
550 getLogger(), *getNode()->get_clock(), 5000,
551 "[odom_tracker] processing odom msg update heartbeat");
552 std::lock_guard<std::mutex> lock(m_mutex_);
553
555
557 {
558 updateRecordPath(*odom);
559 }
561 {
562 updateClearPath(*odom);
563 }
564
565 // RCLCPP_WARN(getLogger(),"odomTracker odometry callback");
566 if (publishMessages)
567 {
568 rtPublishPaths(odom->header.stamp);
569 }
570
571 // RCLCPP_INFO(getLogger(),"odom_tracker m_mutex release");
572}
573} // namespace odom_tracker
574} // namespace cl_nav2z
void popPath(int pathCount=1, bool keepPreviousPath=false)
rclcpp::Subscription< nav_msgs::msg::Odometry >::SharedPtr odomSub_
virtual void processOdometryMessage(const nav_msgs::msg::Odometry::SharedPtr odom)
odom callback: Updates the path - this must be called periodically for each odometry message.
CpOdomTracker(std::string odomtopicName="/odom", std::string odomFrame="odom")
std::optional< geometry_msgs::msg::PoseStamped > currentMotionGoal_
virtual bool updateRecordPath(const nav_msgs::msg::Odometry &odom)
double recordPointDistanceThreshold_
How much distance there is between two points of the path.
nav_msgs::msg::Path baseTrajectory_
Processed path for the mouth of the reel.
virtual void rtPublishPaths(rclcpp::Time timestamp)
void setCurrentPathName(const std::string &currentPathName)
rclcpp::Publisher< nav_msgs::msg::Path >::SharedPtr robotBasePathStackedPub_
void setCurrentMotionGoal(const geometry_msgs::msg::PoseStamped &pose)
virtual bool updateClearPath(const nav_msgs::msg::Odometry &odom)
std::vector< nav_msgs::msg::Path > pathStack_
void setWorkingMode(WorkingMode workingMode)
rclcpp::Publisher< nav_msgs::msg::Path >::SharedPtr robotBasePathPub_
std::optional< geometry_msgs::msg::PoseStamped > getCurrentMotionGoal()
void setStartPoint(const geometry_msgs::msg::PoseStamped &pose)
rclcpp::Logger getLogger() const
rclcpp::Node::SharedPtr getNode()
double p2pDistance(const geometry_msgs::msg::Point &p1, const geometry_msgs::msg::Point &p2)
void parameterDeclareAndtryGetOrSet(rclcpp::Node::SharedPtr &node, std::string param_name, T &value)