Carma-platform v4.2.0
CARMA Platform is built on robot operating system (ROS) and utilizes open source software (OSS) that enables Cooperative Driving Automation (CDA) features to allow Automated Driving Systems to interact and cooperate with infrastructure and other vehicles through communication.
LocalizationManager.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2022 LEIDOS.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17#include <tf2_geometry_msgs/tf2_geometry_msgs.h>
18#include <algorithm>
20
22{
23 // Initialize static values
24 const std::unordered_set<std::string> LocalizationManager::LIDAR_FAILURE_STRINGS({"One LIDAR Failed", "Both LIDARS Failed"});
25
27 StatePublisher state_pub, ManagedInitialPosePublisher initialpose_pub,
28 const LocalizationManagerConfig &config,
29 rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr logger,
30 std::unique_ptr<carma_ros2_utils::timers::TimerFactory> timer_factory)
31 : pose_pub_(pose_pub), state_pub_(state_pub), initialpose_pub_(initialpose_pub), config_(config), logger_(logger), timer_factory_(std::move(timer_factory)), transition_table_(static_cast<LocalizerMode>(config_.localization_mode))
32
33 {
34
36 std::placeholders::_1, std::placeholders::_2,
37 std::placeholders::_3));
38 timer_clock_type_ = timer_factory_->now().get_clock_type();
39 }
40
41 double LocalizationManager::computeFreq(const rclcpp::Time &old_stamp, const rclcpp::Time &new_stamp) const
42 {
43 return 1.0 / (new_stamp - old_stamp).seconds(); // Convert delta to frequency (Hz = 1/s)
44 }
45
47 {
48 config_ = config;
49 }
50
51 double LocalizationManager::computeNDTFreq(const rclcpp::Time &new_stamp)
52 {
53 if (!prev_ndt_stamp_)
54 { // Check if this is the first data point
55 prev_ndt_stamp_ = new_stamp;
56 // When no historic data is available force the frequency into the operational range
58 }
59
60 if (new_stamp <= rclcpp::Time(prev_ndt_stamp_.get(), timer_clock_type_))
61 {
62 RCLCPP_ERROR_STREAM(rclcpp::get_logger("localization_manager"), "LocalizationManager received NDT data out of order. Prev stamp was "
63 << prev_ndt_stamp_.get().seconds() << " new stamp is " << new_stamp.seconds());
64 // When invalid data is received from NDT force the frequency into the fault range
66 }
67 return computeFreq(rclcpp::Time(prev_ndt_stamp_.get(), timer_clock_type_), new_stamp); // Convert delta to frequency (Hz = 1/s)
68 }
69
70 void LocalizationManager::poseAndStatsCallback(const geometry_msgs::msg::PoseStamped::ConstPtr pose,
71 const autoware_msgs::msg::NDTStat::ConstPtr stats)
72 {
73 double ndt_freq = computeNDTFreq(rclcpp::Time(pose->header.stamp, timer_clock_type_));
74 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("localization_manager"), "Received pose resulting in frequency value of " << ndt_freq << " with score of " << stats->score);
75
77 {
79 }
81 {
83 }
84 else
85 {
86 // In GNSS_WITH_NDT_INIT mode, we shouldn't switch back to NDT and only rely on GPS
90 {
92 }
93 }
94
96
98 {
99 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("localization_manager"), "lidar_init_sequential_timesteps_counter_: " << lidar_init_sequential_timesteps_counter_);
101 {
102 if (is_sequential_)
104
105 is_sequential_ = true;
106 }
107 else
108 {
110 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("localization_manager"), "Switched to LIDAR_INITIALIZED_SWITCH_TO_GPS at lidar_init_sequential_timesteps_counter_: " << lidar_init_sequential_timesteps_counter_
111 << ", with new state: " << transition_table_.getState());
112 }
113 }
117 lidar_init_sequential_timesteps_counter_ >= config_.sequential_timesteps_until_gps_operation)) // don't reset counter if Lidar initialized and switched to GPS
118 // as it will reset state to OPERATIONAL
119 {
120 is_sequential_ = false;
122 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("localization_manager"), "Resetting lidar_init_sequential_timesteps_counter_: " << lidar_init_sequential_timesteps_counter_ << ", with new state: " << transition_table_.getState());
123 }
124
126 {
127 tf2::Vector3 ndt_translation(pose->pose.position.x, pose->pose.position.y, pose->pose.position.z);
128
129 tf2::Vector3 gnss_translation(last_raw_gnss_value_->pose.position.x, last_raw_gnss_value_->pose.position.y, last_raw_gnss_value_->pose.position.z);
130
131 gnss_offset_ = ndt_translation - gnss_translation;
132 }
133
135 {
136 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("localization_manager"), "Publishing mixed pose with lidar_init_sequential_timesteps_counter_: " << lidar_init_sequential_timesteps_counter_ << ", and state" << state);
137 current_pose_ = *pose;
138 }
139
140 prev_ndt_stamp_ = pose->header.stamp;
141 }
142
143 void LocalizationManager::gnssPoseCallback(const geometry_msgs::msg::PoseStamped::SharedPtr msg)
144 {
146 // Just like ndt_matching the gnss pose is treated as an initialize signal if the system is not yet intialized
148 {
151
152 geometry_msgs::msg::PoseWithCovarianceStamped new_initial_pose;
153 new_initial_pose.header = msg->header;
154 new_initial_pose.pose.pose = msg->pose;
155
156 initialpose_pub_(new_initial_pose);
157 }
158
160 {
161 geometry_msgs::msg::PoseStamped corrected_pose = *msg;
162 if (gnss_offset_)
163 {
164 corrected_pose.pose.position.x = corrected_pose.pose.position.x + gnss_offset_->x();
165 corrected_pose.pose.position.y = corrected_pose.pose.position.y + gnss_offset_->y();
166 corrected_pose.pose.position.z = corrected_pose.pose.position.z + gnss_offset_->z();
167 }
168 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("localization_manager"), "Publishing GNSS pose with lidar_init_sequential_timesteps_counter_: " << lidar_init_sequential_timesteps_counter_);
169 current_pose_ = corrected_pose;
170 }
171 }
172
173 void LocalizationManager::initialPoseCallback(const geometry_msgs::msg::PoseWithCovarianceStamped::SharedPtr msg)
174 {
177 current_pose_ = boost::none; // Reset the current pose until a new pose is recieved
178 initialpose_pub_(*msg); // Forward the initial pose to the rest of the system
179 }
180
181 void LocalizationManager::systemAlertCallback(const carma_msgs::msg::SystemAlert::SharedPtr alert)
182 {
183 if (LIDAR_FAILURE_STRINGS.find(alert->description) != LIDAR_FAILURE_STRINGS.end())
184 {
186 }
187 }
188
190 {
191 // If there is already a timer callback in progress or the expected state has changed then return
192 if (origin_state != transition_table_.getState())
193 {
194 return;
195 }
196
198 }
199
201 {
202 next_id_++;
203 return next_id_;
204 }
205
207 {
208 std::lock_guard<std::mutex> guard(mutex_);
209
210 // Clear expired timers
211 auto it = timers_.begin();
212 while (it != timers_.end())
213 {
214 // Check if timer is marked for deletion
215 if (it->second.second)
216 {
217 // erase() function returns the iterator of the next to last deleted element
218 it = timers_.erase(it);
219 }
220 else
221 {
222 it++;
223 }
224 }
225 }
226
228 LocalizationSignal signal)
229 {
230 RCLCPP_INFO_STREAM(rclcpp::get_logger("localization_manager"), "State transition from " << prev_state << " to " << new_state << " with signal " << signal);
231 // Mark the expired timers as expired if any
232 if (current_timer_)
233 {
234 // NOTE unit testing of current algorithm depends on all timers being one-shot timers.
235 timers_[current_timer_id_].second = true;
236 }
237
238 switch (new_state)
239 {
241 gnss_offset_ = boost::none;
242 prev_ndt_stamp_ = boost::none;
243
246 std::bind(&LocalizationManager::timerCallback, this, new_state), true, true);
247
248 timers_[current_timer_id_] = std::make_pair(std::move(current_timer_), false); // Add start timer to map by Id
249 break;
253 std::bind(&LocalizationManager::timerCallback, this, new_state), true, true);
254
255 timers_[current_timer_id_] = std::make_pair(std::move(current_timer_), false); // Add start timer to map by Id
256
257 break;
258 default:
259 break;
260 }
261 }
262
264 {
265 // Clear any expired timers
266 clearTimers();
267
268 // Evaluate NDT Frequency if we have started receiving messages
269 // This check provides protection against excessively long NDT computation times that do not trigger the callback
270 if (prev_ndt_stamp_)
271 {
272 double freq = computeFreq(rclcpp::Time(prev_ndt_stamp_.get(), timer_clock_type_), timer_factory_->now());
274 {
276 }
278 {
280 }
281 }
282
283 // check if last gnss time stamp is older than threshold and send the corresponding signal
284 if (last_raw_gnss_value_ && timer_factory_->now() - rclcpp::Time(last_raw_gnss_value_->header.stamp, timer_clock_type_) > rclcpp::Duration(config_.gnss_data_timeout * 1e6))
285 {
287 }
288
289 // Used in LocalizerMode::GNSS_WITH_NDT_INIT
290 // If the state is not Operational with good NDT, or already using GPS only, we reset the counter
293 {
295 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("localization_manager"), "Resetting lidar_init_sequential_timesteps_counter_: " << lidar_init_sequential_timesteps_counter_ << ", with new state: " << transition_table_.getState());
296 }
297
298 // Publish current pose message if available
299 if (current_pose_)
300 {
301 auto pose_to_publish = *current_pose_;
303 {
304 pose_to_publish.pose.position.x += config_.x_offset;
305 pose_to_publish.pose.position.y += config_.y_offset;
306 pose_to_publish.pose.position.z += config_.z_offset;
307 }
308 pose_pub_(pose_to_publish);
309 }
310
311 // Create and publish status report message
312 carma_localization_msgs::msg::LocalizationStatusReport msg = stateToMsg(transition_table_.getState(), timer_factory_->now());
313 state_pub_(msg);
314 }
315
317 {
319 }
320
321}
void poseAndStatsCallback(const geometry_msgs::msg::PoseStamped::ConstPtr pose, const autoware_msgs::msg::NDTStat::ConstPtr stats)
Synced callback for the NDT reported pose and status messages.
void posePubTick()
Timer callback that controls the publication of the selected pose and status report.
void timerCallback(const LocalizationState origin_state)
Callback for timeouts. Used to trigger timeout signals for the state machine.
void gnssPoseCallback(const geometry_msgs::msg::PoseStamped::SharedPtr msg)
Callback for new GNSS messages.
uint32_t nextId()
Generates the next id to be used for a timer.
void stateTransitionCallback(LocalizationState prev_state, LocalizationState new_state, LocalizationSignal signal)
Callback for when a new state was triggered. Allows creation of entry/exit behavior for states.
void initialPoseCallback(const geometry_msgs::msg::PoseWithCovarianceStamped::SharedPtr msg)
Callback for the initial pose provided by Rviz or some external localization initializer.
boost::optional< geometry_msgs::msg::PoseStamped > last_raw_gnss_value_
std::unique_ptr< carma_ros2_utils::timers::TimerFactory > timer_factory_
double computeFreq(const rclcpp::Time &old_stamp, const rclcpp::Time &new_stamp) const
Helper function to compute the instantaneous frequency between two times.
std::function< void(const carma_localization_msgs::msg::LocalizationStatusReport &)> StatePublisher
void clearTimers()
Clears the expired timers from the memory of this scheduler.
static const std::unordered_set< std::string > LIDAR_FAILURE_STRINGS
The set of strings which mark a lidar failure in a system alert message.
void systemAlertCallback(const carma_msgs::msg::SystemAlert::SharedPtr alert)
Callback for SystemAlert data. Used to check for lidar failure.
boost::optional< tf2::Vector3 > gnss_offset_
LocalizationState getState() const
Returns the current localization state.
boost::optional< rclcpp::Time > prev_ndt_stamp_
boost::optional< geometry_msgs::msg::PoseStamped > current_pose_
std::function< void(const geometry_msgs::msg::PoseStamped &)> PosePublisher
LocalizationManager(PosePublisher pose_pub, StatePublisher state_pub, ManagedInitialPosePublisher initialpose_pub, const LocalizationManagerConfig &config, rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr logger, std::unique_ptr< carma_ros2_utils::timers::TimerFactory > timer_factory)
Constructor.
double computeNDTFreq(const rclcpp::Time &new_stamp)
Helper function to both compute the NDT Frequency and update the previous pose timestamp.
void setConfig(const LocalizationManagerConfig &config)
Set config.
std::unordered_map< uint32_t, std::pair< TimerUniquePtr, bool > > timers_
std::function< void(const geometry_msgs::msg::PoseWithCovarianceStamped &)> ManagedInitialPosePublisher
void signal(LocalizationSignal signal)
Trigger signal for the transition table.
LocalizationState getState() const
Returns the current state.
void setTransitionCallback(TransitionCallback cb)
Callback setting function. The provided callback will be triggered any time the current state changes...
carma_localization_msgs::msg::LocalizationStatusReport stateToMsg(LocalizationState state, const rclcpp::Time &stamp)
Helper function to convert LocalizationState objects into LocalizationStatusReport ROS messages.
LocalizationState
Enum describing the possible states of the localization system.
LocalizerMode
Enum describing the possible operational modes of the LocalizationManager.
LocalizationSignal
Enum describing the possible signals to change the current LocalizationState.
Struct to store the configuration settings for the LocalizationManager class.
int gnss_only_operation_timeout
Timeout in ms for GNSS only operation. Ignored when in GNSS mode.
double fitness_score_degraded_threshold
NDT Fitness score above which the localization is considered in a degraded state.
double x_offset
GPS Offset to apply if that mode is enabled.
int gnss_data_timeout
GNSS Data timeout. If exceeded the system will assume the GNSS is no longer functional....
double ndt_frequency_degraded_threshold
NDT solution frequency below which the localization is considered in a degraded state.