18 #include "nav2_ros_common/node_utils.hpp"
19 #include "opennav_docking/simple_charging_dock.hpp"
20 #include "opennav_docking/utils.hpp"
22 using namespace std::chrono_literals;
24 namespace opennav_docking
27 void SimpleChargingDock::configure(
28 const nav2::LifecycleNode::WeakPtr & parent,
29 const std::string & name, std::shared_ptr<tf2_ros::Buffer> tf)
34 node_ = parent.lock();
36 throw std::runtime_error{
"Failed to lock node"};
40 nav2::declare_parameter_if_not_declared(
41 node_, name +
".use_battery_status", rclcpp::ParameterValue(
true));
44 nav2::declare_parameter_if_not_declared(
45 node_, name +
".detector_service_name", rclcpp::ParameterValue(
""));
46 nav2::declare_parameter_if_not_declared(
47 node_, name +
".detector_service_timeout", rclcpp::ParameterValue(5.0));
48 nav2::declare_parameter_if_not_declared(
49 node_, name +
".subscribe_toggle", rclcpp::ParameterValue(
false));
52 nav2::declare_parameter_if_not_declared(
53 node_, name +
".use_external_detection_pose", rclcpp::ParameterValue(
false));
54 nav2::declare_parameter_if_not_declared(
55 node_, name +
".external_detection_timeout", rclcpp::ParameterValue(1.0));
56 nav2::declare_parameter_if_not_declared(
57 node_, name +
".external_detection_translation_x", rclcpp::ParameterValue(-0.20));
58 nav2::declare_parameter_if_not_declared(
59 node_, name +
".external_detection_translation_y", rclcpp::ParameterValue(0.0));
60 nav2::declare_parameter_if_not_declared(
61 node_, name +
".external_detection_rotation_yaw", rclcpp::ParameterValue(0.0));
62 nav2::declare_parameter_if_not_declared(
63 node_, name +
".external_detection_rotation_pitch", rclcpp::ParameterValue(1.57));
64 nav2::declare_parameter_if_not_declared(
65 node_, name +
".external_detection_rotation_roll", rclcpp::ParameterValue(-1.57));
66 nav2::declare_parameter_if_not_declared(
67 node_, name +
".filter_coef", rclcpp::ParameterValue(0.1));
70 nav2::declare_parameter_if_not_declared(
71 node_, name +
".charging_threshold", rclcpp::ParameterValue(0.5));
74 nav2::declare_parameter_if_not_declared(
75 node_, name +
".use_stall_detection", rclcpp::ParameterValue(
false));
76 nav2::declare_parameter_if_not_declared(
77 node_, name +
".stall_joint_names", rclcpp::PARAMETER_STRING_ARRAY);
78 nav2::declare_parameter_if_not_declared(
79 node_, name +
".stall_velocity_threshold", rclcpp::ParameterValue(1.0));
80 nav2::declare_parameter_if_not_declared(
81 node_, name +
".stall_effort_threshold", rclcpp::ParameterValue(1.0));
84 nav2::declare_parameter_if_not_declared(
85 node_, name +
".docking_threshold", rclcpp::ParameterValue(0.05));
88 nav2::declare_parameter_if_not_declared(
89 node_, name +
".staging_x_offset", rclcpp::ParameterValue(-0.7));
90 nav2::declare_parameter_if_not_declared(
91 node_, name +
".staging_yaw_offset", rclcpp::ParameterValue(0.0));
94 nav2::declare_parameter_if_not_declared(
95 node_, name +
".dock_direction", rclcpp::ParameterValue(std::string(
"forward")));
96 nav2::declare_parameter_if_not_declared(
97 node_, name +
".rotate_to_dock", rclcpp::ParameterValue(
false));
99 node_->get_parameter(name +
".use_battery_status", use_battery_status_);
100 node_->get_parameter(name +
".use_external_detection_pose", use_external_detection_pose_);
101 node_->get_parameter(name +
".external_detection_timeout", external_detection_timeout_);
102 node_->get_parameter(
103 name +
".external_detection_translation_x", external_detection_translation_x_);
104 node_->get_parameter(
105 name +
".external_detection_translation_y", external_detection_translation_y_);
106 double yaw, pitch, roll;
107 node_->get_parameter(name +
".external_detection_rotation_yaw", yaw);
108 node_->get_parameter(name +
".external_detection_rotation_pitch", pitch);
109 node_->get_parameter(name +
".external_detection_rotation_roll", roll);
110 external_detection_rotation_.setEuler(pitch, roll, yaw);
111 node_->get_parameter(name +
".charging_threshold", charging_threshold_);
112 node_->get_parameter(name +
".stall_velocity_threshold", stall_velocity_threshold_);
113 node_->get_parameter(name +
".stall_effort_threshold", stall_effort_threshold_);
114 node_->get_parameter(name +
".docking_threshold", docking_threshold_);
115 node_->get_parameter(
"base_frame", base_frame_id_);
116 node_->get_parameter(name +
".staging_x_offset", staging_x_offset_);
117 node_->get_parameter(name +
".staging_yaw_offset", staging_yaw_offset_);
119 node_->get_parameter(name +
".detector_service_name", detector_service_name_);
120 node_->get_parameter(name +
".detector_service_timeout", detector_service_timeout_);
121 node_->get_parameter(name +
".subscribe_toggle", subscribe_toggle_);
124 detection_active_ =
false;
125 initial_pose_received_ =
false;
128 if (use_external_detection_pose_ && !subscribe_toggle_) {
129 dock_pose_.header.stamp = rclcpp::Time(0);
130 dock_pose_sub_ = node_->create_subscription<geometry_msgs::msg::PoseStamped>(
131 "detected_dock_pose",
132 [
this](
const geometry_msgs::msg::PoseStamped::SharedPtr pose) {
133 detected_dock_pose_ = *pose;
134 initial_pose_received_ =
true;
139 std::string dock_direction;
140 node_->get_parameter(name +
".dock_direction", dock_direction);
141 dock_direction_ = utils::getDockDirectionFromString(dock_direction);
142 if (dock_direction_ == opennav_docking_core::DockDirection::UNKNOWN) {
143 throw std::runtime_error{
"Dock direction is not valid. Valid options are: forward or backward"};
146 node_->get_parameter(name +
".rotate_to_dock", rotate_to_dock_);
147 if (rotate_to_dock_ && dock_direction_ != opennav_docking_core::DockDirection::BACKWARD) {
148 throw std::runtime_error{
"Parameter rotate_to_dock is enabled but dock direction is not "
149 "backward. Please set dock direction to backward."};
154 node_->get_parameter(name +
".filter_coef", filter_coef);
155 filter_ = std::make_unique<PoseFilter>(filter_coef, external_detection_timeout_);
157 if (!detector_service_name_.empty()) {
158 detector_client_ = node_->create_client<std_srvs::srv::Trigger>(
159 detector_service_name_,
false);
162 if (use_battery_status_) {
163 battery_sub_ = node_->create_subscription<sensor_msgs::msg::BatteryState>(
165 [
this](
const sensor_msgs::msg::BatteryState::SharedPtr state) {
166 is_charging_ = state->current > charging_threshold_;
170 bool use_stall_detection;
171 node_->get_parameter(name +
".use_stall_detection", use_stall_detection);
172 if (use_stall_detection) {
174 node_->get_parameter(name +
".stall_joint_names", stall_joint_names_);
175 if (stall_joint_names_.size() < 1) {
176 RCLCPP_ERROR(node_->get_logger(),
"stall_joint_names cannot be empty!");
178 joint_state_sub_ = node_->create_subscription<sensor_msgs::msg::JointState>(
180 std::bind(&SimpleChargingDock::jointStateCallback,
this, std::placeholders::_1),
184 dock_pose_pub_ = node_->create_publisher<geometry_msgs::msg::PoseStamped>(
"dock_pose");
185 filtered_dock_pose_pub_ = node_->create_publisher<geometry_msgs::msg::PoseStamped>(
186 "filtered_dock_pose");
187 staging_pose_pub_ = node_->create_publisher<geometry_msgs::msg::PoseStamped>(
"staging_pose");
190 geometry_msgs::msg::PoseStamped SimpleChargingDock::getStagingPose(
191 const geometry_msgs::msg::Pose & pose,
const std::string & frame)
194 if (!use_external_detection_pose_) {
197 dock_pose_.header.frame_id = frame;
198 dock_pose_.pose = pose;
202 const double yaw = tf2::getYaw(pose.orientation);
203 geometry_msgs::msg::PoseStamped staging_pose;
204 staging_pose.header.frame_id = frame;
205 staging_pose.header.stamp = node_->now();
206 staging_pose.pose = pose;
207 staging_pose.pose.position.x += cos(yaw) * staging_x_offset_;
208 staging_pose.pose.position.y += sin(yaw) * staging_x_offset_;
209 tf2::Quaternion orientation;
210 orientation.setEuler(0.0, 0.0, yaw + staging_yaw_offset_);
211 staging_pose.pose.orientation = tf2::toMsg(orientation);
214 staging_pose_pub_->publish(staging_pose);
218 bool SimpleChargingDock::getRefinedPose(geometry_msgs::msg::PoseStamped & pose, std::string )
221 if (!use_external_detection_pose_) {
222 dock_pose_pub_->publish(pose);
228 if (!initial_pose_received_) {
229 RCLCPP_WARN(node_->get_logger(),
"Waiting for first detected_dock_pose; none received yet");
234 geometry_msgs::msg::PoseStamped detected = detected_dock_pose_;
237 auto timeout = rclcpp::Duration::from_seconds(external_detection_timeout_);
238 if (node_->now() - detected.header.stamp > timeout) {
239 RCLCPP_WARN(node_->get_logger(),
"Lost detection or did not detect: timeout exceeded");
246 if (detected.header.frame_id != pose.header.frame_id) {
248 if (!tf2_buffer_->canTransform(
249 pose.header.frame_id, detected.header.frame_id,
250 detected.header.stamp, rclcpp::Duration::from_seconds(0.2)))
252 RCLCPP_WARN(node_->get_logger(),
"Failed to transform detected dock pose");
255 tf2_buffer_->transform(detected, detected, pose.header.frame_id);
256 }
catch (
const tf2::TransformException & ex) {
257 RCLCPP_WARN(node_->get_logger(),
"Failed to transform detected dock pose");
263 detected = filter_->update(detected);
264 filtered_dock_pose_pub_->publish(detected);
267 geometry_msgs::msg::PoseStamped just_orientation;
268 just_orientation.pose.orientation = tf2::toMsg(external_detection_rotation_);
269 geometry_msgs::msg::TransformStamped transform;
270 transform.transform.rotation = detected.pose.orientation;
271 tf2::doTransform(just_orientation, just_orientation, transform);
273 tf2::Quaternion orientation;
274 orientation.setEuler(0.0, 0.0, tf2::getYaw(just_orientation.pose.orientation));
275 dock_pose_.pose.orientation = tf2::toMsg(orientation);
278 dock_pose_.header = detected.header;
279 dock_pose_.pose.position = detected.pose.position;
280 const double yaw = tf2::getYaw(dock_pose_.pose.orientation);
281 dock_pose_.pose.position.x += cos(yaw) * external_detection_translation_x_ -
282 sin(yaw) * external_detection_translation_y_;
283 dock_pose_.pose.position.y += sin(yaw) * external_detection_translation_x_ +
284 cos(yaw) * external_detection_translation_y_;
285 dock_pose_.pose.position.z = 0.0;
288 dock_pose_pub_->publish(dock_pose_);
293 bool SimpleChargingDock::isDocked()
295 if (joint_state_sub_) {
300 if (dock_pose_.header.frame_id.empty()) {
306 geometry_msgs::msg::PoseStamped base_pose;
307 base_pose.header.stamp = rclcpp::Time(0);
308 base_pose.header.frame_id = base_frame_id_;
309 base_pose.pose.orientation.w = 1.0;
311 tf2_buffer_->transform(base_pose, base_pose, dock_pose_.header.frame_id);
312 }
catch (
const tf2::TransformException & ex) {
317 double d = std::hypot(
318 base_pose.pose.position.x - dock_pose_.pose.position.x,
319 base_pose.pose.position.y - dock_pose_.pose.position.y);
320 return d < docking_threshold_;
323 bool SimpleChargingDock::isCharging()
325 return use_battery_status_ ? is_charging_ : isDocked();
328 bool SimpleChargingDock::disableCharging()
333 bool SimpleChargingDock::hasStoppedCharging()
335 return !isCharging();
338 void SimpleChargingDock::jointStateCallback(
const sensor_msgs::msg::JointState::SharedPtr state)
340 double velocity = 0.0;
342 for (
size_t i = 0; i < state->name.size(); ++i) {
343 for (
auto & name : stall_joint_names_) {
344 if (state->name[i] == name) {
346 velocity += abs(state->velocity[i]);
347 effort += abs(state->effort[i]);
353 effort /= stall_joint_names_.size();
354 velocity /= stall_joint_names_.size();
356 is_stalled_ = (velocity < stall_velocity_threshold_) && (effort > stall_effort_threshold_);
359 bool SimpleChargingDock::startDetectionProcess()
362 if (detection_active_) {
367 if (detector_client_) {
368 auto req = std::make_shared<std_srvs::srv::Trigger::Request>();
370 auto future = detector_client_->invoke(
372 std::chrono::duration_cast<std::chrono::nanoseconds>(
373 std::chrono::duration<double>(detector_service_timeout_)));
375 if (!future || !future->success) {
377 node_->get_logger(),
"Detector service '%s' failed to start.",
378 detector_service_name_.c_str());
381 }
catch (
const std::exception & e) {
383 node_->get_logger(),
"Calling detector service '%s' failed: %s",
384 detector_service_name_.c_str(), e.what());
391 if (subscribe_toggle_ && !dock_pose_sub_) {
392 dock_pose_sub_ = node_->create_subscription<geometry_msgs::msg::PoseStamped>(
393 "detected_dock_pose",
394 [
this](
const geometry_msgs::msg::PoseStamped::SharedPtr pose) {
395 detected_dock_pose_ = *pose;
396 initial_pose_received_ =
true;
401 detection_active_ =
true;
402 RCLCPP_INFO(node_->get_logger(),
"External detector activation requested.");
406 bool SimpleChargingDock::stopDetectionProcess()
409 if (!detection_active_) {
414 if (detector_client_) {
415 auto req = std::make_shared<std_srvs::srv::Trigger::Request>();
417 auto future = detector_client_->invoke(
419 std::chrono::duration_cast<std::chrono::nanoseconds>(
420 std::chrono::duration<double>(detector_service_timeout_)));
422 if (!future || !future->success) {
424 node_->get_logger(),
"Detector service '%s' failed to stop.",
425 detector_service_name_.c_str());
428 }
catch (
const std::exception & e) {
430 node_->get_logger(),
"Calling detector service '%s' failed: %s",
431 detector_service_name_.c_str(), e.what());
438 if (subscribe_toggle_ && dock_pose_sub_) {
439 dock_pose_sub_.reset();
442 detection_active_ =
false;
443 initial_pose_received_ =
false;
444 RCLCPP_INFO(node_->get_logger(),
"External detector deactivation requested.");
448 void SimpleChargingDock::activate()
450 dock_pose_pub_->on_activate();
451 filtered_dock_pose_pub_->on_activate();
452 staging_pose_pub_->on_activate();
455 void SimpleChargingDock::deactivate()
457 stopDetectionProcess();
458 dock_pose_pub_->on_deactivate();
459 filtered_dock_pose_pub_->on_deactivate();
460 staging_pose_pub_->on_deactivate();
461 RCLCPP_DEBUG(node_->get_logger(),
"SimpleChargingDock deactivated");
464 void SimpleChargingDock::cleanup()
466 detector_client_.reset();
467 dock_pose_sub_.reset();
468 detection_active_ =
false;
469 initial_pose_received_ =
false;
470 RCLCPP_DEBUG(node_->get_logger(),
"SimpleChargingDock cleaned up");
475 #include "pluginlib/class_list_macros.hpp"
A QoS profile for standard reliable topics with a history of 10 messages.
Abstract interface for a charging dock for the docking framework.