Skip to content

Commit daf759c

Browse files
committed
added nodes for handover and follow person
1 parent 1302f95 commit daf759c

5 files changed

Lines changed: 375 additions & 0 deletions

File tree

bt_nodes/perception/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,15 @@ list(APPEND plugin_libs clear_octomap_bt_node)
112112
add_library(switch_yolo_model_bt_node SHARED src/perception/switch_yolo_model.cpp)
113113
list(APPEND plugin_libs switch_yolo_model_bt_node)
114114

115+
add_library(set_persistent_id_bt_node SHARED src/perception/set_persistent_id.cpp)
116+
list(APPEND plugin_libs set_persistent_id_bt_node)
117+
115118
add_library(extract_pc_from_class_bt_node SHARED src/perception/extract_pc_from_class.cpp)
116119
list(APPEND plugin_libs extract_pc_from_class_bt_node)
117120

121+
add_library(extract_handover_alignment_bt_node SHARED src/perception/ExtractHandoverAlignment.cpp)
122+
list(APPEND plugin_libs extract_handover_alignment_bt_node)
123+
118124
foreach(bt_plugin ${plugin_libs})
119125
ament_target_dependencies(${bt_plugin} ${dependencies})
120126
target_compile_definitions(${bt_plugin} PRIVATE BT_PLUGIN_EXPORT)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2024 Intelligent Robotics Lab - Gentlebots
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef PERCEPTION__EXTRACT_HANDOVER_ALIGNMENT_HPP_
16+
#define PERCEPTION__EXTRACT_HANDOVER_ALIGNMENT_HPP_
17+
18+
#include <string>
19+
20+
#include "behaviortree_cpp_v3/behavior_tree.h"
21+
#include "behaviortree_cpp_v3/bt_factory.h"
22+
#include "geometry_msgs/msg/transform_stamped.hpp"
23+
#include "rclcpp/rclcpp.hpp"
24+
#include "rclcpp_cascade_lifecycle/rclcpp_cascade_lifecycle.hpp"
25+
#include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
26+
#include "tf2_ros/buffer.h"
27+
#include "tf2_ros/transform_listener.h"
28+
#include "yolo_msgs/msg/detection_array.hpp"
29+
#include "sensor_msgs/msg/joint_state.hpp"
30+
31+
namespace perception
32+
{
33+
34+
class ExtractHandoverAlignment : public BT::ActionNodeBase
35+
{
36+
public:
37+
explicit ExtractHandoverAlignment(
38+
const std::string & xml_tag_name, const BT::NodeConfiguration & conf);
39+
40+
void halt();
41+
BT::NodeStatus tick();
42+
43+
static BT::PortsList providedPorts()
44+
{
45+
return BT::PortsList(
46+
{BT::InputPort<std::string>("interest_class"),
47+
BT::InputPort<double>("target_z_distance"),
48+
BT::InputPort<double>("target_y_distance"),
49+
BT::OutputPort<double>("base_x_movement"),
50+
BT::OutputPort<double>("torso_z_movement")});
51+
}
52+
53+
void detection_callback_(yolo_msgs::msg::DetectionArray::UniquePtr msg);
54+
void joint_state_callback_(sensor_msgs::msg::JointState::UniquePtr msg);
55+
56+
private:
57+
std::shared_ptr<rclcpp_cascade_lifecycle::CascadeLifecycleNode> node_;
58+
rclcpp::Subscription<yolo_msgs::msg::DetectionArray>::SharedPtr detected_objs_sub_;
59+
rclcpp::Subscription<sensor_msgs::msg::JointState>::SharedPtr joint_state_sub_;
60+
yolo_msgs::msg::DetectionArray::UniquePtr last_detected_objs_ = {nullptr};
61+
std::string interest_class_{""};
62+
double current_torso_height_ = 0.0;
63+
bool torso_height_received_ = false;
64+
65+
std::unique_ptr<tf2_ros::Buffer> tf_buffer_;
66+
std::shared_ptr<tf2_ros::TransformListener> tf_listener_;
67+
};
68+
69+
} // namespace perception
70+
71+
#endif // PERCEPTION__EXTRACT_HANDOVER_ALIGNMENT_HPP_
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2024 Intelligent Robotics Lab - Gentlebots
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef PERCEPTION__SET_PERSISTENT_ID_HPP_
16+
#define PERCEPTION__SET_PERSISTENT_ID_HPP_
17+
18+
#include <string>
19+
20+
#include "behaviortree_cpp_v3/behavior_tree.h"
21+
#include "behaviortree_cpp_v3/bt_factory.h"
22+
#include "perception/bt_service_node.hpp"
23+
#include "yolo_msgs/srv/set_persitent_id.hpp"
24+
#include "rclcpp/rclcpp.hpp"
25+
#include "rclcpp_cascade_lifecycle/rclcpp_cascade_lifecycle.hpp"
26+
27+
namespace perception
28+
{
29+
30+
class SetPersistentId : public perception::BtServiceNode<
31+
yolo_msgs::srv::SetPersitentID,
32+
rclcpp_cascade_lifecycle::CascadeLifecycleNode>
33+
{
34+
public:
35+
explicit SetPersistentId(
36+
const std::string & xml_tag_name, const std::string & action_name,
37+
const BT::NodeConfiguration & conf);
38+
39+
void on_tick() override;
40+
void on_result() override;
41+
42+
static BT::PortsList providedPorts()
43+
{
44+
return BT::PortsList(
45+
{BT::InputPort<int>("id")});
46+
}
47+
};
48+
49+
} // namespace perception
50+
51+
#endif // PERCEPTION__SET_PERSISTENT_ID_HPP_
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// Copyright 2024 Intelligent Robotics Lab - Gentlebots
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "perception/ExtractHandoverAlignment.hpp"
16+
17+
#include <tf2/LinearMath/Quaternion.h>
18+
#include <tf2/transform_datatypes.h>
19+
20+
#include "geometry_msgs/msg/transform_stamped.hpp"
21+
#include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
22+
23+
using std::placeholders::_1;
24+
using namespace std::chrono_literals;
25+
26+
namespace perception
27+
{
28+
29+
ExtractHandoverAlignment::ExtractHandoverAlignment(
30+
const std::string & xml_tag_name, const BT::NodeConfiguration & conf)
31+
: BT::ActionNodeBase(xml_tag_name, conf)
32+
{
33+
config().blackboard->get("node", node_);
34+
35+
detected_objs_sub_ = node_->create_subscription<yolo_msgs::msg::DetectionArray>(
36+
"detections_3d", 100, std::bind(&ExtractHandoverAlignment::detection_callback_, this, _1));
37+
joint_state_sub_ = node_->create_subscription<sensor_msgs::msg::JointState>(
38+
"joint_states", 10, std::bind(&ExtractHandoverAlignment::joint_state_callback_, this, _1));
39+
tf_buffer_ = std::make_unique<tf2_ros::Buffer>(node_->get_clock());
40+
tf_listener_ = std::make_shared<tf2_ros::TransformListener>(*tf_buffer_);
41+
}
42+
43+
void ExtractHandoverAlignment::halt()
44+
{
45+
RCLCPP_INFO(node_->get_logger(), "ExtractHandoverAlignment halted");
46+
}
47+
48+
void ExtractHandoverAlignment::detection_callback_(yolo_msgs::msg::DetectionArray::UniquePtr msg)
49+
{
50+
last_detected_objs_ = std::move(msg);
51+
}
52+
53+
void ExtractHandoverAlignment::joint_state_callback_(sensor_msgs::msg::JointState::UniquePtr msg)
54+
{
55+
for (size_t i = 0; i < msg->name.size(); ++i) {
56+
if (msg->name[i] == "torso_lift_joint") {
57+
current_torso_height_ = msg->position[i];
58+
torso_height_received_ = true;
59+
break;
60+
}
61+
}
62+
}
63+
64+
BT::NodeStatus ExtractHandoverAlignment::tick()
65+
{
66+
RCLCPP_INFO(node_->get_logger(), "[ExtractHandoverAlignment] ticked");
67+
getInput("interest_class", interest_class_);
68+
double target_z_distance = 0.40; // Default 40cm in Z from arm_4_link
69+
double target_y_distance = -0.30; // Default -30cm in Y from arm_4_link
70+
getInput("target_z_distance", target_z_distance);
71+
getInput("target_y_distance", target_y_distance);
72+
73+
// rclcpp::spin_some(node_->get_node_base_interface());
74+
75+
if (!torso_height_received_) {
76+
RCLCPP_ERROR(node_->get_logger(), "[ExtractHandoverAlignment] No torso joint state received");
77+
return BT::NodeStatus::FAILURE;
78+
}
79+
80+
if (last_detected_objs_ == nullptr) {
81+
RCLCPP_ERROR(node_->get_logger(), "[ExtractHandoverAlignment] No objects detection yet");
82+
return BT::NodeStatus::FAILURE;
83+
}
84+
85+
auto elapsed = node_->now() - rclcpp::Time(last_detected_objs_->header.stamp);
86+
if (elapsed > 2s) {
87+
RCLCPP_ERROR(node_->get_logger(), "[ExtractHandoverAlignment] No recent detections");
88+
return BT::NodeStatus::FAILURE;
89+
}
90+
91+
for (auto const & detected_object : last_detected_objs_->detections) {
92+
if (detected_object.class_name != interest_class_ && interest_class_ != "") {
93+
RCLCPP_DEBUG(node_->get_logger(), "Ignoring object %s", detected_object.class_name.c_str());
94+
continue;
95+
}
96+
97+
tf2::Transform camera_2_object;
98+
camera_2_object.setOrigin(
99+
tf2::Vector3(
100+
detected_object.bbox3d.center.position.x, detected_object.bbox3d.center.position.y,
101+
detected_object.bbox3d.center.position.z));
102+
camera_2_object.setRotation(tf2::Quaternion(0.0, 0.0, 0.0, 1.0));
103+
104+
// Get transform from arm_4_link to camera
105+
geometry_msgs::msg::TransformStamped arm_2_camera_msg;
106+
try {
107+
arm_2_camera_msg = tf_buffer_->lookupTransform(
108+
"arm_4_link", "head_front_camera_rgb_optical_frame", tf2::TimePointZero);
109+
} catch (const tf2::TransformException & ex) {
110+
// Try regular frame if optical fails
111+
try {
112+
arm_2_camera_msg = tf_buffer_->lookupTransform(
113+
"arm_4_link", "head_front_camera_rgb_frame", tf2::TimePointZero);
114+
} catch (const tf2::TransformException & ex2) {
115+
RCLCPP_ERROR(node_->get_logger(), "TF Error: %s", ex2.what());
116+
return BT::NodeStatus::FAILURE;
117+
}
118+
}
119+
120+
tf2::Transform arm_2_camera;
121+
tf2::fromMsg(arm_2_camera_msg.transform, arm_2_camera);
122+
tf2::Transform arm_2_object = arm_2_camera * camera_2_object;
123+
124+
// The pose in arm_4_link
125+
double obj_x = arm_2_object.getOrigin().x(); // Left/Right from arm
126+
double obj_y = arm_2_object.getOrigin().y(); // Up/Down relative to arm base
127+
double obj_z = arm_2_object.getOrigin().z(); // Forward/Back relative to arm
128+
129+
// Calculate required movements
130+
// Z is forward distance from arm_4_link. If target is 40cm, and object is at 20cm, we need to move the base backward (negative X base movement) by 20cm.
131+
// Base movement required = object Z distance - target Z distance
132+
double base_x_movement = obj_z - target_z_distance;
133+
134+
// Y is up/down from arm_4_link. If target is -30cm (object below arm 4 link), and object is at -50cm, torso needs to lift by 20cm (positive Z).
135+
// Note: ROS standard is Z is up, but here Y might be up/down depending on arm_4_link orientation. Assuming standard where object Z is forward, Y is down/up.
136+
// Let's print out the values to be safe, but typically in optical frame Z is forward, Y is down, X is right. From arm_4_link it might be different.
137+
// Assuming arm_4_link Z is along the arm (forward), X is down/up. We will output the raw differences and the robot control node can handle the mapping.
138+
139+
// Torso Z movement required = object Y distance - target Y distance
140+
// If target is -30 (object below), and object is at -50 (even further below), we need to lower the torso? No, if it's -50 and we want it at -30, we must move torso DOWN by 20 so relative Y becomes -30. Wait.
141+
// If torso goes DOWN, arm goes DOWN. Object Y relative to arm becomes less negative.
142+
// If torso goes UP, arm goes UP. Object Y relative to arm becomes more negative.
143+
// So torso_z_movement = target_y_distance - obj_y (or vice versa depending on axes).
144+
// For now, let's output the error directly:
145+
double torso_z_error = obj_y - target_y_distance;
146+
147+
double new_torso_height = current_torso_height_ + torso_z_error;
148+
149+
// Limits
150+
double min_height = 0.0;
151+
double max_height = 0.35;
152+
if (new_torso_height < min_height) {
153+
new_torso_height = min_height;
154+
RCLCPP_WARN(node_->get_logger(), "[HandoverAlign] Torso height limited to min (0.0)");
155+
} else if (new_torso_height > max_height) {
156+
new_torso_height = max_height;
157+
RCLCPP_WARN(node_->get_logger(), "[HandoverAlign] Torso height limited to max (0.35)");
158+
}
159+
160+
RCLCPP_INFO(node_->get_logger(), "[HandoverAlign] Object %s found in arm_4_link. Z: %.2f (target %.2f), Y: %.2f (target %.2f)",
161+
detected_object.class_name.c_str(), obj_z, target_z_distance, obj_y, target_y_distance);
162+
RCLCPP_INFO(node_->get_logger(), "[HandoverAlign] Calculated Base X diff: %.2f, Torso Z diff: %.2f, Absolute Torso Height: %.2f", base_x_movement, torso_z_error, new_torso_height);
163+
164+
ExtractHandoverAlignment::setOutput("base_x_movement", base_x_movement);
165+
ExtractHandoverAlignment::setOutput("torso_z_movement", new_torso_height);
166+
167+
return BT::NodeStatus::SUCCESS;
168+
}
169+
170+
RCLCPP_ERROR(node_->get_logger(), "[ExtractHandoverAlignment] Object of interest %s not found in scene", interest_class_.c_str());
171+
return BT::NodeStatus::FAILURE;
172+
}
173+
174+
} // namespace perception
175+
176+
BT_REGISTER_NODES(factory)
177+
{
178+
factory.registerNodeType<perception::ExtractHandoverAlignment>("ExtractHandoverAlignment");
179+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2024 Intelligent Robotics Lab - Gentlebots
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "perception/set_persistent_id.hpp"
16+
17+
#include <string>
18+
#include <iostream>
19+
20+
namespace perception
21+
{
22+
23+
using namespace std::chrono_literals;
24+
25+
SetPersistentId::SetPersistentId(
26+
const std::string & xml_tag_name, const std::string & action_name,
27+
const BT::NodeConfiguration & conf)
28+
: perception::BtServiceNode<
29+
yolo_msgs::srv::SetPersitentID,
30+
rclcpp_cascade_lifecycle::CascadeLifecycleNode>(xml_tag_name, action_name, conf)
31+
{
32+
}
33+
34+
void SetPersistentId::on_tick()
35+
{
36+
RCLCPP_DEBUG(node_->get_logger(), "SetPersistentId ticked");
37+
38+
int id;
39+
if (!getInput("id", id)) {
40+
RCLCPP_ERROR(node_->get_logger(), "Missing ID in SetPersistentId");
41+
}
42+
43+
request_->id = id;
44+
}
45+
46+
void SetPersistentId::on_result()
47+
{
48+
if (result_.success) {
49+
std::cout << "Success SetPersistentId" << std::endl;
50+
setStatus(BT::NodeStatus::SUCCESS);
51+
} else {
52+
std::cout << "Failure SetPersistentId: " << result_.message << std::endl;
53+
setStatus(BT::NodeStatus::FAILURE);
54+
}
55+
}
56+
57+
} // namespace perception
58+
59+
#include "behaviortree_cpp_v3/bt_factory.h"
60+
BT_REGISTER_NODES(factory)
61+
{
62+
BT::NodeBuilder builder = [](const std::string & name, const BT::NodeConfiguration & config) {
63+
return std::make_unique<perception::SetPersistentId>(
64+
name, "set_persitent_id", config);
65+
};
66+
67+
factory.registerBuilder<perception::SetPersistentId>("SetPersistentId", builder);
68+
}

0 commit comments

Comments
 (0)