ros_filter.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2014, 2015, 2016, Charles River Analytics, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following
13  * disclaimer in the documentation and/or other materials provided
14  * with the distribution.
15  * 3. Neither the name of the copyright holder nor the names of its
16  * contributors may be used to endorse or promote products derived
17  * from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 
35 #include "robot_localization/ekf.h"
36 #include "robot_localization/ukf.h"
37 
39 
40 #include <algorithm>
41 #include <map>
42 #include <string>
43 #include <utility>
44 #include <vector>
45 #include <limits>
46 namespace RobotLocalization
47 {
48  template<typename T>
49  RosFilter<T>::RosFilter(ros::NodeHandle nh, ros::NodeHandle nh_priv, std::vector<double> args) :
50  staticDiagErrorLevel_(diagnostic_msgs::DiagnosticStatus::OK),
51  tfListener_(tfBuffer_),
52  dynamicDiagErrorLevel_(diagnostic_msgs::DiagnosticStatus::OK),
53  filter_(args),
54  frequency_(30.0),
55  historyLength_(0),
56  lastSetPoseTime_(0),
57  latestControl_(),
58  latestControlTime_(0),
59  tfTimeout_(ros::Duration(0)),
60  nh_(nh),
61  nhLocal_(nh_priv),
62  predictToCurrentTime_(false),
63  printDiagnostics_(true),
64  gravitationalAcc_(9.80665),
65  publishTransform_(true),
66  publishAcceleration_(false),
67  twoDMode_(false),
68  useControl_(false),
69  smoothLaggedData_(false),
70  disabledAtStartup_(false),
71  enabled_(false),
72  toggledOn_(true)
73  {
74  stateVariableNames_.push_back("X");
75  stateVariableNames_.push_back("Y");
76  stateVariableNames_.push_back("Z");
77  stateVariableNames_.push_back("ROLL");
78  stateVariableNames_.push_back("PITCH");
79  stateVariableNames_.push_back("YAW");
80  stateVariableNames_.push_back("X_VELOCITY");
81  stateVariableNames_.push_back("Y_VELOCITY");
82  stateVariableNames_.push_back("Z_VELOCITY");
83  stateVariableNames_.push_back("ROLL_VELOCITY");
84  stateVariableNames_.push_back("PITCH_VELOCITY");
85  stateVariableNames_.push_back("YAW_VELOCITY");
86  stateVariableNames_.push_back("X_ACCELERATION");
87  stateVariableNames_.push_back("Y_ACCELERATION");
88  stateVariableNames_.push_back("Z_ACCELERATION");
89 
91  }
92 
93  template<typename T>
95  {
96  topicSubs_.clear();
97  }
98 
99  template<typename T>
101  {
102  ros::Time::init();
103 
104  loadParams();
105 
106  if (printDiagnostics_)
107  {
108  diagnosticUpdater_.add("Filter diagnostic updater", this, &RosFilter<T>::aggregateDiagnostics);
109  }
110 
111  // Set up the frequency diagnostic
114  freqDiag_.reset(new diagnostic_updater::HeaderlessTopicDiagnostic("odometry/filtered",
117  &maxFrequency_,
118  0.1, 10)));
119 
120  // Publisher
121  positionPub_ = nh_.advertise<nav_msgs::Odometry>("odometry/filtered", 20);
122 
123  // Optional acceleration publisher
125  {
126  accelPub_ = nh_.advertise<geometry_msgs::AccelWithCovarianceStamped>("accel/filtered", 20);
127  }
128 
130 
132  }
133 
134  template<typename T>
136  {
137  // Get rid of any initial poses (pretend we've never had a measurement)
138  initialMeasurements_.clear();
139  previousMeasurements_.clear();
141 
143 
144  filterStateHistory_.clear();
145  measurementHistory_.clear();
146 
147  // Also set the last set pose time, so we ignore all messages
148  // that occur before it
150 
151  // clear tf buffer to avoid TF_OLD_DATA errors
152  tfBuffer_.clear();
153 
154  // clear last message timestamp, so older messages will be accepted
155  lastMessageTimes_.clear();
156 
157  // reset filter to uninitialized state
158  filter_.reset();
159 
160  // clear all waiting callbacks
162  }
163 
164  template<typename T>
165  bool RosFilter<T>::toggleFilterProcessingCallback(robot_localization::ToggleFilterProcessing::Request& req,
166  robot_localization::ToggleFilterProcessing::Response& resp)
167  {
168  if (req.on == toggledOn_)
169  {
170  ROS_WARN_STREAM("Service was called to toggle filter processing but state was already as requested.");
171  resp.status = false;
172  }
173  else
174  {
175  ROS_INFO("Toggling filter measurement filtering to %s.", req.on ? "On" : "Off");
176  toggledOn_ = req.on;
177  resp.status = true;
178  }
179  return true;
180  }
181 
182  // @todo: Replace with AccelWithCovarianceStamped
183  template<typename T>
184  void RosFilter<T>::accelerationCallback(const sensor_msgs::Imu::ConstPtr &msg, const CallbackData &callbackData,
185  const std::string &targetFrame)
186  {
187  // If we've just reset the filter, then we want to ignore any messages
188  // that arrive with an older timestamp
189  if (msg->header.stamp <= lastSetPoseTime_)
190  {
191  return;
192  }
193 
194  const std::string &topicName = callbackData.topicName_;
195 
196  RF_DEBUG("------ RosFilter::accelerationCallback (" << topicName << ") ------\n"
197  "Twist message:\n" << *msg);
198 
199  if (lastMessageTimes_.count(topicName) == 0)
200  {
201  lastMessageTimes_.insert(std::pair<std::string, ros::Time>(topicName, msg->header.stamp));
202  }
203 
204  // Make sure this message is newer than the last one
205  if (msg->header.stamp >= lastMessageTimes_[topicName])
206  {
207  RF_DEBUG("Update vector for " << topicName << " is:\n" << topicName);
208 
209  Eigen::VectorXd measurement(STATE_SIZE);
210  Eigen::MatrixXd measurementCovariance(STATE_SIZE, STATE_SIZE);
211 
212  measurement.setZero();
213  measurementCovariance.setZero();
214 
215  // Make sure we're actually updating at least one of these variables
216  std::vector<int> updateVectorCorrected = callbackData.updateVector_;
217 
218  // Prepare the twist data for inclusion in the filter
219  if (prepareAcceleration(msg, topicName, targetFrame, updateVectorCorrected, measurement,
220  measurementCovariance))
221  {
222  // Store the measurement. Add an "acceleration" suffix so we know what kind of measurement
223  // we're dealing with when we debug the core filter logic.
224  enqueueMeasurement(topicName,
225  measurement,
226  measurementCovariance,
227  updateVectorCorrected,
228  callbackData.rejectionThreshold_,
229  msg->header.stamp);
230 
231  RF_DEBUG("Enqueued new measurement for " << topicName << "_acceleration\n");
232  }
233  else
234  {
235  RF_DEBUG("Did *not* enqueue measurement for " << topicName << "_acceleration\n");
236  }
237 
238  lastMessageTimes_[topicName] = msg->header.stamp;
239 
240  RF_DEBUG("Last message time for " << topicName << " is now " <<
241  lastMessageTimes_[topicName] << "\n");
242  }
244  {
245  reset();
246  }
247  else
248  {
249  std::stringstream stream;
250  stream << "The " << topicName << " message has a timestamp before that of the previous message received," <<
251  " this message will be ignored. This may indicate a bad timestamp. (message time: " <<
252  msg->header.stamp.toSec() << ")";
253  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
254  topicName + "_timestamp",
255  stream.str(),
256  false);
257 
258  RF_DEBUG("Message is too old. Last message time for " << topicName <<
259  " is " << lastMessageTimes_[topicName] << ", current message time is " <<
260  msg->header.stamp << ".\n");
261  }
262 
263  RF_DEBUG("\n----- /RosFilter::accelerationCallback (" << topicName << ") ------\n");
264  }
265 
266  template<typename T>
267  void RosFilter<T>::controlCallback(const geometry_msgs::Twist::ConstPtr &msg)
268  {
269  geometry_msgs::TwistStampedPtr twistStampedPtr = geometry_msgs::TwistStampedPtr(new geometry_msgs::TwistStamped());
270  twistStampedPtr->twist = *msg;
271  twistStampedPtr->header.frame_id = baseLinkFrameId_;
272  twistStampedPtr->header.stamp = ros::Time::now();
273  controlCallback(twistStampedPtr);
274  }
275 
276  template<typename T>
277  void RosFilter<T>::controlCallback(const geometry_msgs::TwistStamped::ConstPtr &msg)
278  {
279  if (msg->header.frame_id == baseLinkFrameId_ || msg->header.frame_id == "")
280  {
281  latestControl_(ControlMemberVx) = msg->twist.linear.x;
282  latestControl_(ControlMemberVy) = msg->twist.linear.y;
283  latestControl_(ControlMemberVz) = msg->twist.linear.z;
284  latestControl_(ControlMemberVroll) = msg->twist.angular.x;
285  latestControl_(ControlMemberVpitch) = msg->twist.angular.y;
286  latestControl_(ControlMemberVyaw) = msg->twist.angular.z;
287  latestControlTime_ = msg->header.stamp;
288 
289  // Update the filter with this control term
290  filter_.setControl(latestControl_, msg->header.stamp.toSec());
291  }
292  else
293  {
294  ROS_WARN_STREAM_THROTTLE(5.0, "Commanded velocities must be given in the robot's body frame (" <<
295  baseLinkFrameId_ << "). Message frame was " << msg->header.frame_id);
296  }
297  }
298 
299  template<typename T>
300  void RosFilter<T>::enqueueMeasurement(const std::string &topicName,
301  const Eigen::VectorXd &measurement,
302  const Eigen::MatrixXd &measurementCovariance,
303  const std::vector<int> &updateVector,
304  const double mahalanobisThresh,
305  const ros::Time &time)
306  {
308 
309  meas->topicName_ = topicName;
310  meas->measurement_ = measurement;
311  meas->covariance_ = measurementCovariance;
312  meas->updateVector_ = updateVector;
313  meas->time_ = time.toSec();
314  meas->mahalanobisThresh_ = mahalanobisThresh;
315  meas->latestControl_ = latestControl_;
316  meas->latestControlTime_ = latestControlTime_.toSec();
317  measurementQueue_.push(meas);
318  }
319 
320  template<typename T>
321  void RosFilter<T>::forceTwoD(Eigen::VectorXd &measurement,
322  Eigen::MatrixXd &measurementCovariance,
323  std::vector<int> &updateVector)
324  {
325  measurement(StateMemberZ) = 0.0;
326  measurement(StateMemberRoll) = 0.0;
327  measurement(StateMemberPitch) = 0.0;
328  measurement(StateMemberVz) = 0.0;
329  measurement(StateMemberVroll) = 0.0;
330  measurement(StateMemberVpitch) = 0.0;
331  measurement(StateMemberAz) = 0.0;
332 
333  measurementCovariance(StateMemberZ, StateMemberZ) = 1e-6;
334  measurementCovariance(StateMemberRoll, StateMemberRoll) = 1e-6;
335  measurementCovariance(StateMemberPitch, StateMemberPitch) = 1e-6;
336  measurementCovariance(StateMemberVz, StateMemberVz) = 1e-6;
337  measurementCovariance(StateMemberVroll, StateMemberVroll) = 1e-6;
338  measurementCovariance(StateMemberVpitch, StateMemberVpitch) = 1e-6;
339  measurementCovariance(StateMemberAz, StateMemberAz) = 1e-6;
340 
341  updateVector[StateMemberZ] = 1;
342  updateVector[StateMemberRoll] = 1;
343  updateVector[StateMemberPitch] = 1;
344  updateVector[StateMemberVz] = 1;
345  updateVector[StateMemberVroll] = 1;
346  updateVector[StateMemberVpitch] = 1;
347  updateVector[StateMemberAz] = 1;
348  }
349 
350  template<typename T>
351  bool RosFilter<T>::getFilteredOdometryMessage(nav_msgs::Odometry &message)
352  {
353  // If the filter has received a measurement at some point...
354  if (filter_.getInitializedStatus())
355  {
356  // Grab our current state and covariance estimates
357  const Eigen::VectorXd &state = filter_.getState();
358  const Eigen::MatrixXd &estimateErrorCovariance = filter_.getEstimateErrorCovariance();
359 
360  // Convert from roll, pitch, and yaw back to quaternion for
361  // orientation values
362  tf2::Quaternion quat;
363  quat.setRPY(state(StateMemberRoll), state(StateMemberPitch), state(StateMemberYaw));
364 
365  // Fill out the message
366  message.pose.pose.position.x = state(StateMemberX);
367  message.pose.pose.position.y = state(StateMemberY);
368  message.pose.pose.position.z = state(StateMemberZ);
369  message.pose.pose.orientation.x = quat.x();
370  message.pose.pose.orientation.y = quat.y();
371  message.pose.pose.orientation.z = quat.z();
372  message.pose.pose.orientation.w = quat.w();
373  message.twist.twist.linear.x = state(StateMemberVx);
374  message.twist.twist.linear.y = state(StateMemberVy);
375  message.twist.twist.linear.z = state(StateMemberVz);
376  message.twist.twist.angular.x = state(StateMemberVroll);
377  message.twist.twist.angular.y = state(StateMemberVpitch);
378  message.twist.twist.angular.z = state(StateMemberVyaw);
379 
380  // Our covariance matrix layout doesn't quite match
381  for (size_t i = 0; i < POSE_SIZE; i++)
382  {
383  for (size_t j = 0; j < POSE_SIZE; j++)
384  {
385  message.pose.covariance[POSE_SIZE * i + j] = estimateErrorCovariance(i, j);
386  }
387  }
388 
389  // POSE_SIZE and TWIST_SIZE are currently the same size, but we can spare a few
390  // cycles to be meticulous and not index a twist covariance array on the size of
391  // a pose covariance array
392  for (size_t i = 0; i < TWIST_SIZE; i++)
393  {
394  for (size_t j = 0; j < TWIST_SIZE; j++)
395  {
396  message.twist.covariance[TWIST_SIZE * i + j] =
397  estimateErrorCovariance(i + POSITION_V_OFFSET, j + POSITION_V_OFFSET);
398  }
399  }
400 
401  message.header.stamp = ros::Time(filter_.getLastMeasurementTime());
402  message.header.frame_id = worldFrameId_;
403  message.child_frame_id = baseLinkOutputFrameId_;
404  }
405 
406  return filter_.getInitializedStatus();
407  }
408 
409  template<typename T>
410  bool RosFilter<T>::getFilteredAccelMessage(geometry_msgs::AccelWithCovarianceStamped &message)
411  {
412  // If the filter has received a measurement at some point...
413  if (filter_.getInitializedStatus())
414  {
415  // Grab our current state and covariance estimates
416  const Eigen::VectorXd &state = filter_.getState();
417  const Eigen::MatrixXd &estimateErrorCovariance = filter_.getEstimateErrorCovariance();
418 
420  message.accel.accel.linear.x = state(StateMemberAx);
421  message.accel.accel.linear.y = state(StateMemberAy);
422  message.accel.accel.linear.z = state(StateMemberAz);
423 
424  // Fill the covariance (only the left-upper matrix since we are not estimating
425  // the rotational accelerations arround the axes
426  for (size_t i = 0; i < ACCELERATION_SIZE; i++)
427  {
428  for (size_t j = 0; j < ACCELERATION_SIZE; j++)
429  {
430  // We use the POSE_SIZE since the accel cov matrix of ROS is 6x6
431  message.accel.covariance[POSE_SIZE * i + j] =
432  estimateErrorCovariance(i + POSITION_A_OFFSET, j + POSITION_A_OFFSET);
433  }
434  }
435 
436  // Fill header information
437  message.header.stamp = ros::Time(filter_.getLastMeasurementTime());
438  message.header.frame_id = baseLinkOutputFrameId_;
439  }
440 
441  return filter_.getInitializedStatus();
442  }
443 
444  template<typename T>
445  void RosFilter<T>::imuCallback(const sensor_msgs::Imu::ConstPtr &msg,
446  const std::string &topicName,
447  const CallbackData &poseCallbackData,
448  const CallbackData &twistCallbackData,
449  const CallbackData &accelCallbackData)
450  {
451  RF_DEBUG("------ RosFilter::imuCallback (" << topicName << ") ------\n" << "IMU message:\n" << *msg);
452 
453  // If we've just reset the filter, then we want to ignore any messages
454  // that arrive with an older timestamp
455  if (msg->header.stamp <= lastSetPoseTime_)
456  {
457  std::stringstream stream;
458  stream << "The " << topicName << " message has a timestamp equal to or before the last filter reset, " <<
459  "this message will be ignored. This may indicate an empty or bad timestamp. (message time: " <<
460  msg->header.stamp.toSec() << ")";
461  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
462  topicName + "_timestamp",
463  stream.str(),
464  false);
465  RF_DEBUG("Received message that preceded the most recent pose reset. Ignoring...");
466 
467  return;
468  }
469 
470  // As with the odometry message, we can separate out the pose- and twist-related variables
471  // in the IMU message and pass them to the pose and twist callbacks (filters)
472  if (poseCallbackData.updateSum_ > 0)
473  {
474  // Per the IMU message specification, if the IMU does not provide orientation,
475  // then its first covariance value should be set to -1, and we should ignore
476  // that portion of the message. robot_localization allows users to explicitly
477  // ignore data using its parameters, but we should also be compliant with
478  // message specs.
479  if (::fabs(msg->orientation_covariance[0] + 1) < 1e-9)
480  {
481  RF_DEBUG("Received IMU message with -1 as its first covariance value for orientation. "
482  "Ignoring orientation...");
483  }
484  else
485  {
486  // Extract the pose (orientation) data, pass it to its filter
487  geometry_msgs::PoseWithCovarianceStamped *posPtr = new geometry_msgs::PoseWithCovarianceStamped();
488  posPtr->header = msg->header;
489  posPtr->pose.pose.orientation = msg->orientation;
490 
491  // Copy the covariance for roll, pitch, and yaw
492  for (size_t i = 0; i < ORIENTATION_SIZE; i++)
493  {
494  for (size_t j = 0; j < ORIENTATION_SIZE; j++)
495  {
496  posPtr->pose.covariance[POSE_SIZE * (i + ORIENTATION_SIZE) + (j + ORIENTATION_SIZE)] =
497  msg->orientation_covariance[ORIENTATION_SIZE * i + j];
498  }
499  }
500 
501  // IMU data gets handled a bit differently, since the message is ambiguous and has only a single frame_id,
502  // even though the data in it is reported in two different frames. As we assume users will specify a base_link
503  // to imu transform, we make the target frame baseLinkFrameId_ and tell the poseCallback that it is working
504  // with IMU data. This will cause it to apply different logic to the data.
505  geometry_msgs::PoseWithCovarianceStampedConstPtr pptr(posPtr);
506  poseCallback(pptr, poseCallbackData, baseLinkFrameId_, true);
507  }
508  }
509 
510  if (twistCallbackData.updateSum_ > 0)
511  {
512  // Ignore rotational velocity if the first covariance value is -1
513  if (::fabs(msg->angular_velocity_covariance[0] + 1) < 1e-9)
514  {
515  RF_DEBUG("Received IMU message with -1 as its first covariance value for angular "
516  "velocity. Ignoring angular velocity...");
517  }
518  else
519  {
520  // Repeat for velocity
521  geometry_msgs::TwistWithCovarianceStamped *twistPtr = new geometry_msgs::TwistWithCovarianceStamped();
522  twistPtr->header = msg->header;
523  twistPtr->twist.twist.angular = msg->angular_velocity;
524 
525  // Copy the covariance
526  for (size_t i = 0; i < ORIENTATION_SIZE; i++)
527  {
528  for (size_t j = 0; j < ORIENTATION_SIZE; j++)
529  {
530  twistPtr->twist.covariance[TWIST_SIZE * (i + ORIENTATION_SIZE) + (j + ORIENTATION_SIZE)] =
531  msg->angular_velocity_covariance[ORIENTATION_SIZE * i + j];
532  }
533  }
534 
535  geometry_msgs::TwistWithCovarianceStampedConstPtr tptr(twistPtr);
536  twistCallback(tptr, twistCallbackData, baseLinkFrameId_);
537  }
538  }
539 
540  if (accelCallbackData.updateSum_ > 0)
541  {
542  // Ignore linear acceleration if the first covariance value is -1
543  if (::fabs(msg->linear_acceleration_covariance[0] + 1) < 1e-9)
544  {
545  RF_DEBUG("Received IMU message with -1 as its first covariance value for linear "
546  "acceleration. Ignoring linear acceleration...");
547  }
548  else
549  {
550  // Pass the message on
551  accelerationCallback(msg, accelCallbackData, baseLinkFrameId_);
552  }
553  }
554 
555  RF_DEBUG("\n----- /RosFilter::imuCallback (" << topicName << ") ------\n");
556  }
557 
558  template<typename T>
560  {
561  const double currentTimeSec = currentTime.toSec();
562 
563  RF_DEBUG("------ RosFilter::integrateMeasurements ------\n\n"
564  "Integration time is " << std::setprecision(20) << currentTimeSec << "\n"
565  << measurementQueue_.size() << " measurements in queue.\n");
566 
567  bool predictToCurrentTime = predictToCurrentTime_;
568 
569  // If we have any measurements in the queue, process them
570  if (!measurementQueue_.empty())
571  {
572  // Check if the first measurement we're going to process is older than the filter's last measurement.
573  // This means we have received an out-of-sequence message (one with an old timestamp), and we need to
574  // revert both the filter state and measurement queue to the first state that preceded the time stamp
575  // of our first measurement.
576  const MeasurementPtr& firstMeasurement = measurementQueue_.top();
577  int restoredMeasurementCount = 0;
578  if (smoothLaggedData_ && firstMeasurement->time_ < filter_.getLastMeasurementTime())
579  {
580  RF_DEBUG("Received a measurement that was " << filter_.getLastMeasurementTime() - firstMeasurement->time_ <<
581  " seconds in the past. Reverting filter state and measurement queue...");
582 
583  int originalCount = static_cast<int>(measurementQueue_.size());
584  const double firstMeasurementTime = firstMeasurement->time_;
585  const std::string firstMeasurementTopic = firstMeasurement->topicName_;
586  if (!revertTo(firstMeasurementTime - 1e-9)) // revertTo may invalidate firstMeasurement
587  {
588  RF_DEBUG("ERROR: history interval is too small to revert to time " << firstMeasurementTime << "\n");
589  ROS_WARN_STREAM_DELAYED_THROTTLE(historyLength_, "Received old measurement for topic " <<
590  firstMeasurementTopic << ", but history interval is insufficiently sized. Measurement time is " <<
591  std::setprecision(20) << firstMeasurementTime << ", current time is " << currentTime.toSec() <<
592  ", history length is " << historyLength_ << ".");
593  restoredMeasurementCount = 0;
594  }
595 
596  restoredMeasurementCount = static_cast<int>(measurementQueue_.size()) - originalCount;
597  }
598 
599  while (!measurementQueue_.empty() && ros::ok())
600  {
601  MeasurementPtr measurement = measurementQueue_.top();
602 
603  // If we've reached a measurement that has a time later than now, it should wait until a future iteration.
604  // Since measurements are stored in a priority queue, all remaining measurements will be in the future.
605  if (measurement->time_ > currentTime.toSec())
606  {
607  break;
608  }
609 
610  measurementQueue_.pop();
611 
612  // When we receive control messages, we call this directly in the control callback. However, we also associate
613  // a control with each sensor message so that we can support lagged smoothing. As we cannot guarantee that the
614  // new control callback will fire before a new measurement, we should only perform this operation if we are
615  // processing messages from the history. Otherwise, we may get a new measurement, store the "old" latest
616  // control, then receive a control, call setControl, and then overwrite that value with this one (i.e., with
617  // the "old" control we associated with the measurement).
618  if (useControl_ && restoredMeasurementCount > 0)
619  {
620  filter_.setControl(measurement->latestControl_, measurement->latestControlTime_);
621  restoredMeasurementCount--;
622  }
623 
624  // This will call predict and, if necessary, correct
625  filter_.processMeasurement(*(measurement.get()));
626 
627  // Store old states and measurements if we're smoothing
628  if (smoothLaggedData_)
629  {
630  // Invariant still holds: measurementHistoryDeque_.back().time_ < measurementQueue_.top().time_
631  measurementHistory_.push_back(measurement);
632 
633  // We should only save the filter state once per unique timstamp
634  if (measurementQueue_.empty() ||
635  ::fabs(measurementQueue_.top()->time_ - filter_.getLastMeasurementTime()) > 1e-9)
636  {
638  }
639  }
640  }
641  }
642  else if (filter_.getInitializedStatus())
643  {
644  // In the event that we don't get any measurements for a long time,
645  // we still need to continue to estimate our state. Therefore, we
646  // should project the state forward here.
647  double lastUpdateDelta = currentTimeSec - filter_.getLastMeasurementTime();
648 
649  // If we get a large delta, then continuously predict until
650  if (lastUpdateDelta >= filter_.getSensorTimeout())
651  {
652  predictToCurrentTime = true;
653 
654  RF_DEBUG("Sensor timeout! Last measurement time was " << filter_.getLastMeasurementTime() <<
655  ", current time is " << currentTimeSec <<
656  ", delta is " << lastUpdateDelta << "\n");
657  }
658  }
659  else
660  {
661  RF_DEBUG("Filter not yet initialized.\n");
662  }
663 
664  if (filter_.getInitializedStatus() && predictToCurrentTime)
665  {
666  double lastUpdateDelta = currentTimeSec - filter_.getLastMeasurementTime();
667 
668  filter_.validateDelta(lastUpdateDelta);
669  filter_.predict(currentTimeSec, lastUpdateDelta);
670 
671  // Update the last measurement time and last update time
672  filter_.setLastMeasurementTime(filter_.getLastMeasurementTime() + lastUpdateDelta);
673  }
674 
675  RF_DEBUG("\n----- /RosFilter::integrateMeasurements ------\n");
676  }
677 
678  template<typename T>
680  {
681  /* For diagnostic purposes, collect information about how many different
682  * sources are measuring each absolute pose variable and do not have
683  * differential integration enabled.
684  */
685  std::map<StateMembers, int> absPoseVarCounts;
686  absPoseVarCounts[StateMemberX] = 0;
687  absPoseVarCounts[StateMemberY] = 0;
688  absPoseVarCounts[StateMemberZ] = 0;
689  absPoseVarCounts[StateMemberRoll] = 0;
690  absPoseVarCounts[StateMemberPitch] = 0;
691  absPoseVarCounts[StateMemberYaw] = 0;
692 
693  // Same for twist variables
694  std::map<StateMembers, int> twistVarCounts;
695  twistVarCounts[StateMemberVx] = 0;
696  twistVarCounts[StateMemberVy] = 0;
697  twistVarCounts[StateMemberVz] = 0;
698  twistVarCounts[StateMemberVroll] = 0;
699  twistVarCounts[StateMemberVpitch] = 0;
700  twistVarCounts[StateMemberVyaw] = 0;
701 
702  // Determine if we'll be printing diagnostic information
703  nhLocal_.param("print_diagnostics", printDiagnostics_, true);
704 
705  // Check for custom gravitational acceleration value
706  nhLocal_.param("gravitational_acceleration", gravitationalAcc_, 9.80665);
707 
708  // Grab the debug param. If true, the node will produce a LOT of output.
709  bool debug;
710  nhLocal_.param("debug", debug, false);
711 
712  if (debug)
713  {
714  std::string debugOutFile;
715 
716  try
717  {
718  nhLocal_.param("debug_out_file", debugOutFile, std::string("robot_localization_debug.txt"));
719  debugStream_.open(debugOutFile.c_str());
720 
721  // Make sure we succeeded
722  if (debugStream_.is_open())
723  {
724  filter_.setDebug(debug, &debugStream_);
725  }
726  else
727  {
728  ROS_WARN_STREAM("RosFilter::loadParams() - unable to create debug output file " << debugOutFile);
729  }
730  }
731  catch(const std::exception &e)
732  {
733  ROS_WARN_STREAM("RosFilter::loadParams() - unable to create debug output file" << debugOutFile
734  << ". Error was " << e.what() << "\n");
735  }
736  }
737 
738  // These params specify the name of the robot's body frame (typically
739  // base_link) and odometry frame (typically odom)
740  nhLocal_.param("map_frame", mapFrameId_, std::string("map"));
741  nhLocal_.param("odom_frame", odomFrameId_, std::string("odom"));
742  nhLocal_.param("base_link_frame", baseLinkFrameId_, std::string("base_link"));
743  nhLocal_.param("base_link_frame_output", baseLinkOutputFrameId_, baseLinkFrameId_);
744 
745  /*
746  * These parameters are designed to enforce compliance with REP-105:
747  * http://www.ros.org/reps/rep-0105.html
748  * When fusing absolute position data from sensors such as GPS, the state
749  * estimate can undergo discrete jumps. According to REP-105, we want three
750  * coordinate frames: map, odom, and base_link. The map frame can have
751  * discontinuities, but is the frame with the most accurate position estimate
752  * for the robot and should not suffer from drift. The odom frame drifts over
753  * time, but is guaranteed to be continuous and is accurate enough for local
754  * planning and navigation. The base_link frame is affixed to the robot. The
755  * intention is that some odometry source broadcasts the odom->base_link
756  * transform. The localization software should broadcast map->base_link.
757  * However, tf does not allow multiple parents for a coordinate frame, so
758  * we must *compute* map->base_link, but then use the existing odom->base_link
759  * transform to compute *and broadcast* map->odom.
760  *
761  * The state estimation nodes in robot_localization therefore have two "modes."
762  * If your world_frame parameter value matches the odom_frame parameter value,
763  * then robot_localization will assume someone else is broadcasting a transform
764  * from odom_frame->base_link_frame, and it will compute the
765  * map_frame->odom_frame transform. Otherwise, it will simply compute the
766  * odom_frame->base_link_frame transform.
767  *
768  * The default is the latter behavior (broadcast of odom->base_link).
769  */
770  nhLocal_.param("world_frame", worldFrameId_, odomFrameId_);
771 
777  "Invalid frame configuration! The values for map_frame, odom_frame, "
778  "and base_link_frame must be unique. If using a base_link_frame_output values, it "
779  "must not match the map_frame or odom_frame.");
780 
781  // Try to resolve tf_prefix
782  std::string tfPrefix = "";
783  std::string tfPrefixPath = "";
784  if (nhLocal_.searchParam("tf_prefix", tfPrefixPath))
785  {
786  nhLocal_.getParam(tfPrefixPath, tfPrefix);
787  }
788 
789  // Append the tf prefix in a tf2-friendly manner
794 
795  // Whether we're publshing the world_frame->base_link_frame transform
796  nhLocal_.param("publish_tf", publishTransform_, true);
797 
798  // Whether we're publishing the acceleration state transform
799  nhLocal_.param("publish_acceleration", publishAcceleration_, false);
800 
801  // Transform future dating
802  double offsetTmp;
803  nhLocal_.param("transform_time_offset", offsetTmp, 0.0);
804  tfTimeOffset_.fromSec(offsetTmp);
805 
806  // Transform timeout
807  double timeoutTmp;
808  nhLocal_.param("transform_timeout", timeoutTmp, 0.0);
809  tfTimeout_.fromSec(timeoutTmp);
810 
811  // Update frequency and sensor timeout
812  double sensorTimeout;
813  nhLocal_.param("frequency", frequency_, 30.0);
814  nhLocal_.param("sensor_timeout", sensorTimeout, 1.0 / frequency_);
815  filter_.setSensorTimeout(sensorTimeout);
816 
817  // Determine if we're in 2D mode
818  nhLocal_.param("two_d_mode", twoDMode_, false);
819 
820  // Smoothing window size
821  nhLocal_.param("smooth_lagged_data", smoothLaggedData_, false);
822  nhLocal_.param("history_length", historyLength_, 0.0);
823 
824  // Wether we reset filter on jump back in time
825  nhLocal_.param("reset_on_time_jump", resetOnTimeJump_, false);
826 
827  if (!smoothLaggedData_ && ::fabs(historyLength_) > 1e-9)
828  {
829  ROS_WARN_STREAM("Filter history interval of " << historyLength_ <<
830  " specified, but smooth_lagged_data is set to false. Lagged data will not be smoothed.");
831  }
832 
833  if (smoothLaggedData_ && historyLength_ < -1e9)
834  {
835  ROS_WARN_STREAM("Negative history interval of " << historyLength_ <<
836  " specified. Absolute value will be assumed.");
837  }
838 
839  historyLength_ = ::fabs(historyLength_);
840 
841  nhLocal_.param("predict_to_current_time", predictToCurrentTime_, false);
842 
843  // Determine if we're using a control term
844  bool stampedControl = false;
845  double controlTimeout = sensorTimeout;
846  std::vector<int> controlUpdateVector(TWIST_SIZE, 0);
847  std::vector<double> accelerationLimits(TWIST_SIZE, 1.0);
848  std::vector<double> accelerationGains(TWIST_SIZE, 1.0);
849  std::vector<double> decelerationLimits(TWIST_SIZE, 1.0);
850  std::vector<double> decelerationGains(TWIST_SIZE, 1.0);
851 
852  nhLocal_.param("use_control", useControl_, false);
853  nhLocal_.param("stamped_control", stampedControl, false);
854  nhLocal_.param("control_timeout", controlTimeout, sensorTimeout);
855 
856  if (useControl_)
857  {
858  if (nhLocal_.getParam("control_config", controlUpdateVector))
859  {
860  if (controlUpdateVector.size() != TWIST_SIZE)
861  {
862  ROS_ERROR_STREAM("Control configuration must be of size " << TWIST_SIZE << ". Provided config was of "
863  "size " << controlUpdateVector.size() << ". No control term will be used.");
864  useControl_ = false;
865  }
866  }
867  else
868  {
869  ROS_ERROR_STREAM("use_control is set to true, but control_config is missing. No control term will be used.");
870  useControl_ = false;
871  }
872 
873  if (nhLocal_.getParam("acceleration_limits", accelerationLimits))
874  {
875  if (accelerationLimits.size() != TWIST_SIZE)
876  {
877  ROS_ERROR_STREAM("Acceleration configuration must be of size " << TWIST_SIZE << ". Provided config was of "
878  "size " << accelerationLimits.size() << ". No control term will be used.");
879  useControl_ = false;
880  }
881  }
882  else
883  {
884  ROS_WARN_STREAM("use_control is set to true, but acceleration_limits is missing. Will use default values.");
885  }
886 
887  if (nhLocal_.getParam("acceleration_gains", accelerationGains))
888  {
889  const int size = accelerationGains.size();
890  if (size != TWIST_SIZE)
891  {
892  ROS_ERROR_STREAM("Acceleration gain configuration must be of size " << TWIST_SIZE <<
893  ". Provided config was of size " << size << ". All gains will be assumed to be 1.");
894  std::fill_n(accelerationGains.begin(), std::min(size, TWIST_SIZE), 1.0);
895  accelerationGains.resize(TWIST_SIZE, 1.0);
896  }
897  }
898 
899  if (nhLocal_.getParam("deceleration_limits", decelerationLimits))
900  {
901  if (decelerationLimits.size() != TWIST_SIZE)
902  {
903  ROS_ERROR_STREAM("Deceleration configuration must be of size " << TWIST_SIZE <<
904  ". Provided config was of size " << decelerationLimits.size() << ". No control term will be used.");
905  useControl_ = false;
906  }
907  }
908  else
909  {
910  ROS_INFO_STREAM("use_control is set to true, but no deceleration_limits specified. Will use acceleration "
911  "limits.");
912  decelerationLimits = accelerationLimits;
913  }
914 
915  if (nhLocal_.getParam("deceleration_gains", decelerationGains))
916  {
917  const int size = decelerationGains.size();
918  if (size != TWIST_SIZE)
919  {
920  ROS_ERROR_STREAM("Deceleration gain configuration must be of size " << TWIST_SIZE <<
921  ". Provided config was of size " << size << ". All gains will be assumed to be 1.");
922  std::fill_n(decelerationGains.begin(), std::min(size, TWIST_SIZE), 1.0);
923  decelerationGains.resize(TWIST_SIZE, 1.0);
924  }
925  }
926  else
927  {
928  ROS_INFO_STREAM("use_control is set to true, but no deceleration_gains specified. Will use acceleration "
929  "gains.");
930  decelerationGains = accelerationGains;
931  }
932  }
933 
934  bool dynamicProcessNoiseCovariance = false;
935  nhLocal_.param("dynamic_process_noise_covariance", dynamicProcessNoiseCovariance, false);
936  filter_.setUseDynamicProcessNoiseCovariance(dynamicProcessNoiseCovariance);
937 
938  std::vector<double> initialState(STATE_SIZE, 0.0);
939  if (nhLocal_.getParam("initial_state", initialState))
940  {
941  if (initialState.size() != STATE_SIZE)
942  {
943  ROS_ERROR_STREAM("Initial state must be of size " << STATE_SIZE << ". Provided config was of size " <<
944  initialState.size() << ". The initial state will be ignored.");
945  }
946  else
947  {
948  Eigen::Map<Eigen::VectorXd> eigenState(initialState.data(), initialState.size());
949  filter_.setState(eigenState);
950  }
951  }
952 
953  // Check if the filter should start or not
954  nhLocal_.param("disabled_at_startup", disabledAtStartup_, false);
956 
957 
958  // Debugging writes to file
959  RF_DEBUG("tf_prefix is " << tfPrefix <<
960  "\nmap_frame is " << mapFrameId_ <<
961  "\nodom_frame is " << odomFrameId_ <<
962  "\nbase_link_frame is " << baseLinkFrameId_ <<
963  "\base_link_frame_output is " << baseLinkOutputFrameId_ <<
964  "\nworld_frame is " << worldFrameId_ <<
965  "\ntransform_time_offset is " << tfTimeOffset_.toSec() <<
966  "\ntransform_timeout is " << tfTimeout_.toSec() <<
967  "\nfrequency is " << frequency_ <<
968  "\nsensor_timeout is " << filter_.getSensorTimeout() <<
969  "\ntwo_d_mode is " << (twoDMode_ ? "true" : "false") <<
970  "\nsmooth_lagged_data is " << (smoothLaggedData_ ? "true" : "false") <<
971  "\nhistory_length is " << historyLength_ <<
972  "\nuse_control is " << (useControl_ ? "true" : "false") <<
973  "\nstamped_control is " << (stampedControl ? "true" : "false") <<
974  "\ncontrol_config is " << controlUpdateVector <<
975  "\ncontrol_timeout is " << controlTimeout <<
976  "\nacceleration_limits are " << accelerationLimits <<
977  "\nacceleration_gains are " << accelerationGains <<
978  "\ndeceleration_limits are " << decelerationLimits <<
979  "\ndeceleration_gains are " << decelerationGains <<
980  "\ninitial state is " << filter_.getState() <<
981  "\ndynamic_process_noise_covariance is " << (dynamicProcessNoiseCovariance ? "true" : "false") <<
982  "\nprint_diagnostics is " << (printDiagnostics_ ? "true" : "false") << "\n");
983 
984  // Create a subscriber for manually setting/resetting pose
985  setPoseSub_ = nh_.subscribe("set_pose",
986  1,
988  this, ros::TransportHints().tcpNoDelay(false));
989 
990  // Create a service for manually setting/resetting pose
992 
993  // Create a service for manually enabling the filter
995 
996  // Create a service for toggling processing new measurements while still publishing
999 
1000  // Init the last measurement time so we don't get a huge initial delta
1001  filter_.setLastMeasurementTime(ros::Time::now().toSec());
1002 
1003  // Now pull in each topic to which we want to subscribe.
1004  // Start with odom.
1005  size_t topicInd = 0;
1006  bool moreParams = false;
1007  do
1008  {
1009  // Build the string in the form of "odomX", where X is the odom topic number,
1010  // then check if we have any parameters with that value. Users need to make
1011  // sure they don't have gaps in their configs (e.g., odom0 and then odom2)
1012  std::stringstream ss;
1013  ss << "odom" << topicInd++;
1014  std::string odomTopicName = ss.str();
1015  moreParams = nhLocal_.hasParam(odomTopicName);
1016 
1017  if (moreParams)
1018  {
1019  // Determine if we want to integrate this sensor differentially
1020  bool differential;
1021  nhLocal_.param(odomTopicName + std::string("_differential"), differential, false);
1022 
1023  // Determine if we want to integrate this sensor relatively
1024  bool relative;
1025  nhLocal_.param(odomTopicName + std::string("_relative"), relative, false);
1026 
1027  if (relative && differential)
1028  {
1029  ROS_WARN_STREAM("Both " << odomTopicName << "_differential" << " and " << odomTopicName <<
1030  "_relative were set to true. Using differential mode.");
1031 
1032  relative = false;
1033  }
1034 
1035  std::string odomTopic;
1036  nhLocal_.getParam(odomTopicName, odomTopic);
1037 
1038  // Check for pose rejection threshold
1039  double poseMahalanobisThresh;
1040  nhLocal_.param(odomTopicName + std::string("_pose_rejection_threshold"),
1041  poseMahalanobisThresh,
1042  std::numeric_limits<double>::max());
1043 
1044  // Check for twist rejection threshold
1045  double twistMahalanobisThresh;
1046  nhLocal_.param(odomTopicName + std::string("_twist_rejection_threshold"),
1047  twistMahalanobisThresh,
1048  std::numeric_limits<double>::max());
1049 
1050  // Now pull in its boolean update vector configuration. Create separate vectors for pose
1051  // and twist data, and then zero out the opposite values in each vector (no pose data in
1052  // the twist update vector and vice-versa).
1053  std::vector<int> updateVec = loadUpdateConfig(odomTopicName);
1054  std::vector<int> poseUpdateVec = updateVec;
1055  std::fill(poseUpdateVec.begin() + POSITION_V_OFFSET, poseUpdateVec.begin() + POSITION_V_OFFSET + TWIST_SIZE, 0);
1056  std::vector<int> twistUpdateVec = updateVec;
1057  std::fill(twistUpdateVec.begin() + POSITION_OFFSET, twistUpdateVec.begin() + POSITION_OFFSET + POSE_SIZE, 0);
1058 
1059  int poseUpdateSum = std::accumulate(poseUpdateVec.begin(), poseUpdateVec.end(), 0);
1060  int twistUpdateSum = std::accumulate(twistUpdateVec.begin(), twistUpdateVec.end(), 0);
1061  int odomQueueSize = 1;
1062  nhLocal_.param(odomTopicName + "_queue_size", odomQueueSize, 1);
1063 
1064  const CallbackData poseCallbackData(odomTopicName + "_pose", poseUpdateVec, poseUpdateSum, differential,
1065  relative, poseMahalanobisThresh);
1066  const CallbackData twistCallbackData(odomTopicName + "_twist", twistUpdateVec, twistUpdateSum, false, false,
1067  twistMahalanobisThresh);
1068 
1069  bool nodelayOdom = false;
1070  nhLocal_.param(odomTopicName + "_nodelay", nodelayOdom, false);
1071 
1072  // Store the odometry topic subscribers so they don't go out of scope.
1073  if (poseUpdateSum + twistUpdateSum > 0)
1074  {
1075  topicSubs_.push_back(
1076  nh_.subscribe<nav_msgs::Odometry>(odomTopic, odomQueueSize,
1077  boost::bind(&RosFilter::odometryCallback, this, _1, odomTopicName, poseCallbackData, twistCallbackData),
1078  ros::VoidPtr(), ros::TransportHints().tcpNoDelay(nodelayOdom)));
1079  }
1080  else
1081  {
1082  std::stringstream stream;
1083  stream << odomTopic << " is listed as an input topic, but all update variables are false";
1084 
1085  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
1086  odomTopic + "_configuration",
1087  stream.str(),
1088  true);
1089  }
1090 
1091  if (poseUpdateSum > 0)
1092  {
1093  if (differential)
1094  {
1095  twistVarCounts[StateMemberVx] += poseUpdateVec[StateMemberX];
1096  twistVarCounts[StateMemberVy] += poseUpdateVec[StateMemberY];
1097  twistVarCounts[StateMemberVz] += poseUpdateVec[StateMemberZ];
1098  twistVarCounts[StateMemberVroll] += poseUpdateVec[StateMemberRoll];
1099  twistVarCounts[StateMemberVpitch] += poseUpdateVec[StateMemberPitch];
1100  twistVarCounts[StateMemberVyaw] += poseUpdateVec[StateMemberYaw];
1101  }
1102  else
1103  {
1104  absPoseVarCounts[StateMemberX] += poseUpdateVec[StateMemberX];
1105  absPoseVarCounts[StateMemberY] += poseUpdateVec[StateMemberY];
1106  absPoseVarCounts[StateMemberZ] += poseUpdateVec[StateMemberZ];
1107  absPoseVarCounts[StateMemberRoll] += poseUpdateVec[StateMemberRoll];
1108  absPoseVarCounts[StateMemberPitch] += poseUpdateVec[StateMemberPitch];
1109  absPoseVarCounts[StateMemberYaw] += poseUpdateVec[StateMemberYaw];
1110  }
1111  }
1112 
1113  if (twistUpdateSum > 0)
1114  {
1115  twistVarCounts[StateMemberVx] += twistUpdateVec[StateMemberVx];
1116  twistVarCounts[StateMemberVy] += twistUpdateVec[StateMemberVx];
1117  twistVarCounts[StateMemberVz] += twistUpdateVec[StateMemberVz];
1118  twistVarCounts[StateMemberVroll] += twistUpdateVec[StateMemberVroll];
1119  twistVarCounts[StateMemberVpitch] += twistUpdateVec[StateMemberVpitch];
1120  twistVarCounts[StateMemberVyaw] += twistUpdateVec[StateMemberVyaw];
1121  }
1122 
1123  RF_DEBUG("Subscribed to " << odomTopic << " (" << odomTopicName << ")\n\t" <<
1124  odomTopicName << "_differential is " << (differential ? "true" : "false") << "\n\t" <<
1125  odomTopicName << "_pose_rejection_threshold is " << poseMahalanobisThresh << "\n\t" <<
1126  odomTopicName << "_twist_rejection_threshold is " << twistMahalanobisThresh << "\n\t" <<
1127  odomTopicName << "_queue_size is " << odomQueueSize << "\n\t" <<
1128  odomTopicName << " pose update vector is " << poseUpdateVec << "\t"<<
1129  odomTopicName << " twist update vector is " << twistUpdateVec);
1130  }
1131  }
1132  while (moreParams);
1133 
1134  // Repeat for pose
1135  topicInd = 0;
1136  moreParams = false;
1137  do
1138  {
1139  std::stringstream ss;
1140  ss << "pose" << topicInd++;
1141  std::string poseTopicName = ss.str();
1142  moreParams = nhLocal_.hasParam(poseTopicName);
1143 
1144  if (moreParams)
1145  {
1146  bool differential;
1147  nhLocal_.param(poseTopicName + std::string("_differential"), differential, false);
1148 
1149  // Determine if we want to integrate this sensor relatively
1150  bool relative;
1151  nhLocal_.param(poseTopicName + std::string("_relative"), relative, false);
1152 
1153  if (relative && differential)
1154  {
1155  ROS_WARN_STREAM("Both " << poseTopicName << "_differential" << " and " << poseTopicName <<
1156  "_relative were set to true. Using differential mode.");
1157 
1158  relative = false;
1159  }
1160 
1161  std::string poseTopic;
1162  nhLocal_.getParam(poseTopicName, poseTopic);
1163 
1164  // Check for pose rejection threshold
1165  double poseMahalanobisThresh;
1166  nhLocal_.param(poseTopicName + std::string("_rejection_threshold"),
1167  poseMahalanobisThresh,
1168  std::numeric_limits<double>::max());
1169 
1170  int poseQueueSize = 1;
1171  nhLocal_.param(poseTopicName + "_queue_size", poseQueueSize, 1);
1172 
1173  bool nodelayPose = false;
1174  nhLocal_.param(poseTopicName + "_nodelay", nodelayPose, false);
1175 
1176  // Pull in the sensor's config, zero out values that are invalid for the pose type
1177  std::vector<int> poseUpdateVec = loadUpdateConfig(poseTopicName);
1178  std::fill(poseUpdateVec.begin() + POSITION_V_OFFSET,
1179  poseUpdateVec.begin() + POSITION_V_OFFSET + TWIST_SIZE,
1180  0);
1181  std::fill(poseUpdateVec.begin() + POSITION_A_OFFSET,
1182  poseUpdateVec.begin() + POSITION_A_OFFSET + ACCELERATION_SIZE,
1183  0);
1184 
1185  int poseUpdateSum = std::accumulate(poseUpdateVec.begin(), poseUpdateVec.end(), 0);
1186 
1187  if (poseUpdateSum > 0)
1188  {
1189  const CallbackData callbackData(poseTopicName, poseUpdateVec, poseUpdateSum, differential, relative,
1190  poseMahalanobisThresh);
1191 
1192  topicSubs_.push_back(
1193  nh_.subscribe<geometry_msgs::PoseWithCovarianceStamped>(poseTopic, poseQueueSize,
1194  boost::bind(&RosFilter::poseCallback, this, _1, callbackData, worldFrameId_, false),
1195  ros::VoidPtr(), ros::TransportHints().tcpNoDelay(nodelayPose)));
1196 
1197  if (differential)
1198  {
1199  twistVarCounts[StateMemberVx] += poseUpdateVec[StateMemberX];
1200  twistVarCounts[StateMemberVy] += poseUpdateVec[StateMemberY];
1201  twistVarCounts[StateMemberVz] += poseUpdateVec[StateMemberZ];
1202  twistVarCounts[StateMemberVroll] += poseUpdateVec[StateMemberRoll];
1203  twistVarCounts[StateMemberVpitch] += poseUpdateVec[StateMemberPitch];
1204  twistVarCounts[StateMemberVyaw] += poseUpdateVec[StateMemberYaw];
1205  }
1206  else
1207  {
1208  absPoseVarCounts[StateMemberX] += poseUpdateVec[StateMemberX];
1209  absPoseVarCounts[StateMemberY] += poseUpdateVec[StateMemberY];
1210  absPoseVarCounts[StateMemberZ] += poseUpdateVec[StateMemberZ];
1211  absPoseVarCounts[StateMemberRoll] += poseUpdateVec[StateMemberRoll];
1212  absPoseVarCounts[StateMemberPitch] += poseUpdateVec[StateMemberPitch];
1213  absPoseVarCounts[StateMemberYaw] += poseUpdateVec[StateMemberYaw];
1214  }
1215  }
1216  else
1217  {
1218  ROS_WARN_STREAM("Warning: " << poseTopic << " is listed as an input topic, "
1219  "but all pose update variables are false");
1220  }
1221 
1222  RF_DEBUG("Subscribed to " << poseTopic << " (" << poseTopicName << ")\n\t" <<
1223  poseTopicName << "_differential is " << (differential ? "true" : "false") << "\n\t" <<
1224  poseTopicName << "_rejection_threshold is " << poseMahalanobisThresh << "\n\t" <<
1225  poseTopicName << "_queue_size is " << poseQueueSize << "\n\t" <<
1226  poseTopicName << " update vector is " << poseUpdateVec);
1227  }
1228  }
1229  while (moreParams);
1230 
1231  // Repeat for twist
1232  topicInd = 0;
1233  moreParams = false;
1234  do
1235  {
1236  std::stringstream ss;
1237  ss << "twist" << topicInd++;
1238  std::string twistTopicName = ss.str();
1239  moreParams = nhLocal_.hasParam(twistTopicName);
1240 
1241  if (moreParams)
1242  {
1243  std::string twistTopic;
1244  nhLocal_.getParam(twistTopicName, twistTopic);
1245 
1246  // Check for twist rejection threshold
1247  double twistMahalanobisThresh;
1248  nhLocal_.param(twistTopicName + std::string("_rejection_threshold"),
1249  twistMahalanobisThresh,
1250  std::numeric_limits<double>::max());
1251 
1252  int twistQueueSize = 1;
1253  nhLocal_.param(twistTopicName + "_queue_size", twistQueueSize, 1);
1254 
1255  bool nodelayTwist = false;
1256  nhLocal_.param(twistTopicName + "_nodelay", nodelayTwist, false);
1257 
1258  // Pull in the sensor's config, zero out values that are invalid for the twist type
1259  std::vector<int> twistUpdateVec = loadUpdateConfig(twistTopicName);
1260  std::fill(twistUpdateVec.begin() + POSITION_OFFSET, twistUpdateVec.begin() + POSITION_OFFSET + POSE_SIZE, 0);
1261 
1262  int twistUpdateSum = std::accumulate(twistUpdateVec.begin(), twistUpdateVec.end(), 0);
1263 
1264  if (twistUpdateSum > 0)
1265  {
1266  const CallbackData callbackData(twistTopicName, twistUpdateVec, twistUpdateSum, false, false,
1267  twistMahalanobisThresh);
1268 
1269  topicSubs_.push_back(
1270  nh_.subscribe<geometry_msgs::TwistWithCovarianceStamped>(twistTopic, twistQueueSize,
1271  boost::bind(&RosFilter<T>::twistCallback, this, _1, callbackData, baseLinkFrameId_),
1272  ros::VoidPtr(), ros::TransportHints().tcpNoDelay(nodelayTwist)));
1273 
1274  twistVarCounts[StateMemberVx] += twistUpdateVec[StateMemberVx];
1275  twistVarCounts[StateMemberVy] += twistUpdateVec[StateMemberVy];
1276  twistVarCounts[StateMemberVz] += twistUpdateVec[StateMemberVz];
1277  twistVarCounts[StateMemberVroll] += twistUpdateVec[StateMemberVroll];
1278  twistVarCounts[StateMemberVpitch] += twistUpdateVec[StateMemberVpitch];
1279  twistVarCounts[StateMemberVyaw] += twistUpdateVec[StateMemberVyaw];
1280  }
1281  else
1282  {
1283  ROS_WARN_STREAM("Warning: " << twistTopic << " is listed as an input topic, "
1284  "but all twist update variables are false");
1285  }
1286 
1287  RF_DEBUG("Subscribed to " << twistTopic << " (" << twistTopicName << ")\n\t" <<
1288  twistTopicName << "_rejection_threshold is " << twistMahalanobisThresh << "\n\t" <<
1289  twistTopicName << "_queue_size is " << twistQueueSize << "\n\t" <<
1290  twistTopicName << " update vector is " << twistUpdateVec);
1291  }
1292  }
1293  while (moreParams);
1294 
1295  // Repeat for IMU
1296  topicInd = 0;
1297  moreParams = false;
1298  do
1299  {
1300  std::stringstream ss;
1301  ss << "imu" << topicInd++;
1302  std::string imuTopicName = ss.str();
1303  moreParams = nhLocal_.hasParam(imuTopicName);
1304 
1305  if (moreParams)
1306  {
1307  bool differential;
1308  nhLocal_.param(imuTopicName + std::string("_differential"), differential, false);
1309 
1310  // Determine if we want to integrate this sensor relatively
1311  bool relative;
1312  nhLocal_.param(imuTopicName + std::string("_relative"), relative, false);
1313 
1314  if (relative && differential)
1315  {
1316  ROS_WARN_STREAM("Both " << imuTopicName << "_differential" << " and " << imuTopicName <<
1317  "_relative were set to true. Using differential mode.");
1318 
1319  relative = false;
1320  }
1321 
1322  std::string imuTopic;
1323  nhLocal_.getParam(imuTopicName, imuTopic);
1324 
1325  // Check for pose rejection threshold
1326  double poseMahalanobisThresh;
1327  nhLocal_.param(imuTopicName + std::string("_pose_rejection_threshold"),
1328  poseMahalanobisThresh,
1329  std::numeric_limits<double>::max());
1330 
1331  // Check for angular velocity rejection threshold
1332  double twistMahalanobisThresh;
1333  std::string imuTwistRejectionName =
1334  imuTopicName + std::string("_twist_rejection_threshold");
1335  nhLocal_.param(imuTwistRejectionName, twistMahalanobisThresh, std::numeric_limits<double>::max());
1336 
1337  // Check for acceleration rejection threshold
1338  double accelMahalanobisThresh;
1339  nhLocal_.param(imuTopicName + std::string("_linear_acceleration_rejection_threshold"),
1340  accelMahalanobisThresh,
1341  std::numeric_limits<double>::max());
1342 
1343  bool removeGravAcc = false;
1344  nhLocal_.param(imuTopicName + "_remove_gravitational_acceleration", removeGravAcc, false);
1345  removeGravitationalAcc_[imuTopicName + "_acceleration"] = removeGravAcc;
1346 
1347  // Now pull in its boolean update vector configuration and differential
1348  // update configuration (as this contains pose information)
1349  std::vector<int> updateVec = loadUpdateConfig(imuTopicName);
1350 
1351  std::vector<int> poseUpdateVec = updateVec;
1352  std::fill(poseUpdateVec.begin() + POSITION_V_OFFSET,
1353  poseUpdateVec.begin() + POSITION_V_OFFSET + TWIST_SIZE,
1354  0);
1355  std::fill(poseUpdateVec.begin() + POSITION_A_OFFSET,
1356  poseUpdateVec.begin() + POSITION_A_OFFSET + ACCELERATION_SIZE,
1357  0);
1358 
1359  std::vector<int> twistUpdateVec = updateVec;
1360  std::fill(twistUpdateVec.begin() + POSITION_OFFSET,
1361  twistUpdateVec.begin() + POSITION_OFFSET + POSE_SIZE,
1362  0);
1363  std::fill(twistUpdateVec.begin() + POSITION_A_OFFSET,
1364  twistUpdateVec.begin() + POSITION_A_OFFSET + ACCELERATION_SIZE,
1365  0);
1366 
1367  std::vector<int> accelUpdateVec = updateVec;
1368  std::fill(accelUpdateVec.begin() + POSITION_OFFSET,
1369  accelUpdateVec.begin() + POSITION_OFFSET + POSE_SIZE,
1370  0);
1371  std::fill(accelUpdateVec.begin() + POSITION_V_OFFSET,
1372  accelUpdateVec.begin() + POSITION_V_OFFSET + TWIST_SIZE,
1373  0);
1374 
1375  int poseUpdateSum = std::accumulate(poseUpdateVec.begin(), poseUpdateVec.end(), 0);
1376  int twistUpdateSum = std::accumulate(twistUpdateVec.begin(), twistUpdateVec.end(), 0);
1377  int accelUpdateSum = std::accumulate(accelUpdateVec.begin(), accelUpdateVec.end(), 0);
1378 
1379  // Check if we're using control input for any of the acceleration variables; turn off if so
1380  if (static_cast<bool>(controlUpdateVector[ControlMemberVx]) && static_cast<bool>(accelUpdateVec[StateMemberAx]))
1381  {
1382  ROS_WARN_STREAM("X acceleration is being measured from IMU; X velocity control input is disabled");
1383  controlUpdateVector[ControlMemberVx] = 0;
1384  }
1385  if (static_cast<bool>(controlUpdateVector[ControlMemberVy]) && static_cast<bool>(accelUpdateVec[StateMemberAy]))
1386  {
1387  ROS_WARN_STREAM("Y acceleration is being measured from IMU; Y velocity control input is disabled");
1388  controlUpdateVector[ControlMemberVy] = 0;
1389  }
1390  if (static_cast<bool>(controlUpdateVector[ControlMemberVz]) && static_cast<bool>(accelUpdateVec[StateMemberAz]))
1391  {
1392  ROS_WARN_STREAM("Z acceleration is being measured from IMU; Z velocity control input is disabled");
1393  controlUpdateVector[ControlMemberVz] = 0;
1394  }
1395 
1396  int imuQueueSize = 1;
1397  nhLocal_.param(imuTopicName + "_queue_size", imuQueueSize, 1);
1398 
1399  bool nodelayImu = false;
1400  nhLocal_.param(imuTopicName + "_nodelay", nodelayImu, false);
1401 
1402  if (poseUpdateSum + twistUpdateSum + accelUpdateSum > 0)
1403  {
1404  const CallbackData poseCallbackData(imuTopicName + "_pose", poseUpdateVec, poseUpdateSum, differential,
1405  relative, poseMahalanobisThresh);
1406  const CallbackData twistCallbackData(imuTopicName + "_twist", twistUpdateVec, twistUpdateSum, differential,
1407  relative, twistMahalanobisThresh);
1408  const CallbackData accelCallbackData(imuTopicName + "_acceleration", accelUpdateVec, accelUpdateSum,
1409  differential, relative, accelMahalanobisThresh);
1410 
1411  topicSubs_.push_back(
1412  nh_.subscribe<sensor_msgs::Imu>(imuTopic, imuQueueSize,
1413  boost::bind(&RosFilter<T>::imuCallback, this, _1, imuTopicName, poseCallbackData, twistCallbackData,
1414  accelCallbackData), ros::VoidPtr(), ros::TransportHints().tcpNoDelay(nodelayImu)));
1415  }
1416  else
1417  {
1418  ROS_WARN_STREAM("Warning: " << imuTopic << " is listed as an input topic, "
1419  "but all its update variables are false");
1420  }
1421 
1422  if (poseUpdateSum > 0)
1423  {
1424  if (differential)
1425  {
1426  twistVarCounts[StateMemberVroll] += poseUpdateVec[StateMemberRoll];
1427  twistVarCounts[StateMemberVpitch] += poseUpdateVec[StateMemberPitch];
1428  twistVarCounts[StateMemberVyaw] += poseUpdateVec[StateMemberYaw];
1429  }
1430  else
1431  {
1432  absPoseVarCounts[StateMemberRoll] += poseUpdateVec[StateMemberRoll];
1433  absPoseVarCounts[StateMemberPitch] += poseUpdateVec[StateMemberPitch];
1434  absPoseVarCounts[StateMemberYaw] += poseUpdateVec[StateMemberYaw];
1435  }
1436  }
1437 
1438  if (twistUpdateSum > 0)
1439  {
1440  twistVarCounts[StateMemberVroll] += twistUpdateVec[StateMemberVroll];
1441  twistVarCounts[StateMemberVpitch] += twistUpdateVec[StateMemberVpitch];
1442  twistVarCounts[StateMemberVyaw] += twistUpdateVec[StateMemberVyaw];
1443  }
1444 
1445  RF_DEBUG("Subscribed to " << imuTopic << " (" << imuTopicName << ")\n\t" <<
1446  imuTopicName << "_differential is " << (differential ? "true" : "false") << "\n\t" <<
1447  imuTopicName << "_pose_rejection_threshold is " << poseMahalanobisThresh << "\n\t" <<
1448  imuTopicName << "_twist_rejection_threshold is " << twistMahalanobisThresh << "\n\t" <<
1449  imuTopicName << "_linear_acceleration_rejection_threshold is " << accelMahalanobisThresh << "\n\t" <<
1450  imuTopicName << "_remove_gravitational_acceleration is " <<
1451  (removeGravAcc ? "true" : "false") << "\n\t" <<
1452  imuTopicName << "_queue_size is " << imuQueueSize << "\n\t" <<
1453  imuTopicName << " pose update vector is " << poseUpdateVec << "\t"<<
1454  imuTopicName << " twist update vector is " << twistUpdateVec << "\t" <<
1455  imuTopicName << " acceleration update vector is " << accelUpdateVec);
1456  }
1457  }
1458  while (moreParams);
1459 
1460  // Now that we've checked if IMU linear acceleration is being used, we can determine our final control parameters
1461  if (useControl_ && std::accumulate(controlUpdateVector.begin(), controlUpdateVector.end(), 0) == 0)
1462  {
1463  ROS_ERROR_STREAM("use_control is set to true, but control_config has only false values. No control term "
1464  "will be used.");
1465  useControl_ = false;
1466  }
1467 
1468  // If we're using control, set the parameters and create the necessary subscribers
1469  if (useControl_)
1470  {
1471  latestControl_.resize(TWIST_SIZE);
1472  latestControl_.setZero();
1473 
1474  filter_.setControlParams(controlUpdateVector, controlTimeout, accelerationLimits, accelerationGains,
1475  decelerationLimits, decelerationGains);
1476 
1477  if (stampedControl)
1478  {
1479  controlSub_ = nh_.subscribe<geometry_msgs::TwistStamped>("cmd_vel", 1, &RosFilter<T>::controlCallback, this);
1480  }
1481  else
1482  {
1483  controlSub_ = nh_.subscribe<geometry_msgs::Twist>("cmd_vel", 1, &RosFilter<T>::controlCallback, this);
1484  }
1485  }
1486 
1487  /* Warn users about:
1488  * 1. Multiple non-differential input sources
1489  * 2. No absolute *or* velocity measurements for pose variables
1490  */
1491  if (printDiagnostics_)
1492  {
1493  for (int stateVar = StateMemberX; stateVar <= StateMemberYaw; ++stateVar)
1494  {
1495  if (absPoseVarCounts[static_cast<StateMembers>(stateVar)] > 1)
1496  {
1497  std::stringstream stream;
1498  stream << absPoseVarCounts[static_cast<StateMembers>(stateVar - POSITION_OFFSET)] <<
1499  " absolute pose inputs detected for " << stateVariableNames_[stateVar] <<
1500  ". This may result in oscillations. Please ensure that your variances for each "
1501  "measured variable are set appropriately.";
1502 
1503  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
1504  stateVariableNames_[stateVar] + "_configuration",
1505  stream.str(),
1506  true);
1507  }
1508  else if (absPoseVarCounts[static_cast<StateMembers>(stateVar)] == 0)
1509  {
1510  if ((static_cast<StateMembers>(stateVar) == StateMemberX &&
1511  twistVarCounts[static_cast<StateMembers>(StateMemberVx)] == 0) ||
1512  (static_cast<StateMembers>(stateVar) == StateMemberY &&
1513  twistVarCounts[static_cast<StateMembers>(StateMemberVy)] == 0) ||
1514  (static_cast<StateMembers>(stateVar) == StateMemberZ &&
1515  twistVarCounts[static_cast<StateMembers>(StateMemberVz)] == 0 &&
1516  twoDMode_ == false) ||
1517  (static_cast<StateMembers>(stateVar) == StateMemberRoll &&
1518  twistVarCounts[static_cast<StateMembers>(StateMemberVroll)] == 0 &&
1519  twoDMode_ == false) ||
1520  (static_cast<StateMembers>(stateVar) == StateMemberPitch &&
1521  twistVarCounts[static_cast<StateMembers>(StateMemberVpitch)] == 0 &&
1522  twoDMode_ == false) ||
1523  (static_cast<StateMembers>(stateVar) == StateMemberYaw &&
1524  twistVarCounts[static_cast<StateMembers>(StateMemberVyaw)] == 0))
1525  {
1526  std::stringstream stream;
1527  stream << "Neither " << stateVariableNames_[stateVar] << " nor its "
1528  "velocity is being measured. This will result in unbounded "
1529  "error growth and erratic filter behavior.";
1530 
1531  addDiagnostic(diagnostic_msgs::DiagnosticStatus::ERROR,
1532  stateVariableNames_[stateVar] + "_configuration",
1533  stream.str(),
1534  true);
1535  }
1536  }
1537  }
1538  }
1539 
1540  // Load up the process noise covariance (from the launch file/parameter server)
1541  Eigen::MatrixXd processNoiseCovariance(STATE_SIZE, STATE_SIZE);
1542  processNoiseCovariance.setZero();
1543  XmlRpc::XmlRpcValue processNoiseCovarConfig;
1544 
1545  if (nhLocal_.hasParam("process_noise_covariance"))
1546  {
1547  try
1548  {
1549  nhLocal_.getParam("process_noise_covariance", processNoiseCovarConfig);
1550 
1551  ROS_ASSERT(processNoiseCovarConfig.getType() == XmlRpc::XmlRpcValue::TypeArray);
1552 
1553  int matSize = processNoiseCovariance.rows();
1554 
1555  for (int i = 0; i < matSize; i++)
1556  {
1557  for (int j = 0; j < matSize; j++)
1558  {
1559  try
1560  {
1561  // These matrices can cause problems if all the types
1562  // aren't specified with decimal points. Handle that
1563  // using string streams.
1564  std::ostringstream ostr;
1565  ostr << processNoiseCovarConfig[matSize * i + j];
1566  std::istringstream istr(ostr.str());
1567  istr >> processNoiseCovariance(i, j);
1568  }
1569  catch(XmlRpc::XmlRpcException &e)
1570  {
1571  throw e;
1572  }
1573  catch(...)
1574  {
1575  throw;
1576  }
1577  }
1578  }
1579 
1580  RF_DEBUG("Process noise covariance is:\n" << processNoiseCovariance << "\n");
1581  }
1582  catch (XmlRpc::XmlRpcException &e)
1583  {
1584  ROS_ERROR_STREAM("ERROR reading sensor config: " <<
1585  e.getMessage() <<
1586  " for process_noise_covariance (type: " <<
1587  processNoiseCovarConfig.getType() << ")");
1588  }
1589 
1590  filter_.setProcessNoiseCovariance(processNoiseCovariance);
1591  }
1592 
1593  // Load up the process noise covariance (from the launch file/parameter server)
1594  Eigen::MatrixXd initialEstimateErrorCovariance(STATE_SIZE, STATE_SIZE);
1595  initialEstimateErrorCovariance.setZero();
1596  XmlRpc::XmlRpcValue estimateErrorCovarConfig;
1597 
1598  if (nhLocal_.hasParam("initial_estimate_covariance"))
1599  {
1600  try
1601  {
1602  nhLocal_.getParam("initial_estimate_covariance", estimateErrorCovarConfig);
1603 
1604  ROS_ASSERT(estimateErrorCovarConfig.getType() == XmlRpc::XmlRpcValue::TypeArray);
1605 
1606  int matSize = initialEstimateErrorCovariance.rows();
1607 
1608  for (int i = 0; i < matSize; i++)
1609  {
1610  for (int j = 0; j < matSize; j++)
1611  {
1612  try
1613  {
1614  // These matrices can cause problems if all the types
1615  // aren't specified with decimal points. Handle that
1616  // using string streams.
1617  std::ostringstream ostr;
1618  ostr << estimateErrorCovarConfig[matSize * i + j];
1619  std::istringstream istr(ostr.str());
1620  istr >> initialEstimateErrorCovariance(i, j);
1621  }
1622  catch(XmlRpc::XmlRpcException &e)
1623  {
1624  throw e;
1625  }
1626  catch(...)
1627  {
1628  throw;
1629  }
1630  }
1631  }
1632 
1633  RF_DEBUG("Initial estimate error covariance is:\n" << initialEstimateErrorCovariance << "\n");
1634  }
1635  catch (XmlRpc::XmlRpcException &e)
1636  {
1637  ROS_ERROR_STREAM("ERROR reading initial_estimate_covariance (type: " <<
1638  estimateErrorCovarConfig.getType() <<
1639  "): " <<
1640  e.getMessage());
1641  }
1642  catch(...)
1643  {
1645  "ERROR reading initial_estimate_covariance (type: " << estimateErrorCovarConfig.getType() << ")");
1646  }
1647 
1648  filter_.setEstimateErrorCovariance(initialEstimateErrorCovariance);
1649  }
1650  }
1651 
1652  template<typename T>
1653  void RosFilter<T>::odometryCallback(const nav_msgs::Odometry::ConstPtr &msg, const std::string &topicName,
1654  const CallbackData &poseCallbackData, const CallbackData &twistCallbackData)
1655  {
1656  // If we've just reset the filter, then we want to ignore any messages
1657  // that arrive with an older timestamp
1658  if (msg->header.stamp <= lastSetPoseTime_)
1659  {
1660  std::stringstream stream;
1661  stream << "The " << topicName << " message has a timestamp equal to or before the last filter reset, " <<
1662  "this message will be ignored. This may indicate an empty or bad timestamp. (message time: " <<
1663  msg->header.stamp.toSec() << ")";
1664  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
1665  topicName + "_timestamp",
1666  stream.str(),
1667  false);
1668  RF_DEBUG("Received message that preceded the most recent pose reset. Ignoring...");
1669 
1670  return;
1671  }
1672 
1673  RF_DEBUG("------ RosFilter::odometryCallback (" << topicName << ") ------\n" << "Odometry message:\n" << *msg);
1674 
1675  if (poseCallbackData.updateSum_ > 0)
1676  {
1677  // Grab the pose portion of the message and pass it to the poseCallback
1678  geometry_msgs::PoseWithCovarianceStamped *posPtr = new geometry_msgs::PoseWithCovarianceStamped();
1679  posPtr->header = msg->header;
1680  posPtr->pose = msg->pose; // Entire pose object, also copies covariance
1681 
1682  geometry_msgs::PoseWithCovarianceStampedConstPtr pptr(posPtr);
1683  poseCallback(pptr, poseCallbackData, worldFrameId_, false);
1684  }
1685 
1686  if (twistCallbackData.updateSum_ > 0)
1687  {
1688  // Grab the twist portion of the message and pass it to the twistCallback
1689  geometry_msgs::TwistWithCovarianceStamped *twistPtr = new geometry_msgs::TwistWithCovarianceStamped();
1690  twistPtr->header = msg->header;
1691  twistPtr->header.frame_id = msg->child_frame_id;
1692  twistPtr->twist = msg->twist; // Entire twist object, also copies covariance
1693 
1694  geometry_msgs::TwistWithCovarianceStampedConstPtr tptr(twistPtr);
1695  twistCallback(tptr, twistCallbackData, baseLinkFrameId_);
1696  }
1697 
1698  RF_DEBUG("\n----- /RosFilter::odometryCallback (" << topicName << ") ------\n");
1699  }
1700 
1701  template<typename T>
1702  void RosFilter<T>::poseCallback(const geometry_msgs::PoseWithCovarianceStamped::ConstPtr &msg,
1703  const CallbackData &callbackData,
1704  const std::string &targetFrame,
1705  const bool imuData)
1706  {
1707  const std::string &topicName = callbackData.topicName_;
1708 
1709  // If we've just reset the filter, then we want to ignore any messages
1710  // that arrive with an older timestamp
1711  if (msg->header.stamp <= lastSetPoseTime_)
1712  {
1713  std::stringstream stream;
1714  stream << "The " << topicName << " message has a timestamp equal to or before the last filter reset, " <<
1715  "this message will be ignored. This may indicate an empty or bad timestamp. (message time: " <<
1716  msg->header.stamp.toSec() << ")";
1717  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
1718  topicName + "_timestamp",
1719  stream.str(),
1720  false);
1721  return;
1722  }
1723 
1724  RF_DEBUG("------ RosFilter::poseCallback (" << topicName << ") ------\n" <<
1725  "Pose message:\n" << *msg);
1726 
1727  // Put the initial value in the lastMessagTimes_ for this variable if it's empty
1728  if (lastMessageTimes_.count(topicName) == 0)
1729  {
1730  lastMessageTimes_.insert(std::pair<std::string, ros::Time>(topicName, msg->header.stamp));
1731  }
1732 
1733  // Make sure this message is newer than the last one
1734  if (msg->header.stamp >= lastMessageTimes_[topicName])
1735  {
1736  RF_DEBUG("Update vector for " << topicName << " is:\n" << callbackData.updateVector_);
1737 
1738  Eigen::VectorXd measurement(STATE_SIZE);
1739  Eigen::MatrixXd measurementCovariance(STATE_SIZE, STATE_SIZE);
1740 
1741  measurement.setZero();
1742  measurementCovariance.setZero();
1743 
1744  // Make sure we're actually updating at least one of these variables
1745  std::vector<int> updateVectorCorrected = callbackData.updateVector_;
1746 
1747  // Prepare the pose data for inclusion in the filter
1748  if (preparePose(msg,
1749  topicName,
1750  targetFrame,
1751  callbackData.differential_,
1752  callbackData.relative_,
1753  imuData,
1754  updateVectorCorrected,
1755  measurement,
1756  measurementCovariance))
1757  {
1758  // Store the measurement. Add a "pose" suffix so we know what kind of measurement
1759  // we're dealing with when we debug the core filter logic.
1760  enqueueMeasurement(topicName,
1761  measurement,
1762  measurementCovariance,
1763  updateVectorCorrected,
1764  callbackData.rejectionThreshold_,
1765  msg->header.stamp);
1766 
1767  RF_DEBUG("Enqueued new measurement for " << topicName << "\n");
1768  }
1769  else
1770  {
1771  RF_DEBUG("Did *not* enqueue measurement for " << topicName << "\n");
1772  }
1773 
1774  lastMessageTimes_[topicName] = msg->header.stamp;
1775 
1776  RF_DEBUG("Last message time for " << topicName << " is now " <<
1777  lastMessageTimes_[topicName] << "\n");
1778  }
1779  else if (resetOnTimeJump_ && ros::Time::isSimTime())
1780  {
1781  reset();
1782  }
1783  else
1784  {
1785  std::stringstream stream;
1786  stream << "The " << topicName << " message has a timestamp before that of the previous message received," <<
1787  " this message will be ignored. This may indicate a bad timestamp. (message time: " <<
1788  msg->header.stamp.toSec() << ")";
1789  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
1790  topicName + "_timestamp",
1791  stream.str(),
1792  false);
1793 
1794  RF_DEBUG("Message is too old. Last message time for " << topicName << " is "
1795  << lastMessageTimes_[topicName] << ", current message time is "
1796  << msg->header.stamp << ".\n");
1797  }
1798 
1799  RF_DEBUG("\n----- /RosFilter::poseCallback (" << topicName << ") ------\n");
1800  }
1801 
1802  template<typename T>
1804  {
1805  // Warn the user if the update took too long (> 2 cycles)
1806  const double loop_elapsed = (event.current_real - event.last_expected).toSec();
1807  if (loop_elapsed > 2./frequency_)
1808  {
1809  ROS_WARN_STREAM("Failed to meet update rate! Took " << std::setprecision(20) << loop_elapsed);
1810  }
1811 
1812  // Wait for the filter to be enabled
1813  if (!enabled_)
1814  {
1815  ROS_INFO_STREAM_ONCE("Filter is disabled. To enable it call the " << enableFilterSrv_.getService() <<
1816  " service");
1817  return;
1818  }
1819 
1820  ros::Time curTime = ros::Time::now();
1821 
1822  if (toggledOn_)
1823  {
1824  // Now we'll integrate any measurements we've received if requested
1825  integrateMeasurements(curTime);
1826  }
1827  else
1828  {
1829  // clear out measurements since we're not currently processing new entries
1831 
1832  // Reset last measurement time so we don't get a large time delta on toggle on
1833  if (filter_.getInitializedStatus())
1834  {
1835  filter_.setLastMeasurementTime(ros::Time::now().toSec());
1836  }
1837  }
1838 
1839  // Get latest state and publish it
1840  nav_msgs::Odometry filteredPosition;
1841 
1842  if (getFilteredOdometryMessage(filteredPosition))
1843  {
1845  worldBaseLinkTransMsg_.header.stamp = filteredPosition.header.stamp + tfTimeOffset_;
1846  worldBaseLinkTransMsg_.header.frame_id = filteredPosition.header.frame_id;
1847  worldBaseLinkTransMsg_.child_frame_id = filteredPosition.child_frame_id;
1848 
1849  worldBaseLinkTransMsg_.transform.translation.x = filteredPosition.pose.pose.position.x;
1850  worldBaseLinkTransMsg_.transform.translation.y = filteredPosition.pose.pose.position.y;
1851  worldBaseLinkTransMsg_.transform.translation.z = filteredPosition.pose.pose.position.z;
1852  worldBaseLinkTransMsg_.transform.rotation = filteredPosition.pose.pose.orientation;
1853 
1854  // the filteredPosition is the message containing the state and covariances: nav_msgs Odometry
1855 
1856  if (!validateFilterOutput(filteredPosition))
1857  {
1858  ROS_ERROR_STREAM("Critical Error, NaNs were detected in the output state of the filter." <<
1859  " This was likely due to poorly coniditioned process, noise, or sensor covariances.");
1860  }
1861 
1862  // If the worldFrameId_ is the odomFrameId_ frame, then we can just send the transform. If the
1863  // worldFrameId_ is the mapFrameId_ frame, we'll have some work to do.
1864  if (publishTransform_)
1865  {
1866  if (filteredPosition.header.frame_id == odomFrameId_)
1867  {
1869  }
1870  else if (filteredPosition.header.frame_id == mapFrameId_)
1871  {
1872  try
1873  {
1874  tf2::Transform worldBaseLinkTrans;
1875  tf2::fromMsg(worldBaseLinkTransMsg_.transform, worldBaseLinkTrans);
1876 
1877  tf2::Transform baseLinkOdomTrans;
1879  baseLinkOdomTrans);
1880 
1881  /*
1882  * First, see these two references:
1883  * http://wiki.ros.org/tf/Overview/Using%20Published%20Transforms#lookupTransform
1884  * http://wiki.ros.org/geometry/CoordinateFrameConventions#Transform_Direction
1885  * We have a transform from mapFrameId_->baseLinkFrameId_, but it would actually transform
1886  * a given pose from baseLinkFrameId_->mapFrameId_. We then used lookupTransform, whose
1887  * first two arguments are target frame and source frame, to get a transform from
1888  * baseLinkFrameId_->odomFrameId_. However, this transform would actually transform data
1889  * from odomFrameId_->baseLinkFrameId_. Now imagine that we have a position in the
1890  * mapFrameId_ frame. First, we multiply it by the inverse of the
1891  * mapFrameId_->baseLinkFrameId, which will transform that data from mapFrameId_ to
1892  * baseLinkFrameId_. Now we want to go from baseLinkFrameId_->odomFrameId_, but the
1893  * transform we have takes data from odomFrameId_->baseLinkFrameId_, so we need its
1894  * inverse as well. We have now transformed our data from mapFrameId_ to odomFrameId_.
1895  * However, if we want other users to be able to do the same, we need to broadcast
1896  * the inverse of that entire transform.
1897  */
1898 
1899  tf2::Transform mapOdomTrans;
1900  mapOdomTrans.mult(worldBaseLinkTrans, baseLinkOdomTrans);
1901 
1902  geometry_msgs::TransformStamped mapOdomTransMsg;
1903  mapOdomTransMsg.transform = tf2::toMsg(mapOdomTrans);
1904  mapOdomTransMsg.header.stamp = filteredPosition.header.stamp + tfTimeOffset_;
1905  mapOdomTransMsg.header.frame_id = mapFrameId_;
1906  mapOdomTransMsg.child_frame_id = odomFrameId_;
1907 
1908  worldTransformBroadcaster_.sendTransform(mapOdomTransMsg);
1909  }
1910  catch(...)
1911  {
1912  ROS_ERROR_STREAM_DELAYED_THROTTLE(5.0, "Could not obtain transform from "
1913  << odomFrameId_ << "->" << baseLinkFrameId_);
1914  }
1915  }
1916  else
1917  {
1918  ROS_ERROR_STREAM("Odometry message frame_id was " << filteredPosition.header.frame_id <<
1919  ", expected " << mapFrameId_ << " or " << odomFrameId_);
1920  }
1921  }
1922 
1923  // Fire off the position and the transform
1924  positionPub_.publish(filteredPosition);
1925 
1926  if (printDiagnostics_)
1927  {
1928  freqDiag_->tick();
1929  }
1930  }
1931 
1932  // Publish the acceleration if desired and filter is initialized
1933  geometry_msgs::AccelWithCovarianceStamped filteredAcceleration;
1934  if (publishAcceleration_ && getFilteredAccelMessage(filteredAcceleration))
1935  {
1936  accelPub_.publish(filteredAcceleration);
1937  }
1938 
1939  /* Diagnostics can behave strangely when playing back from bag
1940  * files and using simulated time, so we have to check for
1941  * time suddenly moving backwards as well as the standard
1942  * timeout criterion before publishing. */
1943  double diagDuration = (curTime - lastDiagTime_).toSec();
1944  if (printDiagnostics_ && (diagDuration >= diagnosticUpdater_.getPeriod() || diagDuration < 0.0))
1945  {
1947  lastDiagTime_ = curTime;
1948  }
1949 
1950  // Clear out expired history data
1951  if (smoothLaggedData_)
1952  {
1953  clearExpiredHistory(filter_.getLastMeasurementTime() - historyLength_);
1954  }
1955  }
1956 
1957  template<typename T>
1958  void RosFilter<T>::setPoseCallback(const geometry_msgs::PoseWithCovarianceStamped::ConstPtr &msg)
1959  {
1960  RF_DEBUG("------ RosFilter::setPoseCallback ------\nPose message:\n" << *msg);
1961 
1962  ROS_INFO_STREAM("Received set_pose request with value\n" << *msg);
1963 
1964  std::string topicName("setPose");
1965 
1966  // Get rid of any initial poses (pretend we've never had a measurement)
1967  initialMeasurements_.clear();
1968  previousMeasurements_.clear();
1970 
1972 
1973  filterStateHistory_.clear();
1974  measurementHistory_.clear();
1975 
1976  // Also set the last set pose time, so we ignore all messages
1977  // that occur before it
1978  lastSetPoseTime_ = msg->header.stamp;
1979 
1980  // Set the state vector to the reported pose
1981  Eigen::VectorXd measurement(STATE_SIZE);
1982  Eigen::MatrixXd measurementCovariance(STATE_SIZE, STATE_SIZE);
1983  std::vector<int> updateVector(STATE_SIZE, true);
1984 
1985  // We only measure pose variables, so initialize the vector to 0
1986  measurement.setZero();
1987 
1988  // Set this to the identity and let the message reset it
1989  measurementCovariance.setIdentity();
1990  measurementCovariance *= 1e-6;
1991 
1992  // Prepare the pose data (really just using this to transform it into the target frame).
1993  // Twist data is going to get zeroed out.
1994  preparePose(msg, topicName, worldFrameId_, false, false, false, updateVector, measurement, measurementCovariance);
1995 
1996  // For the state
1997  filter_.setState(measurement);
1998  filter_.setEstimateErrorCovariance(measurementCovariance);
1999 
2000  filter_.setLastMeasurementTime(ros::Time::now().toSec());
2001 
2002  RF_DEBUG("\n------ /RosFilter::setPoseCallback ------\n");
2003  }
2004 
2005  template<typename T>
2006  bool RosFilter<T>::setPoseSrvCallback(robot_localization::SetPose::Request& request,
2007  robot_localization::SetPose::Response&)
2008  {
2009  geometry_msgs::PoseWithCovarianceStamped::Ptr msg;
2010  msg = boost::make_shared<geometry_msgs::PoseWithCovarianceStamped>(request.pose);
2011  setPoseCallback(msg);
2012 
2013  return true;
2014  }
2015 
2016  template<typename T>
2017  bool RosFilter<T>::enableFilterSrvCallback(std_srvs::Empty::Request&,
2018  std_srvs::Empty::Response&)
2019  {
2020  RF_DEBUG("\n[" << ros::this_node::getName() << ":]" << " ------ /RosFilter::enableFilterSrvCallback ------\n");
2021  if (enabled_)
2022  {
2024  ":] Asking for enabling filter service, but the filter was already enabled! Use param disabled_at_startup.");
2025  }
2026  else
2027  {
2028  ROS_INFO_STREAM("[" << ros::this_node::getName() << ":] Enabling filter...");
2029  enabled_ = true;
2030  }
2031  return true;
2032  }
2033 
2034  template<typename T>
2035  void RosFilter<T>::twistCallback(const geometry_msgs::TwistWithCovarianceStamped::ConstPtr &msg,
2036  const CallbackData &callbackData,
2037  const std::string &targetFrame)
2038  {
2039  const std::string &topicName = callbackData.topicName_;
2040 
2041  // If we've just reset the filter, then we want to ignore any messages
2042  // that arrive with an older timestamp
2043  if (msg->header.stamp <= lastSetPoseTime_)
2044  {
2045  std::stringstream stream;
2046  stream << "The " << topicName << " message has a timestamp equal to or before the last filter reset, " <<
2047  "this message will be ignored. This may indicate an empty or bad timestamp. (message time: " <<
2048  msg->header.stamp.toSec() << ")";
2049  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
2050  topicName + "_timestamp",
2051  stream.str(),
2052  false);
2053  return;
2054  }
2055 
2056  RF_DEBUG("------ RosFilter::twistCallback (" << topicName << ") ------\n"
2057  "Twist message:\n" << *msg);
2058 
2059  if (lastMessageTimes_.count(topicName) == 0)
2060  {
2061  lastMessageTimes_.insert(std::pair<std::string, ros::Time>(topicName, msg->header.stamp));
2062  }
2063 
2064  // Make sure this message is newer than the last one
2065  if (msg->header.stamp >= lastMessageTimes_[topicName])
2066  {
2067  RF_DEBUG("Update vector for " << topicName << " is:\n" << callbackData.updateVector_);
2068 
2069  Eigen::VectorXd measurement(STATE_SIZE);
2070  Eigen::MatrixXd measurementCovariance(STATE_SIZE, STATE_SIZE);
2071 
2072  measurement.setZero();
2073  measurementCovariance.setZero();
2074 
2075  // Make sure we're actually updating at least one of these variables
2076  std::vector<int> updateVectorCorrected = callbackData.updateVector_;
2077 
2078  // Prepare the twist data for inclusion in the filter
2079  if (prepareTwist(msg, topicName, targetFrame, updateVectorCorrected, measurement, measurementCovariance))
2080  {
2081  // Store the measurement. Add a "twist" suffix so we know what kind of measurement
2082  // we're dealing with when we debug the core filter logic.
2083  enqueueMeasurement(topicName,
2084  measurement,
2085  measurementCovariance,
2086  updateVectorCorrected,
2087  callbackData.rejectionThreshold_,
2088  msg->header.stamp);
2089 
2090  RF_DEBUG("Enqueued new measurement for " << topicName << "_twist\n");
2091  }
2092  else
2093  {
2094  RF_DEBUG("Did *not* enqueue measurement for " << topicName << "_twist\n");
2095  }
2096 
2097  lastMessageTimes_[topicName] = msg->header.stamp;
2098 
2099  RF_DEBUG("Last message time for " << topicName << " is now " <<
2100  lastMessageTimes_[topicName] << "\n");
2101  }
2102  else if (resetOnTimeJump_ && ros::Time::isSimTime())
2103  {
2104  reset();
2105  }
2106  else
2107  {
2108  std::stringstream stream;
2109  stream << "The " << topicName << " message has a timestamp before that of the previous message received," <<
2110  " this message will be ignored. This may indicate a bad timestamp. (message time: " <<
2111  msg->header.stamp.toSec() << ")";
2112  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
2113  topicName + "_timestamp",
2114  stream.str(),
2115  false);
2116 
2117  RF_DEBUG("Message is too old. Last message time for " << topicName << " is " << lastMessageTimes_[topicName] <<
2118  ", current message time is " << msg->header.stamp << ".\n");
2119  }
2120 
2121  RF_DEBUG("\n----- /RosFilter::twistCallback (" << topicName << ") ------\n");
2122  }
2123 
2124  template<typename T>
2125  void RosFilter<T>::addDiagnostic(const int errLevel,
2126  const std::string &topicAndClass,
2127  const std::string &message,
2128  const bool staticDiag)
2129  {
2130  if (staticDiag)
2131  {
2132  staticDiagnostics_[topicAndClass] = message;
2133  staticDiagErrorLevel_ = std::max(staticDiagErrorLevel_, errLevel);
2134  }
2135  else
2136  {
2137  dynamicDiagnostics_[topicAndClass] = message;
2138  dynamicDiagErrorLevel_ = std::max(dynamicDiagErrorLevel_, errLevel);
2139  }
2140  }
2141 
2142  template<typename T>
2144  {
2145  wrapper.clear();
2146  wrapper.clearSummary();
2147 
2148  int maxErrLevel = std::max(staticDiagErrorLevel_, dynamicDiagErrorLevel_);
2149 
2150  // Report the overall status
2151  switch (maxErrLevel)
2152  {
2153  case diagnostic_msgs::DiagnosticStatus::ERROR:
2154  wrapper.summary(maxErrLevel,
2155  "Erroneous data or settings detected for a robot_localization state estimation node.");
2156  break;
2157  case diagnostic_msgs::DiagnosticStatus::WARN:
2158  wrapper.summary(maxErrLevel,
2159  "Potentially erroneous data or settings detected for "
2160  "a robot_localization state estimation node.");
2161  break;
2162  case diagnostic_msgs::DiagnosticStatus::STALE:
2163  wrapper.summary(maxErrLevel,
2164  "The state of the robot_localization state estimation node is stale.");
2165  break;
2166  case diagnostic_msgs::DiagnosticStatus::OK:
2167  wrapper.summary(maxErrLevel,
2168  "The robot_localization state estimation node appears to be functioning properly.");
2169  break;
2170  default:
2171  break;
2172  }
2173 
2174  // Aggregate all the static messages
2175  for (std::map<std::string, std::string>::iterator diagIt = staticDiagnostics_.begin();
2176  diagIt != staticDiagnostics_.end();
2177  ++diagIt)
2178  {
2179  wrapper.add(diagIt->first, diagIt->second);
2180  }
2181 
2182  // Aggregate all the dynamic messages, then clear them
2183  for (std::map<std::string, std::string>::iterator diagIt = dynamicDiagnostics_.begin();
2184  diagIt != dynamicDiagnostics_.end();
2185  ++diagIt)
2186  {
2187  wrapper.add(diagIt->first, diagIt->second);
2188  }
2189  dynamicDiagnostics_.clear();
2190 
2191  // Reset the warning level for the dynamic diagnostic messages
2192  dynamicDiagErrorLevel_ = diagnostic_msgs::DiagnosticStatus::OK;
2193  }
2194 
2195  template<typename T>
2196  void RosFilter<T>::copyCovariance(const double *arr,
2197  Eigen::MatrixXd &covariance,
2198  const std::string &topicName,
2199  const std::vector<int> &updateVector,
2200  const size_t offset,
2201  const size_t dimension)
2202  {
2203  for (size_t i = 0; i < dimension; i++)
2204  {
2205  for (size_t j = 0; j < dimension; j++)
2206  {
2207  covariance(i, j) = arr[dimension * i + j];
2208 
2209  if (printDiagnostics_)
2210  {
2211  std::string iVar = stateVariableNames_[offset + i];
2212 
2213  if (covariance(i, j) > 1e3 && (updateVector[offset + i] || updateVector[offset + j]))
2214  {
2215  std::string jVar = stateVariableNames_[offset + j];
2216 
2217  std::stringstream stream;
2218  stream << "The covariance at position (" << dimension * i + j << "), which corresponds to " <<
2219  (i == j ? iVar + " variance" : iVar + " and " + jVar + " covariance") <<
2220  ", the value is extremely large (" << covariance(i, j) << "), but the update vector for " <<
2221  (i == j ? iVar : iVar + " and/or " + jVar) << " is set to true. This may produce undesirable results.";
2222 
2223  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
2224  topicName + "_covariance",
2225  stream.str(),
2226  false);
2227  }
2228  else if (updateVector[i] && i == j && covariance(i, j) == 0)
2229  {
2230  std::stringstream stream;
2231  stream << "The covariance at position (" << dimension * i + j << "), which corresponds to " <<
2232  iVar << " variance, was zero. This will be replaced with a small value to maintain filter "
2233  "stability, but should be corrected at the message origin node.";
2234 
2235  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
2236  topicName + "_covariance",
2237  stream.str(),
2238  false);
2239  }
2240  else if (updateVector[i] && i == j && covariance(i, j) < 0)
2241  {
2242  std::stringstream stream;
2243  stream << "The covariance at position (" << dimension * i + j <<
2244  "), which corresponds to " << iVar << " variance, was negative. This will be replaced with a "
2245  "small positive value to maintain filter stability, but should be corrected at the message "
2246  "origin node.";
2247 
2248  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
2249  topicName + "_covariance",
2250  stream.str(),
2251  false);
2252  }
2253  }
2254  }
2255  }
2256  }
2257 
2258  template<typename T>
2259  void RosFilter<T>::copyCovariance(const Eigen::MatrixXd &covariance,
2260  double *arr,
2261  const size_t dimension)
2262  {
2263  for (size_t i = 0; i < dimension; i++)
2264  {
2265  for (size_t j = 0; j < dimension; j++)
2266  {
2267  arr[dimension * i + j] = covariance(i, j);
2268  }
2269  }
2270  }
2271 
2272  template<typename T>
2273  std::vector<int> RosFilter<T>::loadUpdateConfig(const std::string &topicName)
2274  {
2275  XmlRpc::XmlRpcValue topicConfig;
2276  std::vector<int> updateVector(STATE_SIZE, 0);
2277  std::string topicConfigName = topicName + "_config";
2278 
2279  try
2280  {
2281  nhLocal_.getParam(topicConfigName, topicConfig);
2282 
2284 
2285  if (topicConfig.size() != STATE_SIZE)
2286  {
2287  ROS_WARN_STREAM("Configuration vector for " << topicConfigName << " should have 15 entries.");
2288  }
2289 
2290  for (int i = 0; i < topicConfig.size(); i++)
2291  {
2292  // The double cast looks strange, but we'll get exceptions if we
2293  // remove the cast to bool. vector<bool> is discouraged, so updateVector
2294  // uses integers.
2295  updateVector[i] = static_cast<int>(static_cast<bool>(topicConfig[i]));
2296  }
2297  }
2298  catch (XmlRpc::XmlRpcException &e)
2299  {
2300  ROS_FATAL_STREAM("Could not read sensor update configuration for topic " << topicName <<
2301  " (type: " << topicConfig.getType() << ", expected: " << XmlRpc::XmlRpcValue::TypeArray
2302  << "). Error was " << e.getMessage() << "\n");
2303  }
2304 
2305  return updateVector;
2306  }
2307 
2308  template<typename T>
2309  bool RosFilter<T>::prepareAcceleration(const sensor_msgs::Imu::ConstPtr &msg,
2310  const std::string &topicName,
2311  const std::string &targetFrame,
2312  std::vector<int> &updateVector,
2313  Eigen::VectorXd &measurement,
2314  Eigen::MatrixXd &measurementCovariance)
2315  {
2316  RF_DEBUG("------ RosFilter::prepareAcceleration (" << topicName << ") ------\n");
2317 
2318  // 1. Get the measurement into a vector
2319  tf2::Vector3 accTmp(msg->linear_acceleration.x,
2320  msg->linear_acceleration.y,
2321  msg->linear_acceleration.z);
2322 
2323  // Set relevant header info
2324  std::string msgFrame = (msg->header.frame_id == "" ? baseLinkFrameId_ : msg->header.frame_id);
2325 
2326  // 2. robot_localization lets users configure which variables from the sensor should be
2327  // fused with the filter. This is specified at the sensor level. However, the data
2328  // may go through transforms before being fused with the state estimate. In that case,
2329  // we need to know which of the transformed variables came from the pre-transformed
2330  // "approved" variables (i.e., the ones that had "true" in their xxx_config parameter).
2331  // To do this, we create a pose from the original upate vector, which contains only
2332  // zeros and ones. This pose goes through the same transforms as the measurement. The
2333  // non-zero values that result will be used to modify the updateVector.
2334  tf2::Matrix3x3 maskAcc(updateVector[StateMemberAx], 0, 0,
2335  0, updateVector[StateMemberAy], 0,
2336  0, 0, updateVector[StateMemberAz]);
2337 
2338  // 3. We'll need to rotate the covariance as well
2339  Eigen::MatrixXd covarianceRotated(ACCELERATION_SIZE, ACCELERATION_SIZE);
2340  covarianceRotated.setZero();
2341 
2342  this->copyCovariance(&(msg->linear_acceleration_covariance[0]),
2343  covarianceRotated,
2344  topicName,
2345  updateVector,
2348 
2349  RF_DEBUG("Original measurement as tf object: " << accTmp <<
2350  "\nOriginal update vector:\n" << updateVector <<
2351  "\nOriginal covariance matrix:\n" << covarianceRotated << "\n");
2352 
2353  // 4. We need to transform this into the target frame (probably base_link)
2354  // It's unlikely that we'll get a velocity measurement in another frame, but
2355  // we have to handle the situation.
2356  tf2::Transform targetFrameTrans;
2358  targetFrame,
2359  msgFrame,
2360  msg->header.stamp,
2361  tfTimeout_,
2362  targetFrameTrans);
2363 
2364  if (canTransform)
2365  {
2366  // We don't know if the user has already handled the removal
2367  // of normal forces, so we use a parameter
2368  if (removeGravitationalAcc_[topicName])
2369  {
2370  tf2::Vector3 normAcc(0, 0, gravitationalAcc_);
2371  tf2::Quaternion curAttitude;
2372  tf2::Transform trans;
2373 
2374  if (::fabs(msg->orientation_covariance[0] + 1) < 1e-9)
2375  {
2376  // Imu message contains no orientation, so we should use orientation
2377  // from filter state to transform and remove acceleration
2378  const Eigen::VectorXd &state = filter_.getState();
2379  tf2::Vector3 stateTmp(state(StateMemberRoll),
2380  state(StateMemberPitch),
2381  state(StateMemberYaw));
2382  // transform state orientation to IMU frame
2383  tf2::Transform imuFrameTrans;
2385  msgFrame,
2386  targetFrame,
2387  msg->header.stamp,
2388  tfTimeout_,
2389  imuFrameTrans);
2390  stateTmp = imuFrameTrans.getBasis() * stateTmp;
2391  curAttitude.setRPY(stateTmp.getX(), stateTmp.getY(), stateTmp.getZ());
2392  }
2393  else
2394  {
2395  tf2::fromMsg(msg->orientation, curAttitude);
2396  if (fabs(curAttitude.length() - 1.0) > 0.01)
2397  {
2398  ROS_WARN_ONCE("An input was not normalized, this should NOT happen, but will normalize.");
2399  curAttitude.normalize();
2400  }
2401  }
2402  trans.setRotation(curAttitude);
2403  tf2::Vector3 rotNorm = trans.getBasis().inverse() * normAcc;
2404  accTmp.setX(accTmp.getX() - rotNorm.getX());
2405  accTmp.setY(accTmp.getY() - rotNorm.getY());
2406  accTmp.setZ(accTmp.getZ() - rotNorm.getZ());
2407 
2408  RF_DEBUG("Orientation is " << curAttitude <<
2409  "Acceleration due to gravity is " << rotNorm <<
2410  "After removing acceleration due to gravity, acceleration is " << accTmp << "\n");
2411  }
2412 
2413  // Transform to correct frame
2414  // @todo: This needs to take into account offsets from the origin. Right now,
2415  // it assumes that if the sensor is placed at some non-zero offset from the
2416  // vehicle's center, that the vehicle turns with constant velocity. This needs
2417  // to be something like
2418  // accTmp = targetFrameTrans.getBasis() * accTmp - targetFrameTrans.getOrigin().cross(rotation_acceleration);
2419  // We can get rotational acceleration by differentiating the rotational velocity
2420  // (if it's available)
2421  accTmp = targetFrameTrans.getBasis() * accTmp;
2422  maskAcc = targetFrameTrans.getBasis() * maskAcc;
2423 
2424  // Now use the mask values to determine which update vector values should be true
2425  updateVector[StateMemberAx] = static_cast<int>(
2426  maskAcc.getRow(StateMemberAx - POSITION_A_OFFSET).length() >= 1e-6);
2427  updateVector[StateMemberAy] = static_cast<int>(
2428  maskAcc.getRow(StateMemberAy - POSITION_A_OFFSET).length() >= 1e-6);
2429  updateVector[StateMemberAz] = static_cast<int>(
2430  maskAcc.getRow(StateMemberAz - POSITION_A_OFFSET).length() >= 1e-6);
2431 
2432  RF_DEBUG(msg->header.frame_id << "->" << targetFrame << " transform:\n" << targetFrameTrans <<
2433  "\nAfter applying transform to " << targetFrame << ", update vector is:\n" << updateVector <<
2434  "\nAfter applying transform to " << targetFrame << ", measurement is:\n" << accTmp << "\n");
2435 
2436  // 5. Now rotate the covariance: create an augmented
2437  // matrix that contains a 3D rotation matrix in the
2438  // upper-left and lower-right quadrants, and zeros
2439  // elsewhere
2440  tf2::Matrix3x3 rot(targetFrameTrans.getRotation());
2441  Eigen::MatrixXd rot3d(ACCELERATION_SIZE, ACCELERATION_SIZE);
2442  rot3d.setIdentity();
2443 
2444  for (size_t rInd = 0; rInd < ACCELERATION_SIZE; ++rInd)
2445  {
2446  rot3d(rInd, 0) = rot.getRow(rInd).getX();
2447  rot3d(rInd, 1) = rot.getRow(rInd).getY();
2448  rot3d(rInd, 2) = rot.getRow(rInd).getZ();
2449  }
2450 
2451  // Carry out the rotation
2452  covarianceRotated = rot3d * covarianceRotated.eval() * rot3d.transpose();
2453 
2454  RF_DEBUG("Transformed covariance is \n" << covarianceRotated << "\n");
2455 
2456  // 6. Store our corrected measurement and covariance
2457  measurement(StateMemberAx) = accTmp.getX();
2458  measurement(StateMemberAy) = accTmp.getY();
2459  measurement(StateMemberAz) = accTmp.getZ();
2460 
2461  // Copy the covariances
2462  measurementCovariance.block(POSITION_A_OFFSET, POSITION_A_OFFSET, ACCELERATION_SIZE, ACCELERATION_SIZE) =
2463  covarianceRotated.block(0, 0, ACCELERATION_SIZE, ACCELERATION_SIZE);
2464 
2465  // 7. Handle 2D mode
2466  if (twoDMode_)
2467  {
2468  forceTwoD(measurement, measurementCovariance, updateVector);
2469  }
2470  }
2471  else
2472  {
2473  RF_DEBUG("Could not transform measurement into " << targetFrame << ". Ignoring...\n");
2474  }
2475 
2476  RF_DEBUG("\n----- /RosFilter::prepareAcceleration(" << topicName << ") ------\n");
2477 
2478  return canTransform;
2479  }
2480 
2481  template<typename T>
2482  bool RosFilter<T>::preparePose(const geometry_msgs::PoseWithCovarianceStamped::ConstPtr &msg,
2483  const std::string &topicName,
2484  const std::string &targetFrame,
2485  const bool differential,
2486  const bool relative,
2487  const bool imuData,
2488  std::vector<int> &updateVector,
2489  Eigen::VectorXd &measurement,
2490  Eigen::MatrixXd &measurementCovariance)
2491  {
2492  bool retVal = false;
2493 
2494  RF_DEBUG("------ RosFilter::preparePose (" << topicName << ") ------\n");
2495 
2496  // 1. Get the measurement into a tf-friendly transform (pose) object
2498 
2499  // We'll need this later for storing this measurement for differential integration
2500  tf2::Transform curMeasurement;
2501 
2502  // Handle issues where frame_id data is not filled out properly
2503  // @todo: verify that this is necessary still. New IMU handling may
2504  // have rendered this obsolete.
2505  std::string finalTargetFrame;
2506  if (targetFrame == "" && msg->header.frame_id == "")
2507  {
2508  // Blank target and message frames mean we can just
2509  // use our world_frame
2510  finalTargetFrame = worldFrameId_;
2511  poseTmp.frame_id_ = finalTargetFrame;
2512  }
2513  else if (targetFrame == "")
2514  {
2515  // A blank target frame means we shouldn't bother
2516  // transforming the data
2517  finalTargetFrame = msg->header.frame_id;
2518  poseTmp.frame_id_ = finalTargetFrame;
2519  }
2520  else
2521  {
2522  // Otherwise, we should use our target frame
2523  finalTargetFrame = targetFrame;
2524  poseTmp.frame_id_ = (differential ? finalTargetFrame : msg->header.frame_id);
2525  }
2526 
2527  RF_DEBUG("Final target frame for " << topicName << " is " << finalTargetFrame << "\n");
2528 
2529  poseTmp.stamp_ = msg->header.stamp;
2530 
2531  // Fill out the position data
2532  poseTmp.setOrigin(tf2::Vector3(msg->pose.pose.position.x,
2533  msg->pose.pose.position.y,
2534  msg->pose.pose.position.z));
2535 
2536  tf2::Quaternion orientation;
2537 
2538  // Handle bad (empty) quaternions
2539  if (msg->pose.pose.orientation.x == 0 && msg->pose.pose.orientation.y == 0 &&
2540  msg->pose.pose.orientation.z == 0 && msg->pose.pose.orientation.w == 0)
2541  {
2542  orientation.setValue(0.0, 0.0, 0.0, 1.0);
2543 
2544  if (updateVector[StateMemberRoll] || updateVector[StateMemberPitch] || updateVector[StateMemberYaw])
2545  {
2546  std::stringstream stream;
2547  stream << "The " << topicName << " message contains an invalid orientation quaternion, " <<
2548  "but its configuration is such that orientation data is being used. Correcting...";
2549 
2550  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
2551  topicName + "_orientation",
2552  stream.str(),
2553  false);
2554  }
2555  }
2556  else
2557  {
2558  tf2::fromMsg(msg->pose.pose.orientation, orientation);
2559  if (fabs(orientation.length() - 1.0) > 0.01)
2560  {
2561  ROS_WARN_ONCE("An input was not normalized, this should NOT happen, but will normalize.");
2562  orientation.normalize();
2563  }
2564  }
2565 
2566  // Fill out the orientation data
2567  poseTmp.setRotation(orientation);
2568 
2569  // 2. Get the target frame transformation
2570  tf2::Transform targetFrameTrans;
2572  finalTargetFrame,
2573  poseTmp.frame_id_,
2574  poseTmp.stamp_,
2575  tfTimeout_,
2576  targetFrameTrans);
2577 
2578  // 3. Make sure we can work with this data before carrying on
2579  if (canTransform)
2580  {
2581  /* 4. robot_localization lets users configure which variables from the sensor should be
2582  * fused with the filter. This is specified at the sensor level. However, the data
2583  * may go through transforms before being fused with the state estimate. In that case,
2584  * we need to know which of the transformed variables came from the pre-transformed
2585  * "approved" variables (i.e., the ones that had "true" in their xxx_config parameter).
2586  * To do this, we construct matrices using the update vector values on the diagonals,
2587  * pass this matrix through the rotation, and use the length of each row to determine
2588  * the transformed update vector. The process is slightly different for IMUs, as the
2589  * coordinate frame transform is really the base_link->imu_frame transform, and not
2590  * a transform from some other world-fixed frame (even though the IMU data itself *is*
2591  * reported in a world fixed frame). */
2592  tf2::Matrix3x3 maskPosition(updateVector[StateMemberX], 0, 0,
2593  0, updateVector[StateMemberY], 0,
2594  0, 0, updateVector[StateMemberZ]);
2595 
2596  tf2::Matrix3x3 maskOrientation(updateVector[StateMemberRoll], 0, 0,
2597  0, updateVector[StateMemberPitch], 0,
2598  0, 0, updateVector[StateMemberYaw]);
2599 
2600  if (imuData)
2601  {
2602  /* We have to treat IMU orientation data differently. Even though we are dealing with pose
2603  * data when we work with orientations, for IMUs, the frame_id is the frame in which the
2604  * sensor is mounted, and not the coordinate frame of the IMU. Imagine an IMU that is mounted
2605  * facing sideways. The pitch in the IMU frame becomes roll for the vehicle. This means that
2606  * we need to rotate roll and pitch angles by the IMU's mounting yaw offset, and we must apply
2607  * similar treatment to its update mask and covariance. */
2608 
2609  double dummy, yaw;
2610  targetFrameTrans.getBasis().getRPY(dummy, dummy, yaw);
2611  tf2::Matrix3x3 transTmp;
2612  transTmp.setRPY(0.0, 0.0, yaw);
2613 
2614  maskPosition = transTmp * maskPosition;
2615  maskOrientation = transTmp * maskOrientation;
2616  }
2617  else
2618  {
2619  maskPosition = targetFrameTrans.getBasis() * maskPosition;
2620  maskOrientation = targetFrameTrans.getBasis() * maskOrientation;
2621  }
2622 
2623  // Now copy the mask values back into the update vector: any row with a significant vector length
2624  // indicates that we want to set that variable to true in the update vector.
2625  updateVector[StateMemberX] = static_cast<int>(
2626  maskPosition.getRow(StateMemberX - POSITION_OFFSET).length() >= 1e-6);
2627  updateVector[StateMemberY] = static_cast<int>(
2628  maskPosition.getRow(StateMemberY - POSITION_OFFSET).length() >= 1e-6);
2629  updateVector[StateMemberZ] = static_cast<int>(
2630  maskPosition.getRow(StateMemberZ - POSITION_OFFSET).length() >= 1e-6);
2631  updateVector[StateMemberRoll] = static_cast<int>(
2632  maskOrientation.getRow(StateMemberRoll - ORIENTATION_OFFSET).length() >= 1e-6);
2633  updateVector[StateMemberPitch] = static_cast<int>(
2634  maskOrientation.getRow(StateMemberPitch - ORIENTATION_OFFSET).length() >= 1e-6);
2635  updateVector[StateMemberYaw] = static_cast<int>(
2636  maskOrientation.getRow(StateMemberYaw - ORIENTATION_OFFSET).length() >= 1e-6);
2637 
2638  // 5a. We'll need to rotate the covariance as well. Create a container and copy over the
2639  // covariance data
2640  Eigen::MatrixXd covariance(POSE_SIZE, POSE_SIZE);
2641  covariance.setZero();
2642  copyCovariance(&(msg->pose.covariance[0]), covariance, topicName, updateVector, POSITION_OFFSET, POSE_SIZE);
2643 
2644  // 5b. Now rotate the covariance: create an augmented matrix that
2645  // contains a 3D rotation matrix in the upper-left and lower-right
2646  // quadrants, with zeros elsewhere.
2647  tf2::Matrix3x3 rot;
2648  Eigen::MatrixXd rot6d(POSE_SIZE, POSE_SIZE);
2649  rot6d.setIdentity();
2650  Eigen::MatrixXd covarianceRotated;
2651 
2652  if (imuData)
2653  {
2654  // Apply the same special logic to the IMU covariance rotation
2655  double dummy, yaw;
2656  targetFrameTrans.getBasis().getRPY(dummy, dummy, yaw);
2657  rot.setRPY(0.0, 0.0, yaw);
2658  }
2659  else
2660  {
2661  rot.setRotation(targetFrameTrans.getRotation());
2662  }
2663 
2664  for (size_t rInd = 0; rInd < POSITION_SIZE; ++rInd)
2665  {
2666  rot6d(rInd, 0) = rot.getRow(rInd).getX();
2667  rot6d(rInd, 1) = rot.getRow(rInd).getY();
2668  rot6d(rInd, 2) = rot.getRow(rInd).getZ();
2669  rot6d(rInd+POSITION_SIZE, 3) = rot.getRow(rInd).getX();
2670  rot6d(rInd+POSITION_SIZE, 4) = rot.getRow(rInd).getY();
2671  rot6d(rInd+POSITION_SIZE, 5) = rot.getRow(rInd).getZ();
2672  }
2673 
2674  // Now carry out the rotation
2675  covarianceRotated = rot6d * covariance * rot6d.transpose();
2676 
2677  RF_DEBUG("After rotating into the " << finalTargetFrame <<
2678  " frame, covariance is \n" << covarianceRotated << "\n");
2679 
2680  /* 6a. For IMU data, the transform that we get is the transform from the body
2681  * frame of the robot (e.g., base_link) to the mounting frame of the robot. It
2682  * is *not* the coordinate frame in which the IMU orientation data is reported.
2683  * If the IMU is mounted in a non-neutral orientation, we need to remove those
2684  * offsets, and then we need to potentially "swap" roll and pitch.
2685  * Note that this transform does NOT handle NED->ENU conversions. Data is assumed
2686  * to be in the ENU frame when it is received.
2687  * */
2688  if (imuData)
2689  {
2690  // First, convert the transform and measurement rotation to RPY
2691  // @todo: There must be a way to handle this with quaternions. Need to look into it.
2692  double rollOffset = 0;
2693  double pitchOffset = 0;
2694  double yawOffset = 0;
2695  double roll = 0;
2696  double pitch = 0;
2697  double yaw = 0;
2698  RosFilterUtilities::quatToRPY(targetFrameTrans.getRotation(), rollOffset, pitchOffset, yawOffset);
2699  RosFilterUtilities::quatToRPY(poseTmp.getRotation(), roll, pitch, yaw);
2700 
2701  // 6b. Apply the offset (making sure to bound them), and throw them in a vector
2702  tf2::Vector3 rpyAngles(FilterUtilities::clampRotation(roll - rollOffset),
2703  FilterUtilities::clampRotation(pitch - pitchOffset),
2704  FilterUtilities::clampRotation(yaw - yawOffset));
2705 
2706  // 6c. Now we need to rotate the roll and pitch by the yaw offset value.
2707  // Imagine a case where an IMU is mounted facing sideways. In that case
2708  // pitch for the IMU's world frame is roll for the robot.
2709  tf2::Matrix3x3 mat;
2710  mat.setRPY(0.0, 0.0, yawOffset);
2711  rpyAngles = mat * rpyAngles;
2712  poseTmp.getBasis().setRPY(rpyAngles.getX(), rpyAngles.getY(), rpyAngles.getZ());
2713 
2714  // We will use this target transformation later on, but
2715  // we've already transformed this data as if the IMU
2716  // were mounted neutrall on the robot, so we can just
2717  // make the transform the identity.
2718  targetFrameTrans.setIdentity();
2719  }
2720 
2721  // 7. Two cases: if we're in differential mode, we need to generate a twist
2722  // message. Otherwise, we just transform it to the target frame.
2723  if (differential)
2724  {
2725  bool success = false;
2726 
2727  // We're going to be playing with poseTmp, so store it,
2728  // as we'll need to save its current value for the next
2729  // measurement.
2730  curMeasurement = poseTmp;
2731 
2732  // Make sure we have previous measurements to work with
2733  if (previousMeasurements_.count(topicName) > 0 && previousMeasurementCovariances_.count(topicName) > 0)
2734  {
2735  // 7a. If we are carrying out differential integration and
2736  // we have a previous measurement for this sensor,then we
2737  // need to apply the inverse of that measurement to this new
2738  // measurement to produce a "delta" measurement between the two.
2739  // Even if we're not using all of the variables from this sensor,
2740  // we need to use the whole measurement to determine the delta
2741  // to the new measurement
2742  tf2::Transform prevMeasurement = previousMeasurements_[topicName];
2743  poseTmp.setData(prevMeasurement.inverseTimes(poseTmp));
2744 
2745  RF_DEBUG("Previous measurement:\n" << previousMeasurements_[topicName] <<
2746  "\nAfter removing previous measurement, measurement delta is:\n" << poseTmp << "\n");
2747 
2748  // 7b. Now we we have a measurement delta in the frame_id of the
2749  // message, but we want that delta to be in the target frame, so
2750  // we need to apply the rotation of the target frame transform.
2751  targetFrameTrans.setOrigin(tf2::Vector3(0.0, 0.0, 0.0));
2752  poseTmp.mult(targetFrameTrans, poseTmp);
2753 
2754  RF_DEBUG("After rotating to the target frame, measurement delta is:\n" << poseTmp << "\n");
2755 
2756  // 7c. Now use the time difference from the last message to compute
2757  // translational and rotational velocities
2758  double dt = msg->header.stamp.toSec() - lastMessageTimes_[topicName].toSec();
2759  double xVel = poseTmp.getOrigin().getX() / dt;
2760  double yVel = poseTmp.getOrigin().getY() / dt;
2761  double zVel = poseTmp.getOrigin().getZ() / dt;
2762 
2763  double rollVel = 0;
2764  double pitchVel = 0;
2765  double yawVel = 0;
2766 
2767  RosFilterUtilities::quatToRPY(poseTmp.getRotation(), rollVel, pitchVel, yawVel);
2768  rollVel /= dt;
2769  pitchVel /= dt;
2770  yawVel /= dt;
2771 
2772  RF_DEBUG("Previous message time was " << lastMessageTimes_[topicName].toSec() <<
2773  ", current message time is " << msg->header.stamp.toSec() << ", delta is " <<
2774  dt << ", velocity is (vX, vY, vZ): (" << xVel << ", " << yVel << ", " << zVel <<
2775  ")\n" << "(vRoll, vPitch, vYaw): (" << rollVel << ", " << pitchVel << ", " <<
2776  yawVel << ")\n");
2777 
2778  // 7d. Fill out the velocity data in the message
2779  geometry_msgs::TwistWithCovarianceStamped *twistPtr = new geometry_msgs::TwistWithCovarianceStamped();
2780  twistPtr->header = msg->header;
2781  twistPtr->header.frame_id = baseLinkFrameId_;
2782  twistPtr->twist.twist.linear.x = xVel;
2783  twistPtr->twist.twist.linear.y = yVel;
2784  twistPtr->twist.twist.linear.z = zVel;
2785  twistPtr->twist.twist.angular.x = rollVel;
2786  twistPtr->twist.twist.angular.y = pitchVel;
2787  twistPtr->twist.twist.angular.z = yawVel;
2788  std::vector<int> twistUpdateVec(STATE_SIZE, false);
2789  std::copy(updateVector.begin() + POSITION_OFFSET,
2790  updateVector.begin() + POSE_SIZE,
2791  twistUpdateVec.begin() + POSITION_V_OFFSET);
2792  std::copy(twistUpdateVec.begin(), twistUpdateVec.end(), updateVector.begin());
2793  geometry_msgs::TwistWithCovarianceStampedConstPtr ptr(twistPtr);
2794 
2795  // 7e. Now rotate the previous covariance for this measurement to get it
2796  // into the target frame, and add the current measurement's rotated covariance
2797  // to the previous measurement's rotated covariance, and multiply by the time delta.
2798  Eigen::MatrixXd prevCovarRotated = rot6d * previousMeasurementCovariances_[topicName] * rot6d.transpose();
2799  covarianceRotated = (covarianceRotated.eval() + prevCovarRotated) * dt;
2800  copyCovariance(covarianceRotated, &(twistPtr->twist.covariance[0]), POSE_SIZE);
2801 
2802  RF_DEBUG("Previous measurement covariance:\n" << previousMeasurementCovariances_[topicName] <<
2803  "\nPrevious measurement covariance rotated:\n" << prevCovarRotated <<
2804  "\nFinal twist covariance:\n" << covarianceRotated << "\n");
2805 
2806  // Now pass this on to prepareTwist, which will convert it to the required frame
2807  success = prepareTwist(ptr,
2808  topicName + "_twist",
2809  twistPtr->header.frame_id,
2810  updateVector,
2811  measurement,
2812  measurementCovariance);
2813  }
2814 
2815  // 7f. Update the previous measurement and measurement covariance
2816  previousMeasurements_[topicName] = curMeasurement;
2817  previousMeasurementCovariances_[topicName] = covariance;
2818 
2819  retVal = success;
2820  }
2821  else
2822  {
2823  // 7g. If we're in relative mode, remove the initial measurement
2824  if (relative)
2825  {
2826  if (initialMeasurements_.count(topicName) == 0)
2827  {
2828  initialMeasurements_.insert(std::pair<std::string, tf2::Transform>(topicName, poseTmp));
2829  }
2830 
2831  tf2::Transform initialMeasurement = initialMeasurements_[topicName];
2832  poseTmp.setData(initialMeasurement.inverseTimes(poseTmp));
2833  }
2834 
2835  // 7h. Apply the target frame transformation to the pose object.
2836  poseTmp.mult(targetFrameTrans, poseTmp);
2837  poseTmp.frame_id_ = finalTargetFrame;
2838 
2839  // 7i. Finally, copy everything into our measurement and covariance objects
2840  measurement(StateMemberX) = poseTmp.getOrigin().x();
2841  measurement(StateMemberY) = poseTmp.getOrigin().y();
2842  measurement(StateMemberZ) = poseTmp.getOrigin().z();
2843 
2844  // The filter needs roll, pitch, and yaw values instead of quaternions
2845  double roll, pitch, yaw;
2846  RosFilterUtilities::quatToRPY(poseTmp.getRotation(), roll, pitch, yaw);
2847  measurement(StateMemberRoll) = roll;
2848  measurement(StateMemberPitch) = pitch;
2849  measurement(StateMemberYaw) = yaw;
2850 
2851  measurementCovariance.block(0, 0, POSE_SIZE, POSE_SIZE) = covarianceRotated.block(0, 0, POSE_SIZE, POSE_SIZE);
2852 
2853  // 8. Handle 2D mode
2854  if (twoDMode_)
2855  {
2856  forceTwoD(measurement, measurementCovariance, updateVector);
2857  }
2858 
2859  retVal = true;
2860  }
2861  }
2862  else
2863  {
2864  retVal = false;
2865 
2866  RF_DEBUG("Could not transform measurement into " << finalTargetFrame << ". Ignoring...");
2867  }
2868 
2869  RF_DEBUG("\n----- /RosFilter::preparePose (" << topicName << ") ------\n");
2870 
2871  return retVal;
2872  }
2873 
2874  template<typename T>
2875  bool RosFilter<T>::prepareTwist(const geometry_msgs::TwistWithCovarianceStamped::ConstPtr &msg,
2876  const std::string &topicName,
2877  const std::string &targetFrame,
2878  std::vector<int> &updateVector,
2879  Eigen::VectorXd &measurement,
2880  Eigen::MatrixXd &measurementCovariance)
2881  {
2882  RF_DEBUG("------ RosFilter::prepareTwist (" << topicName << ") ------\n");
2883 
2884  // 1. Get the measurement into two separate vector objects.
2885  tf2::Vector3 twistLin(msg->twist.twist.linear.x,
2886  msg->twist.twist.linear.y,
2887  msg->twist.twist.linear.z);
2888  tf2::Vector3 measTwistRot(msg->twist.twist.angular.x,
2889  msg->twist.twist.angular.y,
2890  msg->twist.twist.angular.z);
2891 
2892  // 1a. This sensor may or may not measure rotational velocity. Regardless,
2893  // if it measures linear velocity, then later on, we'll need to remove "false"
2894  // linear velocity resulting from angular velocity and the translational offset
2895  // of the sensor from the vehicle origin.
2896  const Eigen::VectorXd &state = filter_.getState();
2897  tf2::Vector3 stateTwistRot(state(StateMemberVroll),
2898  state(StateMemberVpitch),
2899  state(StateMemberVyaw));
2900 
2901  // Determine the frame_id of the data
2902  std::string msgFrame = (msg->header.frame_id == "" ? targetFrame : msg->header.frame_id);
2903 
2904  // 2. robot_localization lets users configure which variables from the sensor should be
2905  // fused with the filter. This is specified at the sensor level. However, the data
2906  // may go through transforms before being fused with the state estimate. In that case,
2907  // we need to know which of the transformed variables came from the pre-transformed
2908  // "approved" variables (i.e., the ones that had "true" in their xxx_config parameter).
2909  // To do this, we construct matrices using the update vector values on the diagonals,
2910  // pass this matrix through the rotation, and use the length of each row to determine
2911  // the transformed update vector.
2912  tf2::Matrix3x3 maskLin(updateVector[StateMemberVx], 0, 0,
2913  0, updateVector[StateMemberVy], 0,
2914  0, 0, updateVector[StateMemberVz]);
2915 
2916  tf2::Matrix3x3 maskRot(updateVector[StateMemberVroll], 0, 0,
2917  0, updateVector[StateMemberVpitch], 0,
2918  0, 0, updateVector[StateMemberVyaw]);
2919 
2920  // 3. We'll need to rotate the covariance as well
2921  Eigen::MatrixXd covarianceRotated(TWIST_SIZE, TWIST_SIZE);
2922  covarianceRotated.setZero();
2923 
2924  copyCovariance(&(msg->twist.covariance[0]),
2925  covarianceRotated,
2926  topicName,
2927  updateVector,
2929  TWIST_SIZE);
2930 
2931  RF_DEBUG("Original measurement as tf object:\nLinear: " << twistLin <<
2932  "Rotational: " << measTwistRot <<
2933  "\nOriginal update vector:\n" << updateVector <<
2934  "\nOriginal covariance matrix:\n" << covarianceRotated << "\n");
2935 
2936  // 4. We need to transform this into the target frame (probably base_link)
2937  tf2::Transform targetFrameTrans;
2939  targetFrame,
2940  msgFrame,
2941  msg->header.stamp,
2942  tfTimeout_,
2943  targetFrameTrans);
2944 
2945  if (canTransform)
2946  {
2947  // Transform to correct frame. Note that we can get linear velocity
2948  // as a result of the sensor offset and rotational velocity
2949  measTwistRot = targetFrameTrans.getBasis() * measTwistRot;
2950  twistLin = targetFrameTrans.getBasis() * twistLin + targetFrameTrans.getOrigin().cross(stateTwistRot);
2951  maskLin = targetFrameTrans.getBasis() * maskLin;
2952  maskRot = targetFrameTrans.getBasis() * maskRot;
2953 
2954  // Now copy the mask values back into the update vector
2955  updateVector[StateMemberVx] = static_cast<int>(
2956  maskLin.getRow(StateMemberVx - POSITION_V_OFFSET).length() >= 1e-6);
2957  updateVector[StateMemberVy] = static_cast<int>(
2958  maskLin.getRow(StateMemberVy - POSITION_V_OFFSET).length() >= 1e-6);
2959  updateVector[StateMemberVz] = static_cast<int>(
2960  maskLin.getRow(StateMemberVz - POSITION_V_OFFSET).length() >= 1e-6);
2961  updateVector[StateMemberVroll] = static_cast<int>(
2962  maskRot.getRow(StateMemberVroll - ORIENTATION_V_OFFSET).length() >= 1e-6);
2963  updateVector[StateMemberVpitch] = static_cast<int>(
2964  maskRot.getRow(StateMemberVpitch - ORIENTATION_V_OFFSET).length() >= 1e-6);
2965  updateVector[StateMemberVyaw] = static_cast<int>(
2966  maskRot.getRow(StateMemberVyaw - ORIENTATION_V_OFFSET).length() >= 1e-6);
2967 
2968  RF_DEBUG(msg->header.frame_id << "->" << targetFrame << " transform:\n" << targetFrameTrans <<
2969  "\nAfter applying transform to " << targetFrame << ", update vector is:\n" << updateVector <<
2970  "\nAfter applying transform to " << targetFrame << ", measurement is:\n" <<
2971  "Linear: " << twistLin << "Rotational: " << measTwistRot << "\n");
2972 
2973  // 5. Now rotate the covariance: create an augmented
2974  // matrix that contains a 3D rotation matrix in the
2975  // upper-left and lower-right quadrants, and zeros
2976  // elsewhere
2977  tf2::Matrix3x3 rot(targetFrameTrans.getRotation());
2978  Eigen::MatrixXd rot6d(TWIST_SIZE, TWIST_SIZE);
2979  rot6d.setIdentity();
2980 
2981  for (size_t rInd = 0; rInd < POSITION_SIZE; ++rInd)
2982  {
2983  rot6d(rInd, 0) = rot.getRow(rInd).getX();
2984  rot6d(rInd, 1) = rot.getRow(rInd).getY();
2985  rot6d(rInd, 2) = rot.getRow(rInd).getZ();
2986  rot6d(rInd+POSITION_SIZE, 3) = rot.getRow(rInd).getX();
2987  rot6d(rInd+POSITION_SIZE, 4) = rot.getRow(rInd).getY();
2988  rot6d(rInd+POSITION_SIZE, 5) = rot.getRow(rInd).getZ();
2989  }
2990 
2991  // Carry out the rotation
2992  covarianceRotated = rot6d * covarianceRotated.eval() * rot6d.transpose();
2993 
2994  RF_DEBUG("Transformed covariance is \n" << covarianceRotated << "\n");
2995 
2996  // 6. Store our corrected measurement and covariance
2997  measurement(StateMemberVx) = twistLin.getX();
2998  measurement(StateMemberVy) = twistLin.getY();
2999  measurement(StateMemberVz) = twistLin.getZ();
3000  measurement(StateMemberVroll) = measTwistRot.getX();
3001  measurement(StateMemberVpitch) = measTwistRot.getY();
3002  measurement(StateMemberVyaw) = measTwistRot.getZ();
3003 
3004  // Copy the covariances
3005  measurementCovariance.block(POSITION_V_OFFSET, POSITION_V_OFFSET, TWIST_SIZE, TWIST_SIZE) =
3006  covarianceRotated.block(0, 0, TWIST_SIZE, TWIST_SIZE);
3007 
3008  // 7. Handle 2D mode
3009  if (twoDMode_)
3010  {
3011  forceTwoD(measurement, measurementCovariance, updateVector);
3012  }
3013  }
3014  else
3015  {
3016  RF_DEBUG("Could not transform measurement into " << targetFrame << ". Ignoring...");
3017  }
3018 
3019  RF_DEBUG("\n----- /RosFilter::prepareTwist (" << topicName << ") ------\n");
3020 
3021  return canTransform;
3022  }
3023 
3024  template<typename T>
3026  {
3027  FilterStatePtr state = FilterStatePtr(new FilterState());
3028  state->state_ = Eigen::VectorXd(filter.getState());
3029  state->estimateErrorCovariance_ = Eigen::MatrixXd(filter.getEstimateErrorCovariance());
3030  state->lastMeasurementTime_ = filter.getLastMeasurementTime();
3031  state->latestControl_ = Eigen::VectorXd(filter.getControl());
3032  state->latestControlTime_ = filter.getControlTime();
3033  filterStateHistory_.push_back(state);
3034  RF_DEBUG("Saved state with timestamp " << std::setprecision(20) << state->lastMeasurementTime_ <<
3035  " to history. " << filterStateHistory_.size() << " measurements are in the queue.\n");
3036  }
3037 
3038  template<typename T>
3039  bool RosFilter<T>::revertTo(const double time)
3040  {
3041  RF_DEBUG("\n----- RosFilter::revertTo -----\n");
3042  RF_DEBUG("\nRequested time was " << std::setprecision(20) << time << "\n")
3043 
3044  size_t history_size = filterStateHistory_.size();
3045 
3046  // Walk back through the queue until we reach a filter state whose time stamp is less than or equal to the
3047  // requested time. Since every saved state after that time will be overwritten/corrected, we can pop from
3048  // the queue. If the history is insufficiently short, we just take the oldest state we have.
3049  FilterStatePtr lastHistoryState;
3050  while (!filterStateHistory_.empty() && filterStateHistory_.back()->lastMeasurementTime_ > time)
3051  {
3052  lastHistoryState = filterStateHistory_.back();
3053  filterStateHistory_.pop_back();
3054  }
3055 
3056  // If the state history is not empty at this point, it means that our history was large enough, and we
3057  // should revert to the state at the back of the history deque.
3058  bool retVal = false;
3059  if (!filterStateHistory_.empty())
3060  {
3061  retVal = true;
3062  lastHistoryState = filterStateHistory_.back();
3063  }
3064  else
3065  {
3066  RF_DEBUG("Insufficient history to revert to time " << time << "\n");
3067 
3068  if (lastHistoryState)
3069  {
3070  RF_DEBUG("Will revert to oldest state at " << lastHistoryState->latestControlTime_ << ".\n");
3071  ROS_WARN_STREAM_DELAYED_THROTTLE(historyLength_, "Could not revert to state with time " <<
3072  std::setprecision(20) << time << ". Instead reverted to state with time " <<
3073  lastHistoryState->lastMeasurementTime_ << ". History size was " << history_size);
3074  }
3075  }
3076 
3077  // If we have a valid reversion state, revert
3078  if (lastHistoryState)
3079  {
3080  // Reset filter to the latest state from the queue.
3081  const FilterStatePtr &state = lastHistoryState;
3082  filter_.setState(state->state_);
3083  filter_.setEstimateErrorCovariance(state->estimateErrorCovariance_);
3084  filter_.setLastMeasurementTime(state->lastMeasurementTime_);
3085 
3086  RF_DEBUG("Reverted to state with time " << std::setprecision(20) << state->lastMeasurementTime_ << "\n");
3087 
3088  // Repeat for measurements, but push every measurement onto the measurement queue as we go
3089  int restored_measurements = 0;
3090  while (!measurementHistory_.empty() && measurementHistory_.back()->time_ > time)
3091  {
3092  // Don't need to restore measurements that predate our earliest state time
3093  if (state->lastMeasurementTime_ <= measurementHistory_.back()->time_)
3094  {
3096  restored_measurements++;
3097  }
3098 
3099  measurementHistory_.pop_back();
3100  }
3101 
3102  RF_DEBUG("Restored " << restored_measurements << " to measurement queue.\n");
3103  }
3104 
3105  RF_DEBUG("\n----- /RosFilter::revertTo\n");
3106 
3107  return retVal;
3108  }
3109 
3110  template<typename T>
3111  bool RosFilter<T>::validateFilterOutput(const nav_msgs::Odometry &message)
3112  {
3113  return !std::isnan(message.pose.pose.position.x) && !std::isinf(message.pose.pose.position.x) &&
3114  !std::isnan(message.pose.pose.position.y) && !std::isinf(message.pose.pose.position.y) &&
3115  !std::isnan(message.pose.pose.position.z) && !std::isinf(message.pose.pose.position.z) &&
3116  !std::isnan(message.pose.pose.orientation.x) && !std::isinf(message.pose.pose.orientation.x) &&
3117  !std::isnan(message.pose.pose.orientation.y) && !std::isinf(message.pose.pose.orientation.y) &&
3118  !std::isnan(message.pose.pose.orientation.z) && !std::isinf(message.pose.pose.orientation.z) &&
3119  !std::isnan(message.pose.pose.orientation.w) && !std::isinf(message.pose.pose.orientation.w) &&
3120  !std::isnan(message.twist.twist.linear.x) && !std::isinf(message.twist.twist.linear.x) &&
3121  !std::isnan(message.twist.twist.linear.y) && !std::isinf(message.twist.twist.linear.y) &&
3122  !std::isnan(message.twist.twist.linear.z) && !std::isinf(message.twist.twist.linear.z) &&
3123  !std::isnan(message.twist.twist.angular.x) && !std::isinf(message.twist.twist.angular.x) &&
3124  !std::isnan(message.twist.twist.angular.y) && !std::isinf(message.twist.twist.angular.y) &&
3125  !std::isnan(message.twist.twist.angular.z) && !std::isinf(message.twist.twist.angular.z);
3126  }
3127 
3128  template<typename T>
3129  void RosFilter<T>::clearExpiredHistory(const double cutOffTime)
3130  {
3131  RF_DEBUG("\n----- RosFilter::clearExpiredHistory -----" <<
3132  "\nCutoff time is " << cutOffTime << "\n");
3133 
3134  int poppedMeasurements = 0;
3135  int poppedStates = 0;
3136 
3137  while (!measurementHistory_.empty() && measurementHistory_.front()->time_ < cutOffTime)
3138  {
3139  measurementHistory_.pop_front();
3140  poppedMeasurements++;
3141  }
3142 
3143  while (!filterStateHistory_.empty() && filterStateHistory_.front()->lastMeasurementTime_ < cutOffTime)
3144  {
3145  filterStateHistory_.pop_front();
3146  poppedStates++;
3147  }
3148 
3149  RF_DEBUG("\nPopped " << poppedMeasurements << " measurements and " <<
3150  poppedStates << " states from their respective queues." <<
3151  "\n---- /RosFilter::clearExpiredHistory ----\n");
3152  }
3153 
3154  template<typename T>
3156  {
3157  while (!measurementQueue_.empty() && ros::ok())
3158  {
3159  measurementQueue_.pop();
3160  }
3161  return;
3162  }
3163 } // namespace RobotLocalization
3164 
3165 // Instantiations of classes is required when template class code
3166 // is placed in a .cpp file.
msg
const std::string & getMessage() const
bool useControl_
Whether or not we use a control term.
Definition: ros_filter.h:633
ros::Time lastDiagTime_
last call of periodicUpdate
Definition: ros_filter.h:699
bool publishAcceleration_
Whether we publish the acceleration.
Definition: ros_filter.h:666
const int STATE_SIZE
Global constants that define our state vector size and offsets to groups of values within that state...
Definition: filter_common.h:75
void setRPY(const tf2Scalar &roll, const tf2Scalar &pitch, const tf2Scalar &yaw)
void getRPY(tf2Scalar &roll, tf2Scalar &pitch, tf2Scalar &yaw, unsigned int solution_number=1) const
const int TWIST_SIZE
Definition: filter_common.h:85
void integrateMeasurements(const ros::Time &currentTime)
Processes all measurements in the measurement queue, in temporal order.
Definition: ros_filter.cpp:559
const int POSITION_A_OFFSET
Definition: filter_common.h:80
bool getFilteredAccelMessage(geometry_msgs::AccelWithCovarianceStamped &message)
Retrieves the EKF&#39;s acceleration output for broadcasting.
Definition: ros_filter.cpp:410
void appendPrefix(std::string tfPrefix, std::string &frameId)
Utility method for appending tf2 prefixes cleanly.
void loadParams()
Loads all parameters from file.
Definition: ros_filter.cpp:679
void odometryCallback(const nav_msgs::Odometry::ConstPtr &msg, const std::string &topicName, const CallbackData &poseCallbackData, const CallbackData &twistCallbackData)
Callback method for receiving all odometry messages.
bool validateFilterOutput(const nav_msgs::Odometry &message)
Validates filter outputs for NaNs and Infinite values.
TF2SIMD_FORCE_INLINE Matrix3x3 & getBasis()
std::vector< int > updateVector_
Definition: ros_filter.h:95
ros::Subscriber controlSub_
Subscribes to the control input topic.
Definition: ros_filter.h:431
#define ROS_WARN_STREAM_THROTTLE(period, args)
void publish(const boost::shared_ptr< M > &message) const
void twistCallback(const geometry_msgs::TwistWithCovarianceStamped::ConstPtr &msg, const CallbackData &callbackData, const std::string &targetFrame)
Callback method for receiving all twist messages.
boost::shared_ptr< FilterState > FilterStatePtr
Definition: filter_base.h:149
ros::NodeHandle nhLocal_
Local node handle (for params)
Definition: ros_filter.h:543
std::map< std::string, Eigen::MatrixXd > previousMeasurementCovariances_
We also need the previous covariance matrix for differential data.
Definition: ros_filter.h:561
const Eigen::VectorXd & getState()
Gets the filter state.
bool resetOnTimeJump_
Whether to reset the filters when backwards jump in time is detected.
Definition: ros_filter.h:478
MeasurementHistoryDeque measurementHistory_
A deque of previous measurements which is implicitly ordered from earliest to latest measurement...
Definition: ros_filter.h:678
void setPoseCallback(const geometry_msgs::PoseWithCovarianceStamped::ConstPtr &msg)
Callback method for manually setting/resetting the internal pose estimate.
void aggregateDiagnostics(diagnostic_updater::DiagnosticStatusWrapper &wrapper)
Aggregates all diagnostics so they can be published.
std::string baseLinkFrameId_
tf frame name for the robot&#39;s body frame
Definition: ros_filter.h:420
std::map< std::string, ros::Time > lastMessageTimes_
Store the last time a message from each topic was received.
Definition: ros_filter.h:516
ros::Subscriber setPoseSub_
Subscribes to the set_pose topic (usually published from rviz). Message type is geometry_msgs/PoseWit...
Definition: ros_filter.h:583
void summary(unsigned char lvl, const std::string s)
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
Quaternion getRotation() const
void accelerationCallback(const sensor_msgs::Imu::ConstPtr &msg, const CallbackData &callbackData, const std::string &targetFrame)
Callback method for receiving all acceleration (IMU) messages.
Definition: ros_filter.cpp:184
TF2SIMD_FORCE_INLINE void setRotation(const Quaternion &q)
bool enabled_
Whether the filter is enabled or not. See disabledAtStartup_.
Definition: ros_filter.h:643
ros::Timer periodicUpdateTimer_
timer calling periodicUpdate
Definition: ros_filter.h:703
ros::ServiceServer toggleFilterProcessingSrv_
Service that allows another node to toggle on/off filter processing while still publishing. Uses a robot_localization ToggleFilterProcessing service.
Definition: ros_filter.h:601
int size() const
tf2_ros::TransformBroadcaster worldTransformBroadcaster_
broadcaster of worldTransform tfs
Definition: ros_filter.h:682
void setHardwareID(const std::string &hwid)
Eigen::VectorXd latestControl_
The most recent control input.
Definition: ros_filter.h:482
ros::Duration tfTimeout_
Parameter that specifies the how long we wait for a transform to become available.
Definition: ros_filter.h:490
virtual geometry_msgs::TransformStamped lookupTransform(const std::string &target_frame, const std::string &source_frame, const ros::Time &time, const ros::Duration timeout) const
Structure used for storing and comparing measurements (for priority queues)
Definition: filter_base.h:61
void copyCovariance(const double *arr, Eigen::MatrixXd &covariance, const std::string &topicName, const std::vector< int > &updateVector, const size_t offset, const size_t dimension)
Utility method for copying covariances from ROS covariance arrays to Eigen matrices.
bool setPoseSrvCallback(robot_localization::SetPose::Request &request, robot_localization::SetPose::Response &)
Service callback for manually setting/resetting the internal pose estimate.
ros::Publisher accelPub_
optional acceleration publisher
Definition: ros_filter.h:691
ros::Time latestControlTime_
The time of the most recent control input.
Definition: ros_filter.h:486
TF2SIMD_FORCE_INLINE const Vector3 & getRow(int i) const
tf2Scalar length() const
std::vector< int > loadUpdateConfig(const std::string &topicName)
Loads fusion settings from the config file.
std::vector< ros::Subscriber > topicSubs_
Vector to hold our subscribers until they go out of scope.
Definition: ros_filter.h:494
void add(const std::string &name, TaskFunction f)
double minFrequency_
minimal frequency
Definition: ros_filter.h:707
ROSCPP_DECL const std::string & getName()
bool printDiagnostics_
Whether or not we print diagnostic messages to the /diagnostics topic.
Definition: ros_filter.h:570
std::string worldFrameId_
tf frame name that is the parent frame of the transform that this node will calculate and broadcast...
Definition: ros_filter.h:658
int staticDiagErrorLevel_
The max (worst) static diagnostic level.
Definition: ros_filter.h:609
void forceTwoD(Eigen::VectorXd &measurement, Eigen::MatrixXd &measurementCovariance, std::vector< int > &updateVector)
Method for zeroing out 3D variables within measurements.
Definition: ros_filter.cpp:321
void clearExpiredHistory(const double cutoffTime)
Removes measurements and filter states older than the given cutoff time.
ServiceServer advertiseService(const std::string &service, bool(T::*srv_func)(MReq &, MRes &), T *obj)
Type const & getType() const
const int POSITION_V_OFFSET
Definition: filter_common.h:78
ros::ServiceServer setPoseSrv_
Service that allows another node to change the current state and recieve a confirmation. Uses a custom SetPose service.
Definition: ros_filter.h:588
#define ROS_FATAL_COND(cond,...)
std::auto_ptr< diagnostic_updater::HeaderlessTopicDiagnostic > freqDiag_
optional signaling diagnostic frequency
Definition: ros_filter.h:695
int dynamicDiagErrorLevel_
The max (worst) dynamic diagnostic level.
Definition: ros_filter.h:453
void reset()
Resets the filter to its initial state.
Definition: ros_filter.cpp:135
void setIdentity()
ros::Time lastSetPoseTime_
Store the last time set pose message was received.
Definition: ros_filter.h:525
void periodicUpdate(const ros::TimerEvent &event)
callback function which is called for periodic updates
void controlCallback(const geometry_msgs::Twist::ConstPtr &msg)
Callback method for receiving non-stamped control input.
Definition: ros_filter.cpp:267
double getControlTime()
Returns the time at which the control term was issued.
void enqueueMeasurement(const std::string &topicName, const Eigen::VectorXd &measurement, const Eigen::MatrixXd &measurementCovariance, const std::vector< int > &updateVector, const double mahalanobisThresh, const ros::Time &time)
Adds a measurement to the queue of measurements to be processed.
Definition: ros_filter.cpp:300
bool publishTransform_
Whether we publish the transform from the world_frame to the base_link_frame.
Definition: ros_filter.h:662
#define RF_DEBUG(msg)
Quaternion & normalize()
ROSCPP_DECL CallbackQueue * getGlobalCallbackQueue()
void setRPY(tf2Scalar roll, tf2Scalar pitch, tf2Scalar yaw)
const int ORIENTATION_SIZE
Definition: filter_common.h:87
TransportHints & tcpNoDelay(bool nodelay=true)
RosFilter(ros::NodeHandle nh, ros::NodeHandle nh_priv, std::vector< double > args=std::vector< double >())
Constructor.
Definition: ros_filter.cpp:49
std::map< std::string, tf2::Transform > previousMeasurements_
Stores the last measurement from a given topic for differential integration.
Definition: ros_filter.h:557
double getLastMeasurementTime()
Gets the most recent measurement time.
std::string baseLinkOutputFrameId_
tf frame name for the robot&#39;s body frame
Definition: ros_filter.h:427
MeasurementQueue measurementQueue_
We process measurements by queueing them up in callbacks and processing them all at once within each ...
Definition: ros_filter.h:535
boost::shared_ptr< Measurement > MeasurementPtr
Definition: filter_base.h:110
bool disabledAtStartup_
Start the Filter disabled at startup.
Definition: ros_filter.h:640
void imuCallback(const sensor_msgs::Imu::ConstPtr &msg, const std::string &topicName, const CallbackData &poseCallbackData, const CallbackData &twistCallbackData, const CallbackData &accelCallbackData)
Callback method for receiving all IMU messages.
Definition: ros_filter.cpp:445
ros::Duration tfTimeOffset_
For future (or past) dating the world_frame->base_link_frame transform.
Definition: ros_filter.h:621
#define ROS_FATAL_STREAM(args)
Duration & fromSec(double t)
const int POSITION_OFFSET
Definition: filter_common.h:76
TF2SIMD_FORCE_INLINE Vector3 & getOrigin()
ros::Publisher positionPub_
position publisher
Definition: ros_filter.h:687
ros::ServiceServer enableFilterSrv_
Service that allows another node to enable the filter. Uses a standard Empty service.
Definition: ros_filter.h:596
std::map< std::string, std::string > staticDiagnostics_
This object accumulates static diagnostics, e.g., diagnostics relating to the configuration parameter...
Definition: ros_filter.h:438
#define ROS_WARN_ONCE(...)
#define ROS_INFO(...)
void poseCallback(const geometry_msgs::PoseWithCovarianceStamped::ConstPtr &msg, const CallbackData &callbackData, const std::string &targetFrame, const bool imuData)
Callback method for receiving all pose messages.
static void init()
bool smoothLaggedData_
Whether or not we use smoothing.
Definition: ros_filter.h:592
void addDiagnostic(const int errLevel, const std::string &topicAndClass, const std::string &message, const bool staticDiag)
Adds a diagnostic message to the accumulating map and updates the error level.
bool searchParam(const std::string &key, std::string &result) const
bool param(const std::string &param_name, T &param_val, const T &default_val) const
ros::Time stamp_
void fromMsg(const A &, B &b)
ROSCPP_DECL bool ok()
bool toggledOn_
Whether the filter should process new measurements or not.
Definition: ros_filter.h:646
Timer createTimer(Rate r, Handler h, Obj o, bool oneshot=false, bool autostart=true) const
static const Transform & getIdentity()
bool enableFilterSrvCallback(std_srvs::Empty::Request &, std_srvs::Empty::Response &)
Service callback for manually enable the filter.
geometry_msgs::TransformStamped worldBaseLinkTransMsg_
Message that contains our latest transform (i.e., state)
Definition: ros_filter.h:654
std::string getService() const
double gravitationalAcc_
What is the acceleration in Z due to gravity (m/s^2)? Default is +9.80665.
Definition: ros_filter.h:578
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
const int ACCELERATION_SIZE
Definition: filter_common.h:88
const int POSITION_SIZE
Definition: filter_common.h:86
TF2SIMD_FORCE_INLINE void setOrigin(const Vector3 &origin)
std::string mapFrameId_
tf frame name for the robot&#39;s map (world-fixed) frame
Definition: ros_filter.h:529
#define ROS_WARN_STREAM(args)
void sendTransform(const geometry_msgs::TransformStamped &transform)
TF2SIMD_FORCE_INLINE void mult(const Transform &t1, const Transform &t2)
bool prepareAcceleration(const sensor_msgs::Imu::ConstPtr &msg, const std::string &topicName, const std::string &targetFrame, std::vector< int > &updateVector, Eigen::VectorXd &measurement, Eigen::MatrixXd &measurementCovariance)
Prepares an IMU message&#39;s linear acceleration for integration into the filter.
double clampRotation(double rotation)
Utility method keeping RPY angles in the range [-pi, pi].
OK
#define ROS_WARN_STREAM_DELAYED_THROTTLE(period, args)
void saveFilterState(FilterBase &filter)
Saves the current filter state in the queue of previous filter states.
void setRotation(const Quaternion &q)
const Eigen::VectorXd & getControl()
Returns the control vector currently being used.
B toMsg(const A &a)
std::string frame_id_
Transform inverseTimes(const Transform &t) const
#define ROS_INFO_STREAM_ONCE(args)
void initialize()
Initialize filter.
Definition: ros_filter.cpp:100
bool toggleFilterProcessingCallback(robot_localization::ToggleFilterProcessing::Request &, robot_localization::ToggleFilterProcessing::Response &)
Service callback to toggle processing measurements for a standby mode but continuing to publish...
Definition: ros_filter.cpp:165
#define ROS_INFO_STREAM(args)
bool getFilteredOdometryMessage(nav_msgs::Odometry &message)
Retrieves the EKF&#39;s output for broadcasting.
Definition: ros_filter.cpp:351
std::map< std::string, tf2::Transform > initialMeasurements_
Stores the first measurement from each topic for relative measurements.
Definition: ros_filter.h:506
double historyLength_
The depth of the history we track for smoothing/delayed measurement processing.
Definition: ros_filter.h:471
const int POSE_SIZE
Pose and twist messages each contain six variables.
Definition: filter_common.h:84
const int ORIENTATION_OFFSET
Definition: filter_common.h:77
bool getParam(const std::string &key, std::string &s) const
bool predictToCurrentTime_
By default, the filter predicts and corrects up to the time of the latest measurement. If this is set to true, the filter does the same, but then also predicts up to the current time step.
Definition: ros_filter.h:566
Matrix3x3 inverse() const
std::map< std::string, std::string > dynamicDiagnostics_
This object accumulates dynamic diagnostics, e.g., diagnostics relating to sensor data...
Definition: ros_filter.h:445
tf2_ros::Buffer tfBuffer_
Transform buffer for managing coordinate transforms.
Definition: ros_filter.h:613
static Time now()
const Eigen::MatrixXd & getEstimateErrorCovariance()
Gets the estimate error covariance.
#define ROS_ERROR_STREAM_DELAYED_THROTTLE(period, args)
bool lookupTransformSafe(const tf2_ros::Buffer &buffer, const std::string &targetFrame, const std::string &sourceFrame, const ros::Time &time, const ros::Duration &timeout, tf2::Transform &targetFrameTrans, const bool silent=false)
Method for safely obtaining transforms.
diagnostic_updater::Updater diagnosticUpdater_
Used for updating the diagnostics.
Definition: ros_filter.h:457
Structure used for storing and comparing filter states.
Definition: filter_base.h:118
bool preparePose(const geometry_msgs::PoseWithCovarianceStamped::ConstPtr &msg, const std::string &topicName, const std::string &targetFrame, const bool differential, const bool relative, const bool imuData, std::vector< int > &updateVector, Eigen::VectorXd &measurement, Eigen::MatrixXd &measurementCovariance)
Prepares a pose message for integration into the filter.
bool twoDMode_
Whether or not we&#39;re in 2D mode.
Definition: ros_filter.h:629
void add(const std::string &key, const T &val)
bool prepareTwist(const geometry_msgs::TwistWithCovarianceStamped::ConstPtr &msg, const std::string &topicName, const std::string &targetFrame, std::vector< int > &updateVector, Eigen::VectorXd &measurement, Eigen::MatrixXd &measurementCovariance)
Prepares a twist message for integration into the filter.
void clearMeasurementQueue()
Clears measurement queue.
std::string odomFrameId_
tf frame name for the robot&#39;s odometry (world-fixed) frame
Definition: ros_filter.h:547
bool hasParam(const std::string &key) const
#define ROS_ERROR_STREAM(args)
double frequency_
The frequency of the run loop.
Definition: ros_filter.h:465
#define ROS_ASSERT(cond)
std::ofstream debugStream_
Used for outputting debug messages.
Definition: ros_filter.h:449
double maxFrequency_
maximal frequency
Definition: ros_filter.h:710
std::vector< std::string > stateVariableNames_
Contains the state vector variable names in string format.
Definition: ros_filter.h:605
ros::NodeHandle nh_
Node handle.
Definition: ros_filter.h:539
void quatToRPY(const tf2::Quaternion &quat, double &roll, double &pitch, double &yaw)
Utility method for converting quaternion to RPY.
T filter_
Our filter (EKF, UKF, etc.)
Definition: ros_filter.h:461
void setData(const T &input)
bool revertTo(const double time)
Finds the latest filter state before the given timestamp and makes it the current state again...
static bool isSimTime()
FilterStateHistoryDeque filterStateHistory_
An implicitly time ordered queue of past filter states used for smoothing.
Definition: ros_filter.h:672
StateMembers
Enumeration that defines the state vector.
Definition: filter_common.h:41
const int ORIENTATION_V_OFFSET
Definition: filter_common.h:79
std::map< std::string, bool > removeGravitationalAcc_
If including acceleration for each IMU input, whether or not we remove acceleration due to gravity...
Definition: ros_filter.h:574


robot_localization
Author(s): Tom Moore
autogenerated on Sun Feb 17 2019 03:15:37