Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
29e4709
feat(opennav_docking): Add dynamic lifecycle for external detectors
bkoensgen Jun 2, 2025
fae0389
fix: Address review feedback and fix unit tests
bkoensgen Jun 17, 2025
fc6fcef
refactor(docking): Improve plugin lifecycle and resource management
bkoensgen Jun 17, 2025
d985909
fix(plugins): Resolve race condition by setting enabled state in call…
bkoensgen Jun 18, 2025
1797a51
refactor(docking): migrate to nav2_ros_common (node_utils, LifecycleN…
bkoensgen Aug 21, 2025
a6ba31b
build(opennav_docking): update deps (std_srvs, package.xml + CMakeLists)
bkoensgen Aug 21, 2025
805544d
refactor(Docking): migrate to nav2::LifecycleNode
bkoensgen Aug 21, 2025
52d1cc0
refactor(docking): use nav2::qos::StandardTopicQoS for subscriptions
bkoensgen Aug 21, 2025
7cdfb5d
refactor(opennav_docking): replace raw queue size with rclcpp::QoS(1)…
bkoensgen Aug 22, 2025
3f2f5ba
refactor(tests): migrate nav2_util::NodeThread to nav2::NodeThread
bkoensgen Aug 22, 2025
6abeb54
refactor(tests): migrate to 3-args service callbacks
bkoensgen Aug 22, 2025
ae6209a
style(test): apply ament_uncrustify
bkoensgen Aug 22, 2025
a1f2ba2
refactor(opennav_docking) migrate detector Trigger service to node_->…
bkoensgen Aug 25, 2025
55aa43e
docking: use nav2 types for pubs/subs in SimpleChargingDock and add e…
bkoensgen Aug 25, 2025
f1c7634
docking: use nav2 types for pubs/subs in SimpleNonChargingDock and ad…
bkoensgen Aug 25, 2025
f0ca065
docking: split detection state in SimpleChargingDock (detection_start…
bkoensgen Aug 25, 2025
a10f89b
docking: split detection state in SimpleNonChargingDock (detection_st…
bkoensgen Aug 25, 2025
9a6c905
tests: adapt SimpleChargingDockTestable to initial_pose_received_ state
bkoensgen Aug 25, 2025
604ea00
tests: adapt SimpleNonChargingDockTestable to initial_pose_received_ …
bkoensgen Aug 25, 2025
6e6de33
docs(docking): clarify external detection gating and subscribe_toggle…
bkoensgen Aug 25, 2025
816ab57
fix(docking): keep SimpleNonChargingDock registered as ChargingDock (…
bkoensgen Aug 29, 2025
a62e800
docs(docking): revert README note to pre-e881de19 state
bkoensgen Aug 29, 2025
1294cdf
fix(docking_server): remove redundant null-check before stopDetection…
bkoensgen Aug 29, 2025
a3f4f37
style(docking_server): unify terminal order (stash->publishZeroVeloci…
bkoensgen Aug 29, 2025
b2fa664
lint
bkoensgen Aug 29, 2025
5500dfa
fix(docking): inline detection process
bkoensgen Sep 16, 2025
7edfb0d
chore(docking): drop redundant detector comment
bkoensgen Sep 16, 2025
5102f5d
chore(docking): clarify detector logs
bkoensgen Sep 16, 2025
79537f8
fix(docking): activate lifecycle publishers
bkoensgen Sep 16, 2025
67a8079
chore(docking): reuse dock pose subscription
bkoensgen Sep 16, 2025
82e7712
lint
bkoensgen Sep 16, 2025
16412c8
fix(docking_server): drop redundant DB deactivate on cleanup
bkoensgen Sep 16, 2025
4783ff2
refactor(docking): rename detection state flag to detection_active
bkoensgen Sep 16, 2025
45f4bcb
fix(docking): reset detection flags on cleanup
bkoensgen Sep 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions nav2_docking/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ The `ChargingDock` and `NonChargingDock` plugins are the heart of the customizab
The docking procedure is as follows:
1. Take action request and obtain the dock's plugin and its pose
2. If the robot is not within the prestaging tolerance of the dock's staging pose, navigate to the staging pose
3. Use the dock's plugin to initially detect the dock and return the docking pose
4. Enter a vision-control loop where the robot attempts to reach the docking pose while its actively being refined by the vision system
5. Exit the vision-control loop once contact has been detected or charging has started
6. Wait until charging starts (if applicable) and return success.
3. Call the dock's plugin `startDetectionProcess()` method to activate any external detection mechanisms.
4. Use the dock's plugin to initially detect the dock (`getRefinedPose`) and return the docking pose.
5. Enter a vision-control loop where the robot attempts to reach the docking pose while it's actively being refined by the vision system.
6. Exit the vision-control loop once contact has been detected or charging has started (if applicable).
7. Wait until charging starts (if applicable) and return success.
8. Call the dock's plugin `stopDetectionProcess()` method to deactivate any external detection mechanisms.

If anywhere this procedure is unsuccessful, `N` retries may be made by driving back to the dock's staging pose and trying again. If still unsuccessful, it will return a failure code to indicate what kind of failure occurred to the client.
If anywhere this procedure is unsuccessful (before step 8), `N` retries may be made, driving back to the dock's staging pose, and then restarting the process from step 3. If still unsuccessful after retries, it will return a failure code to indicate what kind of failure occurred to the client.

Undocking works more simply:
1. If previously docked, use the known dock information to get the dock type. If not, use the undock action request's indicated dock type
Expand Down Expand Up @@ -175,8 +177,9 @@ some robots.
`getStagingPose` applies a parameterized translational and rotational offset to the dock pose to obtain the staging pose.

`getRefinedPose` can be used in two ways.
1. A blind approach where the returned dock pose will simply be equal to whatever was passed in from the dock database. This may work with a reduced success rate on a real robot (due to global localization error), but is useful for initial testing and simulation.
1. A blind approach where the returned dock pose will simply be equal to whatever was passed in from the dock database. This may work with a reduced success rate on a real robot (due to global localization error), but is useful for initial testing and simulation. The `start/stopDetectionProcess` methods would typically do nothing in this case.
Comment thread
SteveMacenski marked this conversation as resolved.
2. The more realistic use case is to use an AR marker, dock pose detection algorithm, etc. The plugin will subscribe to a `geometry_msgs/PoseStamped` topic `detected_dock_pose`. This can be used with the `image_proc/TrackMarkerNode` for Apriltags or other custom detectors for your dock. It is unlikely the detected pose is actually the pose you want to dock with, so several parameters are supplied to represent your docked pose relative to the detected feature's pose.
The subscription to `detected_dock_pose` can be managed dynamically (see `subscribe_toggle` parameter). Additionally, an external detector process can be triggered via a service call (see `detector_service_name`). The `DockingServer` calls `startDetectionProcess()` before `getRefinedPose` (which is called in a loop) and `stopDetectionProcess()` after the docking action concludes (successfully or not). This allows plugins to manage resources like GPU usage by only running detection when needed.
Comment thread
SteveMacenski marked this conversation as resolved.

During the docking approach, there are two options for detecting `isDocked`:
1. We can check the joint states of the wheels if the current has spiked above a set threshold to indicate that the robot has made contact with the dock or other physical object.
Expand Down Expand Up @@ -257,6 +260,9 @@ Note: `dock_plugins` and either `docks` or `dock_database` are required.
| staging_yaw_offset | Staging pose angle relative to dock pose (rad) | double | 0.0 |
| dock_direction | Whether the robot is docking with the dock forward or backward in motion | string | "forward" or "backward" |
| rotate_to_dock | Enables backward docking without requiring a sensor for detection during the final approach. When enabled, the robot approaches the staging pose facing forward with sensor coverage for dock detection; after detection, it rotates and backs into the dock using only the initially detected pose for dead reckoning. | bool | false |
| detector_service_name | Trigger service name to start/stop the detector (e.g., a camera node or an AprilTag detector node). Leave empty to disable service calls. | string | "" |
| detector_service_timeout | Timeout (seconds) to wait for the detector service (if `detector_service_name` is set) to become available or respond. | double | 5.0 |
| subscribe_toggle | If true, dynamically subscribe/unsubscribe to `detected_dock_pose` topic when `start/stopDetectionProcess` are called. If false, the subscription is kept alive throughout the plugin's lifecycle if `use_external_detection_pose` is true. This can be useful if the detector node is always running and publishing. | bool | false |

Note: The external detection rotation angles are setup to work out of the box with Apriltags detectors in `image_proc` and `isaac_ros`.

Expand Down
4 changes: 4 additions & 0 deletions nav2_docking/opennav_docking/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ find_package(rclcpp_components REQUIRED)
find_package(rclcpp_lifecycle REQUIRED)
find_package(rcl_interfaces REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(std_srvs REQUIRED)
find_package(tf2 REQUIRED)
find_package(tf2_geometry_msgs REQUIRED)
find_package(tf2_ros REQUIRED)
Expand Down Expand Up @@ -127,6 +128,7 @@ target_link_libraries(simple_charging_dock PUBLIC
rclcpp::rclcpp
rclcpp_lifecycle::rclcpp_lifecycle
${sensor_msgs_TARGETS}
${std_srvs_TARGETS}
tf2_geometry_msgs::tf2_geometry_msgs
tf2_ros::tf2_ros
nav2_ros_common::nav2_ros_common
Expand Down Expand Up @@ -155,6 +157,7 @@ target_link_libraries(simple_non_charging_dock PUBLIC
rclcpp::rclcpp
rclcpp_lifecycle::rclcpp_lifecycle
${sensor_msgs_TARGETS}
${std_srvs_TARGETS}
tf2_geometry_msgs::tf2_geometry_msgs
tf2_ros::tf2_ros
)
Expand Down Expand Up @@ -211,6 +214,7 @@ ament_export_dependencies(
rclcpp_action
rclcpp_lifecycle
rcl_interfaces
std_srvs
tf2_ros
yaml-cpp
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <memory>
#include <vector>

#include "std_srvs/srv/trigger.hpp"
#include "geometry_msgs/msg/pose_stamped.hpp"
#include "sensor_msgs/msg/battery_state.hpp"
#include "sensor_msgs/msg/joint_state.hpp"
Expand Down Expand Up @@ -54,17 +55,17 @@ class SimpleChargingDock : public opennav_docking_core::ChargingDock
/**
* @brief Method to cleanup resources used on shutdown.
*/
virtual void cleanup() {}
void cleanup() override;

/**
* @brief Method to active Behavior and any threads involved in execution.
*/
virtual void activate() {}
void activate() override;

/**
* @brief Method to deactivate Behavior and any threads involved in execution.
*/
virtual void deactivate() {}
void deactivate() override;

/**
* @brief Method to obtain the dock's staging pose. This method should likely
Expand Down Expand Up @@ -104,14 +105,24 @@ class SimpleChargingDock : public opennav_docking_core::ChargingDock
*/
virtual bool hasStoppedCharging();

/**
* @brief Start external detection process (service call + subscribe).
*/
bool startDetectionProcess() override;

/**
* @brief Stop external detection process.
*/
bool stopDetectionProcess() override;

protected:
void jointStateCallback(const sensor_msgs::msg::JointState::SharedPtr state);

// Optionally subscribe to a detected dock pose topic
nav2::Subscription<geometry_msgs::msg::PoseStamped>::SharedPtr dock_pose_sub_;
rclcpp::Publisher<geometry_msgs::msg::PoseStamped>::SharedPtr dock_pose_pub_;
rclcpp::Publisher<geometry_msgs::msg::PoseStamped>::SharedPtr filtered_dock_pose_pub_;
rclcpp::Publisher<geometry_msgs::msg::PoseStamped>::SharedPtr staging_pose_pub_;
nav2::Publisher<geometry_msgs::msg::PoseStamped>::SharedPtr dock_pose_pub_;
nav2::Publisher<geometry_msgs::msg::PoseStamped>::SharedPtr filtered_dock_pose_pub_;
nav2::Publisher<geometry_msgs::msg::PoseStamped>::SharedPtr staging_pose_pub_;
// If subscribed to a detected pose topic, will contain latest message
geometry_msgs::msg::PoseStamped detected_dock_pose_;
// This is the actual dock pose once it has the specified translation/rotation applied
Expand Down Expand Up @@ -150,6 +161,18 @@ class SimpleChargingDock : public opennav_docking_core::ChargingDock

nav2::LifecycleNode::SharedPtr node_;
std::shared_ptr<tf2_ros::Buffer> tf2_buffer_;

// Detector control parameters
std::string detector_service_name_;
double detector_service_timeout_{5.0};
bool subscribe_toggle_{false};

// Client used to call the Trigger service
nav2::ServiceClient<std_srvs::srv::Trigger>::SharedPtr detector_client_;

// Detection state flags
bool detection_active_{false};
bool initial_pose_received_{false};
};

} // namespace opennav_docking
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <memory>
#include <vector>

#include "std_srvs/srv/trigger.hpp"
#include "geometry_msgs/msg/pose_stamped.hpp"
#include "sensor_msgs/msg/battery_state.hpp"
#include "sensor_msgs/msg/joint_state.hpp"
Expand Down Expand Up @@ -54,17 +55,17 @@ class SimpleNonChargingDock : public opennav_docking_core::NonChargingDock
/**
* @brief Method to cleanup resources used on shutdown.
*/
virtual void cleanup() {}
void cleanup() override;

/**
* @brief Method to active Behavior and any threads involved in execution.
*/
virtual void activate() {}
/**
* @brief Method to active Behavior and any threads involved in execution.
*/
void activate() override;

/**
* @brief Method to deactivate Behavior and any threads involved in execution.
*/
virtual void deactivate() {}
/**
* @brief Method to deactivate Behavior and any threads involved in execution.
*/
void deactivate() override;

/**
* @brief Method to obtain the dock's staging pose. This method should likely
Expand All @@ -89,14 +90,24 @@ class SimpleNonChargingDock : public opennav_docking_core::NonChargingDock
*/
virtual bool isDocked();

/**
* @brief Start external detection process (service call + subscribe).
*/
bool startDetectionProcess() override;

/**
* @brief Stop external detection process.
*/
bool stopDetectionProcess() override;

protected:
void jointStateCallback(const sensor_msgs::msg::JointState::SharedPtr state);

// Optionally subscribe to a detected dock pose topic
nav2::Subscription<geometry_msgs::msg::PoseStamped>::SharedPtr dock_pose_sub_;
rclcpp::Publisher<geometry_msgs::msg::PoseStamped>::SharedPtr dock_pose_pub_;
rclcpp::Publisher<geometry_msgs::msg::PoseStamped>::SharedPtr filtered_dock_pose_pub_;
rclcpp::Publisher<geometry_msgs::msg::PoseStamped>::SharedPtr staging_pose_pub_;
nav2::Publisher<geometry_msgs::msg::PoseStamped>::SharedPtr dock_pose_pub_;
nav2::Publisher<geometry_msgs::msg::PoseStamped>::SharedPtr filtered_dock_pose_pub_;
nav2::Publisher<geometry_msgs::msg::PoseStamped>::SharedPtr staging_pose_pub_;
// If subscribed to a detected pose topic, will contain latest message
geometry_msgs::msg::PoseStamped detected_dock_pose_;
// This is the actual dock pose once it has the specified translation/rotation applied
Expand Down Expand Up @@ -128,6 +139,18 @@ class SimpleNonChargingDock : public opennav_docking_core::NonChargingDock

nav2::LifecycleNode::SharedPtr node_;
std::shared_ptr<tf2_ros::Buffer> tf2_buffer_;

// Detector control parameters
std::string detector_service_name_;
double detector_service_timeout_{5.0};
bool subscribe_toggle_{false};

// Client used to call the Trigger service
nav2::ServiceClient<std_srvs::srv::Trigger>::SharedPtr detector_client_;

// Detection state flags
bool detection_active_{false};
bool initial_pose_received_{false};
};

} // namespace opennav_docking
Expand Down
1 change: 1 addition & 0 deletions nav2_docking/opennav_docking/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<depend>rclcpp_lifecycle</depend>
<depend>rcl_interfaces</depend>
<depend>sensor_msgs</depend>
<depend>std_srvs</depend>
<depend>tf2</depend>
<depend>tf2_geometry_msgs</depend>
<depend>tf2_ros</depend>
Expand Down
12 changes: 11 additions & 1 deletion nav2_docking/opennav_docking/src/docking_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ void DockingServer::dockRobot()
result->num_retries = num_retries_;
stashDockData(goal->use_dock_id, dock, true);
publishZeroVelocity();
dock->plugin->stopDetectionProcess();
Comment thread
SteveMacenski marked this conversation as resolved.
docking_action_server_->succeeded_current(result);
return;
}
Expand All @@ -339,6 +340,7 @@ void DockingServer::dockRobot()
// Cancelled, preempted, or shutting down (recoverable errors throw DockingException)
stashDockData(goal->use_dock_id, dock, false);
publishZeroVelocity();
dock->plugin->stopDetectionProcess();
docking_action_server_->terminate_all(result);
return;
} catch (opennav_docking_core::DockingException & e) {
Expand All @@ -354,6 +356,7 @@ void DockingServer::dockRobot()
// Cancelled, preempted, or shutting down
stashDockData(goal->use_dock_id, dock, false);
publishZeroVelocity();
dock->plugin->stopDetectionProcess();
Comment thread
SteveMacenski marked this conversation as resolved.
docking_action_server_->terminate_all(result);
return;
}
Expand Down Expand Up @@ -401,6 +404,7 @@ void DockingServer::dockRobot()
stashDockData(goal->use_dock_id, dock, false);
result->num_retries = num_retries_;
publishZeroVelocity();
dock->plugin->stopDetectionProcess();
docking_action_server_->terminate_current(result);
}

Expand Down Expand Up @@ -429,12 +433,18 @@ Dock * DockingServer::generateGoalDock(std::shared_ptr<const DockRobot::Goal> go
void DockingServer::doInitialPerception(Dock * dock, geometry_msgs::msg::PoseStamped & dock_pose)
{
publishDockingFeedback(DockRobot::Feedback::INITIAL_PERCEPTION);

if (!dock->plugin->startDetectionProcess()) {
throw opennav_docking_core::FailedToDetectDock("Failed to start the detection process.");
}

rclcpp::Rate loop_rate(controller_frequency_);
auto start = this->now();
auto timeout = rclcpp::Duration::from_seconds(initial_perception_timeout_);
while (!dock->plugin->getRefinedPose(dock_pose, dock->id)) {
if (this->now() - start > timeout) {
throw opennav_docking_core::FailedToDetectDock("Failed initial dock detection");
throw opennav_docking_core::FailedToDetectDock(
"Failed initial dock detection: Timeout exceeded");
}

if (checkAndWarnIfCancelled<DockRobot>(docking_action_server_, "dock_robot") ||
Expand Down
Loading
Loading