|
| 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 | +} |
0 commit comments