13 Commits

Author SHA1 Message Date
e4684bbe70 add x86_64 arch libapi_cpp.so 2025-12-02 14:19:04 +08:00
9fe000911f rm lib for ik cal 2025-12-01 13:36:18 +08:00
b4b3c3b80c support srv default topic 2025-11-28 11:45:21 +08:00
1b69632c7b fix error default_topic 2025-11-28 11:38:15 +08:00
f9992d8437 modify JzCmd -> GripperCmd 2025-11-27 10:36:20 +08:00
0992caae72 更新夹爪参数 2025-11-25 15:10:32 +08:00
4f679c77ae add calc grasp pose 2025-11-24 18:34:12 +08:00
1951dd8276 move cerebellum hooks funcs to cerebellum_hooks.hpp/cpp 2025-11-24 14:39:07 +08:00
e8f1021af1 update grasp sch and data 2025-11-24 13:55:03 +08:00
79ade2d741 merge remote 2025-11-21 18:29:10 +08:00
53e62e2714 add JzCmd gripper support 2025-11-21 18:22:28 +08:00
3d79cf2eb8 add final_arm_pose_ 2025-11-20 16:30:17 +08:00
9ecdb1be60 test vision grasp 2025-11-20 11:12:01 +08:00
36 changed files with 11797 additions and 790 deletions

View File

@@ -34,13 +34,18 @@ find_package(tf2_geometry_msgs REQUIRED)
add_executable(brain_node
src/brain_node.cpp
src/cerebellum_node.cpp
src/cerebellum_hooks.cpp
src/cerebrum_node.cpp
src/skill_manager.cpp
src/calc_grasp_pose.cpp
)
target_include_directories(brain_node PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
$<INSTALL_INTERFACE:include>
${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/rm_arm/include
interfaces/include
)
target_compile_features(brain_node PUBLIC c_std_99 cxx_std_17) # Require C99 and C++17
@@ -66,6 +71,7 @@ ament_target_dependencies(brain_node
target_link_libraries(brain_node
Boost::thread
yaml-cpp
${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/rm_arm/lib/libapi_cpp.so
)
install(TARGETS brain_node
@@ -161,9 +167,12 @@ if(BUILD_TESTING)
tf2_ros
tf2_geometry_msgs
)
target_include_directories(test_cerebellum_node PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(test_cerebellum_node PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/rm_arm/include
)
target_link_libraries(test_cerebellum_node Boost::thread)
target_link_libraries(test_cerebellum_node yaml-cpp)
target_link_libraries(test_cerebellum_node yaml-cpp ${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/rm_arm/lib/libapi_cpp.so)
endif()
ament_add_gtest(test_cerebrum_node
@@ -188,9 +197,12 @@ if(BUILD_TESTING)
tf2_ros
tf2_geometry_msgs
)
target_include_directories(test_cerebrum_node PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(test_cerebrum_node PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/rm_arm/include
)
target_link_libraries(test_cerebrum_node Boost::thread)
target_link_libraries(test_cerebrum_node yaml-cpp)
target_link_libraries(test_cerebrum_node yaml-cpp ${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/rm_arm/lib/libapi_cpp.so)
endif()
ament_add_gtest(test_sm_cerebellum
@@ -209,7 +221,11 @@ if(BUILD_TESTING)
tf2_ros
tf2_geometry_msgs
)
target_include_directories(test_sm_cerebellum PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(test_sm_cerebellum PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/rm_arm/include
)
target_link_libraries(test_sm_cerebellum ${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/rm_arm/lib/libapi_cpp.so)
endif()
# ExecuteBtAction end-to-end single skill test
@@ -234,8 +250,11 @@ if(BUILD_TESTING)
tf2_ros
tf2_geometry_msgs
)
target_include_directories(test_execute_bt_action PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(test_execute_bt_action Boost::thread yaml-cpp)
target_include_directories(test_execute_bt_action PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/rm_arm/include
)
target_link_libraries(test_execute_bt_action Boost::thread yaml-cpp ${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/rm_arm/lib/libapi_cpp.so)
endif()
# Extended ExecuteBtAction scenario tests (multi-skill, failure, RUN interpolation, timeout, stats, cancel)
@@ -260,8 +279,11 @@ if(BUILD_TESTING)
tf2_ros
tf2_geometry_msgs
)
target_include_directories(test_execute_bt_action_extended PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(test_execute_bt_action_extended Boost::thread yaml-cpp)
target_include_directories(test_execute_bt_action_extended PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/rm_arm/include
)
target_link_libraries(test_execute_bt_action_extended Boost::thread yaml-cpp ${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/rm_arm/lib/libapi_cpp.so)
endif()
# Additional isolated ExecuteBtAction scenario tests
@@ -276,8 +298,11 @@ if(BUILD_TESTING)
target_compile_definitions(${test_name} PRIVATE BRAIN_DISABLE_SM=1)
ament_target_dependencies(${test_name}
rclcpp rclcpp_action behaviortree_cpp interfaces smacc2 smacc2_msgs ament_index_cpp nav2_msgs std_srvs tf2 tf2_ros tf2_geometry_msgs)
target_include_directories(${test_name} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(${test_name} Boost::thread yaml-cpp)
target_include_directories(${test_name} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/rm_arm/include
)
target_link_libraries(${test_name} Boost::thread yaml-cpp ${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/rm_arm/lib/libapi_cpp.so)
endif()
endforeach()
@@ -291,8 +316,11 @@ if(BUILD_TESTING)
target_compile_definitions(test_cerebrum_utils PRIVATE BRAIN_DISABLE_SM=1)
ament_target_dependencies(test_cerebrum_utils
rclcpp rclcpp_action behaviortree_cpp interfaces smacc2 smacc2_msgs ament_index_cpp nav2_msgs std_srvs tf2 tf2_ros tf2_geometry_msgs)
target_include_directories(test_cerebrum_utils PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(test_cerebrum_utils Boost::thread yaml-cpp)
target_include_directories(test_cerebrum_utils PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/rm_arm/include
)
target_link_libraries(test_cerebrum_utils Boost::thread yaml-cpp ${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/rm_arm/lib/libapi_cpp.so)
endif()
endif()

Binary file not shown.

View File

@@ -11,7 +11,7 @@
'
- name: s1_arm_move_origin_position
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -25,7 +25,7 @@
'
- name: s2_arm_move_to_snapshot_pose
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -45,7 +45,7 @@
'
- name: s3_snapshot_action1
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -59,7 +59,7 @@
'
- name: s3_snapshot_action2
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -73,7 +73,7 @@
'
- name: s3_snapshot_top_action1
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -87,7 +87,7 @@
'
- name: s3_snapshot_top_action2
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -101,7 +101,7 @@
'
- name: s3_snapshot_top_action3
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -115,7 +115,7 @@
'
- name: s4_arm_prepare_grasp_box
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -129,7 +129,7 @@
'
- name: s4_arm_grasp_box
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -143,7 +143,7 @@
'
- name: s5_arm_pickup_box
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -175,7 +175,7 @@
'
- name: s9_arm_put_down_box
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -189,7 +189,7 @@
'
- name: s10_arm_release
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -203,7 +203,7 @@
'
- name: s10_arm_raise_up
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -229,7 +229,7 @@
'
- name: s13_arm_move_origin_position
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -243,7 +243,7 @@
'
- name: s14_arm_move_to_snapshot_pose
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -257,7 +257,7 @@
'
- name: s14_arm_move_pre_origin_position
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -271,7 +271,7 @@
'
- name: s14_arm_move_origin_position
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -291,7 +291,7 @@
'
- name: s15_snapshot_action1
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -305,7 +305,7 @@
'
- name: s15_snapshot_action2
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -319,7 +319,7 @@
'
- name: s15_snapshot_top_action1
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -333,7 +333,7 @@
'
- name: s15_snapshot_top_action2
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -347,7 +347,7 @@
'
- name: s15_snapshot_top_action3
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -361,7 +361,7 @@
'
- name: s16_arm_prepare_grasp_box
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -375,7 +375,7 @@
'
- name: s16_arm_grasp_box
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -389,7 +389,7 @@
'
- name: s17_arm_pickup_box
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -421,7 +421,7 @@
'
- name: s21_arm_put_down_box
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -435,7 +435,7 @@
'
- name: s22_arm_release
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -449,7 +449,7 @@
'
- name: s22_arm_raise_up
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -475,7 +475,7 @@
'
- name: s25_arm_move_origin_position
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -495,7 +495,7 @@
'
- name: s26_arm_move_pre_origin_position
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -509,7 +509,7 @@
'
- name: s26_arm_move_origin_position
params: 'body_id: 0
params: 'body_id: 2
data_type: 2

View File

@@ -7,7 +7,7 @@
'
- name: s1_arm_move_origin_position
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -39,7 +39,7 @@
'
- name: s4_arm_move_pre_origin_position
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -53,7 +53,7 @@
'
- name: s4_arm_move_to_snapshot_pose
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -67,7 +67,7 @@
'
- name: s4_snapshot_action1
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -81,7 +81,7 @@
'
- name: s4_snapshot_action2
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -95,7 +95,7 @@
'
- name: s4_snapshot_top_action1
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -109,7 +109,7 @@
'
- name: s4_snapshot_top_action2
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -123,7 +123,7 @@
'
- name: s4_snapshot_top_action3
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -137,7 +137,7 @@
'
- name: s5_arm_prepare_to_grasp
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -151,7 +151,7 @@
'
- name: s6_arm_grasp_box
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -165,7 +165,7 @@
'
- name: s7_arm_pickup_box
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -197,7 +197,7 @@
'
- name: s11_arm_putdown_box
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -211,7 +211,7 @@
'
- name: s12_arm_release_box
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -225,7 +225,7 @@
'
- name: s12_arm_raise_up
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -239,7 +239,7 @@
'
- name: s13_arm_move_to_snapshot_pose
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -253,7 +253,7 @@
'
- name: s13_arm_move_origin_position
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -291,7 +291,7 @@
'
- name: s15_arm_move_pre_origin_position
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -305,7 +305,7 @@
'
- name: s15_arm_move_to_snapshot_pose
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -319,7 +319,7 @@
'
- name: s15_snapshot_action1
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -333,7 +333,7 @@
'
- name: s15_snapshot_action2
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -347,7 +347,7 @@
'
- name: s15_snapshot_top_action1
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -361,7 +361,7 @@
'
- name: s15_snapshot_top_action2
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -375,7 +375,7 @@
'
- name: s15_snapshot_top_action3
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -389,7 +389,7 @@
'
- name: s16_arm_prepare_to_grasp
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -403,7 +403,7 @@
'
- name: s17_arm_grasp_box
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -417,7 +417,7 @@
'
- name: s18_arm_pickup_box
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -449,7 +449,7 @@
'
- name: s22_arm_putdown_box
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -463,7 +463,7 @@
'
- name: s23_arm_release_box
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -477,7 +477,7 @@
'
- name: s23_arm_raise_up
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -491,7 +491,7 @@
'
- name: s23_arm_move_to_snapshot_pose
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -505,7 +505,7 @@
'
- name: s23_arm_move_origin_position
params: 'body_id: 0
params: 'body_id: 2
data_type: 2
@@ -525,7 +525,7 @@
'
- name: s25_arm_move_origin_position
params: 'body_id: 0
params: 'body_id: 2
data_type: 2

View File

@@ -6,12 +6,22 @@
params: '{}
'
- name: VisionObjectRecognition_H
params: '{}
- name: gripper_open
params: 'loc: 0
speed: 20
torque: 20
mode: 2
'
- name: Arm_H
params: 'body_id: 0
- name: VisionObjectRecognition_H
params: 'camera_position: right
'
- name: right_arm_vision_grasp
params: 'body_id: 1
data_type: 0
@@ -24,9 +34,13 @@
data_array: []
'
- name: HandControl_H
params: 'mode: 0
- name: gripper_close
params: 'loc: 30
effort: 0.0
speed: 20
torque: 20
mode: 2
'

View File

@@ -1,15 +1,17 @@
<root BTCPP_format="4">
<BehaviorTree ID="MainTree">
<Sequence name="root">
<!-- Retry vision + hand until HandControl_H succeeds -->
<RetryUntilSuccessful name="retry_vision_hand" num_attempts="5">
<!-- Retry vision + hand until GripperControl_H succeeds -->
<RetryUntilSuccessful name="retry_vision_hand" num_attempts="3">
<Sequence>
<!-- <GripperCmd_H name="gripper_open" /> -->
<VisionObjectRecognition_H name="VisionObjectRecognition_H" />
<Arm_H name="Arm_H" />
<!-- <Arm_H name="right_arm_vision_grasp" /> -->
<!-- <GripperCmd_H name="gripper_close" /> -->
</Sequence>
</RetryUntilSuccessful>
<!-- After Arm_H eventually succeeds, run the hand -->
<HandControl_H name="HandControl_H" />
<!-- <GripperCmd_H name="GripperCmd_H" /> -->
</Sequence>
</BehaviorTree>
</root>

View File

@@ -6,12 +6,14 @@
version: 1.0.0
config_params_path: [
{config: /config/bt_carry_boxes_sch1.xml, param: /config/bt_carry_boxes_sch1.params.yaml},
{config: /config/bt_carry_boxes_sch2.xml, param: /config/bt_carry_boxes_sch2.params.yaml}
{config: /config/bt_carry_boxes_sch2.xml, param: /config/bt_carry_boxes_sch2.params.yaml},
{config: /config/bt_vision_grasp.xml, param: /config/bt_vision_grasp.params.yaml}
]
- name: cerebellum_node
version: 1.0.0
config_params_path: [
{config: /config/bt_carry_boxes_sch1.xml, param: /config/bt_carry_boxes_sch1.params.yaml},
{config: /config/bt_carry_boxes_sch2.xml, param: /config/bt_carry_boxes_sch2.params.yaml}
{config: /config/bt_carry_boxes_sch2.xml, param: /config/bt_carry_boxes_sch2.params.yaml},
{config: /config/bt_vision_grasp.xml, param: /config/bt_vision_grasp.params.yaml}
]

View File

@@ -61,4 +61,11 @@
description: "回到原点位姿"
interfaces:
- name: MoveHome.action
default_topic: "MoveHome"
default_topic: "MoveHome"
- name: GripperCmd
version: 1.0.0
description: "夹爪控制"
interfaces:
- name: GripperCmd.action
default_topic: "gripper_cmd0"

View File

@@ -379,16 +379,16 @@ public:
}
auto result_future = e->client->async_get_result(goal_handle);
// auto deadline = std::chrono::steady_clock::now() + timeout;
// if (result_future.wait_until(deadline) != std::future_status::ready) {
// RCLCPP_ERROR(logger, "Timed out waiting for result on '%s', sending cancel", name.c_str());
// try {
// e->client->async_cancel_goal(goal_handle);
// } catch (const std::exception & ex) {
// RCLCPP_WARN(logger, "Failed cancelling goal for '%s': %s", name.c_str(), ex.what());
// }
// return std::nullopt;
// }
auto deadline = std::chrono::steady_clock::now() + timeout;
if (result_future.wait_until(deadline) != std::future_status::ready) {
RCLCPP_ERROR(logger, "Timed out waiting for result on '%s', sending cancel", name.c_str());
try {
e->client->async_cancel_goal(goal_handle);
} catch (const std::exception & ex) {
RCLCPP_WARN(logger, "Failed cancelling goal for '%s': %s", name.c_str(), ex.what());
}
return std::nullopt;
}
typename rclcpp_action::ClientGoalHandle<ActionT>::WrappedResult wrapped_result;
try {
wrapped_result = result_future.get();
@@ -460,16 +460,16 @@ public:
}
auto result_future = e->client->async_get_result(goal_handle);
// auto deadline = std::chrono::steady_clock::now() + timeout;
// if (result_future.wait_until(deadline) != std::future_status::ready) {
// RCLCPP_ERROR(logger, "Timed out waiting for result on '%s', sending cancel", name.c_str());
// try {
// e->client->async_cancel_goal(goal_handle);
// } catch (const std::exception & ex) {
// RCLCPP_WARN(logger, "Failed cancelling goal for '%s': %s", name.c_str(), ex.what());
// }
// return std::nullopt;
// }
auto deadline = std::chrono::steady_clock::now() + timeout;
if (result_future.wait_until(deadline) != std::future_status::ready) {
RCLCPP_ERROR(logger, "Timed out waiting for result on '%s', sending cancel", name.c_str());
try {
e->client->async_cancel_goal(goal_handle);
} catch (const std::exception & ex) {
RCLCPP_WARN(logger, "Failed cancelling goal for '%s': %s", name.c_str(), ex.what());
}
return std::nullopt;
}
typename rclcpp_action::ClientGoalHandle<ActionT>::WrappedResult wrapped_result;
try {
wrapped_result = result_future.get();

View File

@@ -0,0 +1,66 @@
#pragma once
#include <tf2/LinearMath/Vector3.h>
#include <tf2/LinearMath/Quaternion.h>
#include <tf2/LinearMath/Matrix3x3.h>
#include <tf2/LinearMath/Transform.h>
#include <string>
#include <vector>
namespace brain {
struct Pose {
tf2::Vector3 position;
tf2::Quaternion orientation;
};
struct GraspResult {
std::string name;
Pose pre_grasp_pose;
Pose grasp_pose;
};
class GraspPoseCalculator {
public:
GraspPoseCalculator();
static GraspResult calculate(
const tf2::Vector3& target_pos,
const tf2::Quaternion& target_quat,
const std::string& grasp_type = "side",
double angle_deg = 0.0,
const std::string& palm_facing = "down",
double safety_dist = 0.06,
double finger_length = 0.0,
const std::string& arm = "right"
);
static double evaluate_grasp_quality(
const tf2::Vector3& target_pos,
double angle_deg,
const std::string& arm = "right"
);
static std::vector<double> find_optimal_grasp_angles(
const tf2::Vector3& target_pos,
const std::string& arm = "right",
int top_k = 3
);
static int rm_arm_inverse_kinematics(
const tf2::Vector3& target_pos,
const tf2::Quaternion& target_quat,
std::vector<double>& joint_angles
);
private:
static bool rm_arm_algo_init_done_;
static GraspResult create_grasp_matrix(
const tf2::Vector3& target_pos,
const tf2::Vector3& approach_vec,
const tf2::Vector3& palm_normal_vec,
double safety_dist,
double finger_length
);
static void rm_arm_algo_init(void);
};
} // namespace brain

View File

@@ -0,0 +1,35 @@
#ifndef BRAIN_CEREBELLUM_HOOKS_HPP_
#define BRAIN_CEREBELLUM_HOOKS_HPP_
namespace brain
{
class CerebellumNode;
class CerebellumHooks
{
public:
static void ConfigureActionHooks(CerebellumNode * node);
static void ConfigureArmSpaceControlHooks(CerebellumNode * node);
static void ConfigureArmHooks(CerebellumNode * node);
static void ConfigureHandControlHooks(CerebellumNode * node);
static void ConfigureCameraTakePhotoHooks(CerebellumNode * node);
static void ConfigureLegControlHooks(CerebellumNode * node);
static void ConfigureVisionGraspObjectHooks(CerebellumNode * node);
static void ConfigureSlamModeHooks(CerebellumNode * node);
static void ConfigureNavigateToPoseHooks(CerebellumNode * node);
static void ConfigureMoveWaistHooks(CerebellumNode * node);
static void ConfigureMoveLegHooks(CerebellumNode * node);
static void ConfigureMoveWheelHooks(CerebellumNode * node);
static void ConfigureMoveHomeHooks(CerebellumNode * node);
static void ConfigureGripperCmdControlHooks(CerebellumNode * node);
static void ConfigureServiceHooks(CerebellumNode * node);
static void ConfigureVisionObjectRecognitionHooks(CerebellumNode * node);
static void ConfigureMapLoadHooks(CerebellumNode * node);
static void ConfigureMapSaveHooks(CerebellumNode * node);
};
} // namespace brain
#endif // BRAIN_CEREBELLUM_HOOKS_HPP_

View File

@@ -18,8 +18,10 @@
#include <smacc2/smacc_signal_detector.hpp>
#include "brain/sm_cerebellum.hpp" // for CerebellumData::ExecResult
// For from_yaml<T>(YAML::Node)
#include <sensor_msgs/msg/joint_state.hpp>
#include "brain/payload_converters.hpp"
#include "brain/robot_config.hpp"
#include "brain/calc_grasp_pose.hpp"
namespace brain
{
@@ -41,11 +43,19 @@ public:
*/
explicit CerebellumNode(const rclcpp::NodeOptions & options = rclcpp::NodeOptions());
std::vector<geometry_msgs::msg::Pose> pre_grasp_poses_;
std::vector<geometry_msgs::msg::Pose> grasp_poses_;
std::vector<double> left_arm_joint_angles_;
std::vector<double> right_arm_joint_angles_;
std::mutex joint_state_mutex_;
private:
struct GoalExecutionContext { std::atomic<bool> cancel{false}; };
std::unordered_map<std::uintptr_t, std::shared_ptr<GoalExecutionContext>> goal_ctxs_;
std::mutex goal_ctxs_mutex_;
static thread_local const std::vector<interfaces::msg::SkillCall>* tls_current_calls_;
static thread_local int tls_arm_body_id;
// Generic action registry to support multiple actions via registration
std::unique_ptr<ActionServerRegistry> action_registry_;
std::unique_ptr<ActionClientRegistry> action_clients_;
@@ -86,10 +96,17 @@ private:
std::unordered_map<std::string, geometry_msgs::msg::PoseStamped> target_pose_;
std::string target_frame_{"bottle"};
int64_t command_id_{0};
int64_t command_id_left_arm_ {0};
int64_t command_id_right_arm_{0};
std::unordered_map<std::string, geometry_msgs::msg::Pose> final_arm_pose_;
std::shared_ptr<tf2_ros::Buffer> tf_buffer_;
std::shared_ptr<tf2_ros::TransformListener> tf_listener_;
std::string arm_target_frame_{"base_link_leftarm"};
double arm_transform_timeout_sec_{0.2};
std::string arm_target_frame_{"base_link_rightarm"};
double arm_transform_timeout_sec_{1};
rclcpp::Subscription<sensor_msgs::msg::JointState>::SharedPtr joint_state_sub_;
void JointStateCallback(const sensor_msgs::msg::JointState::SharedPtr msg);
double grab_width_{0.0};
// Current accepted sequence epoch (from client) used to filter/reject stale goals
std::atomic<uint32_t> current_epoch_{0};
@@ -107,6 +124,8 @@ private:
std::atomic<double> nav2_last_distance_remaining_{std::numeric_limits<double>::quiet_NaN()};
std::atomic<bool> nav2_last_goal_succeeded_{false};
friend class CerebellumHooks;
// ---- helpers ----
/**
* @brief Try to parse the current goal's per-call YAML payload into a typed object.
@@ -136,26 +155,6 @@ private:
}
return false;
}
/** @brief Install application-specific action hook overrides for skill manager. */
void ConfigureActionHooks();
void ConfigureArmSpaceControlHooks();
void ConfigureArmHooks();
void ConfigureHandControlHooks();
void ConfigureCameraTakePhotoHooks();
void ConfigureLegControlHooks();
void ConfigureVisionGraspObjectHooks();
void ConfigureSlamModeHooks();
void ConfigureNavigateToPoseHooks();
void ConfigureMoveWaistHooks();
void ConfigureMoveLegHooks();
void ConfigureMoveWheelHooks();
void ConfigureMoveHomeHooks();
/** @brief Install application-specific service hook overrides. */
void ConfigureServiceHooks();
void ConfigureVisionObjectRecognitionHooks();
void ConfigureMapLoadHooks();
void ConfigureMapSaveHooks();
/**
* @brief Parse a raw comma / semicolon / whitespace separated list of skill names.

View File

@@ -9,6 +9,8 @@
#include "interfaces/action/arm.hpp"
#include "interfaces/action/arm_space_control.hpp"
#include "interfaces/action/camera_take_photo.hpp"
#include "interfaces/action/execute_bt_action.hpp"
#include "interfaces/action/gripper_cmd.hpp"
#include "interfaces/action/hand_control.hpp"
#include "interfaces/action/leg_control.hpp"
#include "interfaces/action/move_home.hpp"
@@ -18,8 +20,12 @@
#include "interfaces/action/move_wheel.hpp"
#include "interfaces/action/slam_mode.hpp"
#include "interfaces/action/vision_grasp_object.hpp"
#include "interfaces/srv/bt_rebuild.hpp"
#include "interfaces/srv/gripper_cmd.hpp"
#include "interfaces/srv/map_load.hpp"
#include "interfaces/srv/map_save.hpp"
#include "interfaces/srv/motor_info.hpp"
#include "interfaces/srv/motor_param.hpp"
#include "interfaces/srv/vision_object_recognition.hpp"
@@ -153,6 +159,70 @@ inline interfaces::action::MoveHome::Goal from_yaml<interfaces::action::MoveHome
return g;
}
// GripperCmd::Goal: loc, speed, torque, mode
template<>
inline interfaces::action::GripperCmd::Goal from_yaml<interfaces::action::GripperCmd::Goal>(const YAML::Node & n)
{
interfaces::action::GripperCmd::Goal g;
if (n && n.IsMap()) {
if (n["loc"]) g.loc = n["loc"].as<uint8_t>();
if (n["speed"]) g.speed = n["speed"].as<uint8_t>();
if (n["torque"]) g.torque = n["torque"].as<uint8_t>();
if (n["mode"]) g.mode = n["mode"].as<uint8_t>();
}
return g;
}
// ExecuteBtAction::Goal: epoch, action_name, calls
template<>
inline interfaces::action::ExecuteBtAction::Goal from_yaml<interfaces::action::ExecuteBtAction::Goal>(const YAML::Node & n)
{
interfaces::action::ExecuteBtAction::Goal g;
if (n && n.IsMap()) {
if (n["epoch"]) g.epoch = n["epoch"].as<uint32_t>();
if (n["action_name"]) g.action_name = n["action_name"].as<std::string>();
// TODO: parse field `calls` of ROS type `interfaces/SkillCall[]` into g.calls (complex type)
}
return g;
}
// LegControl::Goal: target
template<>
inline interfaces::action::LegControl::Goal from_yaml<interfaces::action::LegControl::Goal>(const YAML::Node & n)
{
interfaces::action::LegControl::Goal g;
if (n && n.IsMap()) {
// geometry_msgs::msg::TwistStamped
if (n) {
if (n["frame_id"]) g.target.header.frame_id = n["frame_id"].as<std::string>();
if (n["stamp_sec"]) g.target.header.stamp.sec = n["stamp_sec"].as<int32_t>();
if (n["stamp_nanosec"]) g.target.header.stamp.nanosec = n["stamp_nanosec"].as<uint32_t>();
if (auto lin = n["linear"]) {
if (lin["x"]) g.target.twist.linear.x = lin["x"].as<double>();
if (lin["y"]) g.target.twist.linear.y = lin["y"].as<double>();
if (lin["z"]) g.target.twist.linear.z = lin["z"].as<double>();
}
if (auto ang = n["angular"]) {
if (ang["x"]) g.target.twist.angular.x = ang["x"].as<double>();
if (ang["y"]) g.target.twist.angular.y = ang["y"].as<double>();
if (ang["z"]) g.target.twist.angular.z = ang["z"].as<double>();
}
}
}
return g;
}
// VisionGraspObject::Goal: object_id
template<>
inline interfaces::action::VisionGraspObject::Goal from_yaml<interfaces::action::VisionGraspObject::Goal>(const YAML::Node & n)
{
interfaces::action::VisionGraspObject::Goal g;
if (n && n.IsMap()) {
if (n["object_id"]) g.object_id = n["object_id"].as<std::string>();
}
return g;
}
// MoveToPosition::Goal: target_x, target_y
template<>
inline interfaces::action::MoveToPosition::Goal from_yaml<interfaces::action::MoveToPosition::Goal>(const YAML::Node & n)
@@ -203,41 +273,61 @@ inline interfaces::action::ArmSpaceControl::Goal from_yaml<interfaces::action::A
return g;
}
// LegControl::Goal: target
// GripperCmd::Request: devid, loc, speed, torque
template<>
inline interfaces::action::LegControl::Goal from_yaml<interfaces::action::LegControl::Goal>(const YAML::Node & n)
inline interfaces::srv::GripperCmd::Request from_yaml<interfaces::srv::GripperCmd::Request>(const YAML::Node & n)
{
interfaces::action::LegControl::Goal g;
interfaces::srv::GripperCmd::Request r;
if (n && n.IsMap()) {
// geometry_msgs::msg::TwistStamped
if (n) {
if (n["frame_id"]) g.target.header.frame_id = n["frame_id"].as<std::string>();
if (n["stamp_sec"]) g.target.header.stamp.sec = n["stamp_sec"].as<int32_t>();
if (n["stamp_nanosec"]) g.target.header.stamp.nanosec = n["stamp_nanosec"].as<uint32_t>();
if (auto lin = n["linear"]) {
if (lin["x"]) g.target.twist.linear.x = lin["x"].as<double>();
if (lin["y"]) g.target.twist.linear.y = lin["y"].as<double>();
if (lin["z"]) g.target.twist.linear.z = lin["z"].as<double>();
}
if (auto ang = n["angular"]) {
if (ang["x"]) g.target.twist.angular.x = ang["x"].as<double>();
if (ang["y"]) g.target.twist.angular.y = ang["y"].as<double>();
if (ang["z"]) g.target.twist.angular.z = ang["z"].as<double>();
}
}
if (n["devid"]) r.devid = n["devid"].as<uint8_t>();
if (n["loc"]) r.loc = n["loc"].as<uint8_t>();
if (n["speed"]) r.speed = n["speed"].as<uint8_t>();
if (n["torque"]) r.torque = n["torque"].as<uint8_t>();
}
return g;
return r;
}
// VisionGraspObject::Goal: object_id
// MotorInfo::Request: target, type, motor_ids
template<>
inline interfaces::action::VisionGraspObject::Goal from_yaml<interfaces::action::VisionGraspObject::Goal>(const YAML::Node & n)
inline interfaces::srv::MotorInfo::Request from_yaml<interfaces::srv::MotorInfo::Request>(const YAML::Node & n)
{
interfaces::action::VisionGraspObject::Goal g;
interfaces::srv::MotorInfo::Request r;
if (n && n.IsMap()) {
if (n["object_id"]) g.object_id = n["object_id"].as<std::string>();
if (n["target"]) r.target = n["target"].as<std::string>();
if (n["type"]) r.type = n["type"].as<std::string>();
if (n["motor_ids"]) {
auto vec = n["motor_ids"].as<std::vector<uint8_t>>();
r.motor_ids.assign(vec.begin(), vec.end());
}
}
return g;
return r;
}
// MotorParam::Request: motor_id, max_speed, add_acc, dec_acc
template<>
inline interfaces::srv::MotorParam::Request from_yaml<interfaces::srv::MotorParam::Request>(const YAML::Node & n)
{
interfaces::srv::MotorParam::Request r;
if (n && n.IsMap()) {
if (n["motor_id"]) r.motor_id = n["motor_id"].as<uint16_t>();
if (n["max_speed"]) r.max_speed = n["max_speed"].as<uint16_t>();
if (n["add_acc"]) r.add_acc = n["add_acc"].as<uint16_t>();
if (n["dec_acc"]) r.dec_acc = n["dec_acc"].as<uint16_t>();
}
return r;
}
// BtRebuild::Request: type, config, param
template<>
inline interfaces::srv::BtRebuild::Request from_yaml<interfaces::srv::BtRebuild::Request>(const YAML::Node & n)
{
interfaces::srv::BtRebuild::Request r;
if (n && n.IsMap()) {
if (n["type"]) r.type = n["type"].as<std::string>();
if (n["config"]) r.config = n["config"].as<std::string>();
if (n["param"]) r.param = n["param"].as<std::string>();
}
return r;
}
} // namespace brain

View File

@@ -6,6 +6,7 @@
#include "interfaces/action/arm.hpp"
#include "interfaces/action/camera_take_photo.hpp"
#include "interfaces/action/gripper_cmd.hpp"
#include "interfaces/action/hand_control.hpp"
#include "interfaces/action/move_home.hpp"
#include "interfaces/action/move_leg.hpp"
@@ -30,7 +31,8 @@ using SkillActionTypes = std::tuple<
interfaces::action::MoveWaist,
interfaces::action::MoveLeg,
interfaces::action::MoveWheel,
interfaces::action::MoveHome
interfaces::action::MoveHome,
interfaces::action::GripperCmd
>;
using SkillServiceTypes = std::tuple<
@@ -70,7 +72,7 @@ template<>
struct SkillActionTrait<interfaces::action::MoveWaist>
{
static constexpr const char * skill_name = "MoveWaist";
static constexpr const char * default_topic = "";
static constexpr const char * default_topic = "MoveWaist";
static bool success(const interfaces::action::MoveWaist::Result & r) {return r.success;}
static std::string message(const interfaces::action::MoveWaist::Result & r) {return r.message;}
};
@@ -79,7 +81,7 @@ template<>
struct SkillActionTrait<interfaces::action::MoveLeg>
{
static constexpr const char * skill_name = "MoveLeg";
static constexpr const char * default_topic = "";
static constexpr const char * default_topic = "MoveLeg";
static bool success(const interfaces::action::MoveLeg::Result & r) {return r.success;}
static std::string message(const interfaces::action::MoveLeg::Result & r) {return r.message;}
};
@@ -88,7 +90,7 @@ template<>
struct SkillActionTrait<interfaces::action::MoveWheel>
{
static constexpr const char * skill_name = "MoveWheel";
static constexpr const char * default_topic = "";
static constexpr const char * default_topic = "MoveWheel";
static bool success(const interfaces::action::MoveWheel::Result & r) {return r.success;}
static std::string message(const interfaces::action::MoveWheel::Result & r) {return r.message;}
};
@@ -97,11 +99,20 @@ template<>
struct SkillActionTrait<interfaces::action::MoveHome>
{
static constexpr const char * skill_name = "MoveHome";
static constexpr const char * default_topic = "";
static constexpr const char * default_topic = "MoveHome";
static bool success(const interfaces::action::MoveHome::Result & r) {return r.success;}
static std::string message(const interfaces::action::MoveHome::Result & r) {return r.message;}
};
template<>
struct SkillActionTrait<interfaces::action::GripperCmd>
{
static constexpr const char * skill_name = "GripperCmd";
static constexpr const char * default_topic = "gripper_cmd0";
static bool success(const interfaces::action::GripperCmd::Result & r) {return (r.result == 1);}
static std::string message(const interfaces::action::GripperCmd::Result & r) {return r.state;}
};
template<>
struct SkillServiceTrait<interfaces::srv::VisionObjectRecognition>
{

View File

@@ -149,10 +149,10 @@ struct StExecuting : smacc2::SmaccState<StExecuting, SmCerebellum>
while (!d.execute_fn) {
std::this_thread::sleep_for(5ms);
auto now = std::chrono::steady_clock::now();
if (now - last_warn > 5s) {
RCLCPP_WARN(this->getLogger(), "[SM] Still waiting for execute_fn to be set...");
last_warn = now;
}
if (now - last_warn > 10s) {
RCLCPP_WARN(this->getLogger(), "[SM] Still waiting for execute_fn to be set...");
last_warn = now;
}
}
try {
auto res = d.execute_fn(this->getLogger());

View File

@@ -0,0 +1,252 @@
#include "brain/calc_grasp_pose.hpp"
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <utility>
#include <string.h>
#include <rclcpp/rclcpp.hpp>
#include <rclcpp/logger.hpp>
#include "arm_action_define.h"
#include "rm_interface.h"
namespace brain {
bool GraspPoseCalculator::rm_arm_algo_init_done_ = false;
GraspPoseCalculator::GraspPoseCalculator()
{
rm_arm_algo_init();
}
tf2::Vector3 normalize_vector(const tf2::Vector3& v) {
if (v.length() == 0) return v;
return v.normalized();
}
GraspResult GraspPoseCalculator::create_grasp_matrix(
const tf2::Vector3& target_pos,
const tf2::Vector3& approach_vec,
const tf2::Vector3& palm_normal_vec,
double safety_dist,
double finger_length
) {
tf2::Vector3 z_axis = normalize_vector(approach_vec);
tf2::Vector3 x_axis = normalize_vector(palm_normal_vec);
tf2::Vector3 y_axis = z_axis.cross(x_axis);
x_axis = y_axis.cross(z_axis);
tf2::Matrix3x3 R_grasp(
x_axis.x(), y_axis.x(), z_axis.x(),
x_axis.y(), y_axis.y(), z_axis.y(),
x_axis.z(), y_axis.z(), z_axis.z()
);
tf2::Transform T_grasp;
T_grasp.setIdentity();
T_grasp.setOrigin(target_pos);
T_grasp.setBasis(R_grasp);
T_grasp.setOrigin(T_grasp.getOrigin() - z_axis * finger_length);
tf2::Transform T_pre = T_grasp;
T_pre.setOrigin(T_pre.getOrigin() - z_axis * safety_dist);
GraspResult result;
result.grasp_pose.position = T_grasp.getOrigin();
result.grasp_pose.orientation = T_grasp.getRotation();
result.pre_grasp_pose.position = T_pre.getOrigin();
result.pre_grasp_pose.orientation = T_pre.getRotation();
return result;
}
GraspResult GraspPoseCalculator::calculate(
const tf2::Vector3& target_pos,
const tf2::Quaternion& target_quat,
const std::string& grasp_type,
double angle_deg,
const std::string& palm_facing,
double safety_dist,
double finger_length,
const std::string& arm
) {
(void)target_quat;
(void)arm;
tf2::Vector3 V_world_up(-1.0, 0.0, 0.0);
tf2::Vector3 V_world_down = -V_world_up;
tf2::Vector3 P_shoulder(0, 0, 0);
tf2::Vector3 V_sb = target_pos - P_shoulder;
tf2::Vector3 V_horiz = V_sb - (V_sb.dot(V_world_up) * V_world_up);
tf2::Vector3 approach_vec;
tf2::Vector3 palm_normal;
std::string name_suffix;
if (grasp_type == "side") {
tf2::Vector3 V_horiz_sb(0, V_sb.y(), V_sb.z());
double base_angle = 0.0;
if (V_horiz_sb.length() >= 1e-3) {
base_angle = std::atan2(V_horiz_sb.z(), V_horiz_sb.y());
}
double rad = angle_deg * M_PI / 180.0;
double angle = base_angle + rad;
double dy = std::cos(angle);
double dz = std::sin(angle);
approach_vec = tf2::Vector3(0, dy, dz); // Horizontal approach
if (palm_facing == "up") {
palm_normal = V_world_up;
name_suffix = "Side_Angle_" + std::to_string((int)angle_deg) + "_Palm_Up";
} else {
palm_normal = V_world_down;
name_suffix = "Side_Angle_" + std::to_string((int)angle_deg) + "_Palm_Down";
}
} else if (grasp_type == "top") {
approach_vec = V_world_down;
palm_normal = V_horiz;
name_suffix = "Top_Grasp";
} else {
approach_vec = V_horiz;
palm_normal = V_world_down;
name_suffix = "Side_Default";
}
GraspResult result = create_grasp_matrix(target_pos, approach_vec, palm_normal, safety_dist, finger_length);
result.name = name_suffix;
return result;
}
double GraspPoseCalculator::evaluate_grasp_quality(
const tf2::Vector3& target_pos,
double angle_deg,
const std::string& arm
) {
double score = 0.0;
bool is_right = (arm.find("right") != std::string::npos);
bool is_outward = (is_right && angle_deg < 0) || (!is_right && angle_deg > 0);
if (is_outward) {
score += 50.0;
} else if (std::abs(angle_deg) < 1e-3) {
score += 10.0;
} else {
score -= 50.0;
}
double abs_angle = std::abs(angle_deg);
if (abs_angle < 10.0) {
score -= 20.0;
} else if (abs_angle >= 20.0 && abs_angle <= 50.0) {
score += 30.0;
} else if (abs_angle > 60.0) {
score -= 20.0;
}
tf2::Vector3 P_shoulder(0, 0, 0);
tf2::Vector3 V_sb = target_pos - P_shoulder;
tf2::Vector3 V_horiz_sb(0, V_sb.y(), V_sb.z());
double base_angle = std::atan2(V_horiz_sb.z(), V_horiz_sb.y());
double rad = angle_deg * M_PI / 180.0;
double angle = base_angle + rad;
tf2::Vector3 approach_vec(0, std::cos(angle), std::sin(angle));
tf2::Vector3 wrist_pos = target_pos - approach_vec * 0.2;
double dist_shoulder_wrist = wrist_pos.length();
if (dist_shoulder_wrist > 0.8) {
score -= 100.0;
} else if (dist_shoulder_wrist < 0.2) {
score -= 100.0;
} else if (dist_shoulder_wrist >= 0.35 && dist_shoulder_wrist <= 0.65) {
score += 20.0;
}
return score;
}
std::vector<double> GraspPoseCalculator::find_optimal_grasp_angles(
const tf2::Vector3& target_pos,
const std::string& arm,
int top_k
) {
std::vector<std::pair<double, double>> scored_angles;
std::vector<double> angles;
bool is_right = (arm.find("right") != std::string::npos);
if (is_right) {
for (double a = 0.0; a >= -60.0; a -= 5.0) angles.push_back(a);
} else {
for (double a = 0.0; a <= 60.0; a += 5.0) angles.push_back(a);
}
for (double angle : angles) {
double score = evaluate_grasp_quality(target_pos, angle, arm);
scored_angles.push_back({score, angle});
}
std::sort(scored_angles.begin(), scored_angles.end(),
[](const std::pair<double, double>& a, const std::pair<double, double>& b) {
return a.first > b.first;
}
);
std::vector<double> result;
for (int i = 0; i < std::min((int)scored_angles.size(), top_k); ++i) {
result.push_back(scored_angles[i].second);
}
return result;
}
void GraspPoseCalculator::rm_arm_algo_init(void)
{
rm_robot_arm_model_e Mode = RM_MODEL_RM_65_E;
rm_force_type_e Type = RM_MODEL_RM_B_E;
rm_algo_init_sys_data(Mode, Type);
rm_arm_algo_init_done_ = true;
}
int GraspPoseCalculator::rm_arm_inverse_kinematics(
const tf2::Vector3& target_pos,
const tf2::Quaternion& target_quat,
std::vector<double>& joint_angles
) {
if (!rm_arm_algo_init_done_) {
rm_arm_algo_init();
}
float joints[ARM_DOF] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
if (joint_angles.size() < USED_ARM_DOF) {
joint_angles.resize(USED_ARM_DOF, 0.0);
}
for (int i = 0; i < USED_ARM_DOF; i++) {
joints[i] = static_cast<float>(joint_angles[i]);
}
rm_inverse_kinematics_params_t ik_params;
memcpy(ik_params.q_in, joints, sizeof(joints));
ik_params.q_pose.position.x = target_pos.x();
ik_params.q_pose.position.y = target_pos.y();
ik_params.q_pose.position.z = target_pos.z();
ik_params.q_pose.quaternion.x = target_quat.x();
ik_params.q_pose.quaternion.y = target_quat.y();
ik_params.q_pose.quaternion.z = target_quat.z();
ik_params.q_pose.quaternion.w = target_quat.w();
ik_params.flag = 0;
#ifdef RM_ALGO_IK_DEBUG
RCLCPP_INFO(rclcpp::get_logger("GraspPoseCalculator"),
"IK Target Pos: [%.4f, %.4f, %.4f], Quat: [%.4f, %.4f, %.4f, %.4f]",
ik_params.q_pose.position.x, ik_params.q_pose.position.y, ik_params.q_pose.position.z,
ik_params.q_pose.quaternion.x, ik_params.q_pose.quaternion.y,
ik_params.q_pose.quaternion.z, ik_params.q_pose.quaternion.w);
RCLCPP_INFO(rclcpp::get_logger("GraspPoseCalculator"),
"IK Input Joints: [%.4f, %.4f, %.4f, %.4f, %.4f, %.4f]",
joints[0], joints[1], joints[2], joints[3], joints[4], joints[5]);
#endif
int result = rm_algo_inverse_kinematics(NULL, ik_params, joints);
if (result != 0) {
RCLCPP_ERROR(rclcpp::get_logger("GraspPoseCalculator"), "Inverse kinematics failed with error code %d", result);
return ARM_AIM_CANNOT_REACH;
}
for (int i = 0; i < USED_ARM_DOF; i++) {
joint_angles[i] = static_cast<double>(joints[i]);
}
return 0;
}
} // namespace brain

View File

@@ -0,0 +1,793 @@
#include "brain/cerebellum_hooks.hpp"
#include "brain/cerebellum_node.hpp"
#include "brain/payload_converters.hpp"
#include "arm_action_define.h"
#include <rclcpp/rclcpp.hpp>
#include <sstream>
#include <iomanip>
#include <mutex>
namespace brain
{
void CerebellumHooks::ConfigureActionHooks(CerebellumNode * node)
{
if (!node->skill_manager_) {
RCLCPP_WARN(node->get_logger(), "SkillManager unavailable, skipping action hook configuration");
return;
}
ConfigureArmHooks(node);
ConfigureHandControlHooks(node);
ConfigureCameraTakePhotoHooks(node);
ConfigureMoveWaistHooks(node);
ConfigureMoveLegHooks(node);
ConfigureMoveWheelHooks(node);
ConfigureMoveHomeHooks(node);
ConfigureGripperCmdControlHooks(node);
}
void CerebellumHooks::ConfigureArmSpaceControlHooks(CerebellumNode * node)
{
(void)node;
}
void CerebellumHooks::ConfigureArmHooks(CerebellumNode * node)
{
// Arm
SkillManager::ActionHookOptions<interfaces::action::Arm> hooks;
hooks.make_goal = [node](const std::string & skill_name) {
interfaces::action::Arm::Goal goal{};
if (!node->TryParseCallPayload<interfaces::action::Arm::Goal>(skill_name, goal)) {
RCLCPP_ERROR(node->get_logger(), "Failed to parse call payload for skill %s", skill_name.c_str());
return goal;
}
RCLCPP_INFO(node->get_logger(), "[%s]Received Parse Arm goal: body_id=%d, data_type=%d, data_length=%d, command_id=%ld, frame_time_stamp=%ld",
skill_name.c_str(), goal.body_id, goal.data_type, goal.data_length, goal.command_id, goal.frame_time_stamp);
goal.body_id = (CerebellumNode::tls_arm_body_id == LEFT_ARM ||
CerebellumNode::tls_arm_body_id == RIGHT_ARM) ? CerebellumNode::tls_arm_body_id : goal.body_id;
goal.command_id = ++node->command_id_;
if (goal.body_id == LEFT_ARM) {
node->command_id_left_arm_ = goal.command_id;
} else if (goal.body_id == RIGHT_ARM) {
node->command_id_right_arm_ = goal.command_id;
}
goal.frame_time_stamp = node->get_clock()->now().nanoseconds();
if (goal.data_type == ARM_COMMAND_TYPE_POSE_DIRECT_MOVE) {
if (goal.data_length == POSE_DIMENSION*2 && goal.data_array.size() == POSE_DIMENSION*2) {
if (goal.body_id == LEFT_ARM) {
goal.data_array = {goal.data_array[0], goal.data_array[1], goal.data_array[2],
goal.data_array[3], goal.data_array[4], goal.data_array[5], goal.data_array[6]};
} else if (goal.body_id == RIGHT_ARM) {
goal.data_array = {goal.data_array[7], goal.data_array[8], goal.data_array[9],
goal.data_array[10], goal.data_array[11], goal.data_array[12], goal.data_array[13]};
}
goal.data_length = POSE_DIMENSION;
}
} else if (goal.data_type == ARM_COMMAND_TYPE_ANGLE_DIRECT_MOVE) {
if (goal.data_length == USED_ARM_DOF*2 && goal.data_array.size() == USED_ARM_DOF*2) {
if (goal.body_id == LEFT_ARM) {
goal.data_array = {goal.data_array[0], goal.data_array[1], goal.data_array[2],
goal.data_array[3], goal.data_array[4], goal.data_array[5]};
} else if (goal.body_id == RIGHT_ARM) {
goal.data_array = {goal.data_array[6], goal.data_array[7], goal.data_array[8], goal.data_array[9],
goal.data_array[10], goal.data_array[11]};
}
goal.data_length = USED_ARM_DOF;
}
} else {
RCLCPP_ERROR(node->get_logger(), "ARM make goal error: data_type[%d] not support", goal.data_type);
return goal;
}
if (goal.data_array.size() >= USED_ARM_DOF) {
RCLCPP_INFO(node->get_logger(), "[%s] Control params: body_id=%d, data_type=%d, data_length=%d, command_id=%ld, frame_time_stamp=%ld, data_array=[%.6f, %.6f, %.6f, %.6f, %.6f, %.6f]",
skill_name.c_str(), goal.body_id, goal.data_type, goal.data_length, goal.command_id, goal.frame_time_stamp,
goal.data_array[0], goal.data_array[1], goal.data_array[2], goal.data_array[3], goal.data_array[4], goal.data_array[5]);
} else {
RCLCPP_ERROR(node->get_logger(), "ARM make goal error: invalid data_array size[%ld]", goal.data_array.size());
}
return goal;
};
hooks.on_feedback = [node](
const std::string & skill_name,
ActionClientRegistry::GoalHandle<interfaces::action::Arm>::SharedPtr,
const std::shared_ptr<const interfaces::action::Arm::Feedback> & feedback) {
if (!feedback) {
RCLCPP_WARN(node->get_logger(), "[%s] feedback is empty", skill_name.c_str());
return;
}
(void)feedback;
};
hooks.on_result = [node](
const std::string & skill_name,
const ActionClientRegistry::GoalHandle<interfaces::action::Arm>::WrappedResult & res) {
const bool success = (res.code == rclcpp_action::ResultCode::SUCCEEDED && res.result && res.result->result == OK);
const std::string message = res.result ? std::string("action end") : std::string("No return information");
geometry_msgs::msg::Pose pose = res.result->pose;
std::string res_arm_name = "";
if (res.result->command_id == node->command_id_left_arm_) {
node->final_arm_pose_["left_arm"] = pose;
res_arm_name = "Left Arm";
} else if (res.result->command_id == node->command_id_right_arm_) {
node->final_arm_pose_["right_arm"] = pose;
res_arm_name = "Right Arm";
}
RCLCPP_WARN(node->get_logger(), "[%s] %s Final position: %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f",
skill_name.c_str(), res_arm_name.c_str(), pose.position.x, pose.position.y, pose.position.z,
pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w);
if (success) {
RCLCPP_INFO(node->get_logger(), "[%s] %s command_id %ld completed: %s",
skill_name.c_str(), res_arm_name.c_str(), res.result->command_id, message.c_str());
} else if (res.code == rclcpp_action::ResultCode::CANCELED) {
RCLCPP_WARN(node->get_logger(), "[%s] was cancelled", skill_name.c_str());
} else {
RCLCPP_ERROR(node->get_logger(), "[%s] failed (code=%d): %s",
skill_name.c_str(), static_cast<int>(res.code), message.c_str());
}
};
hooks.on_goal_response = [node](
const std::string & skill_name,
const std::shared_ptr<ActionClientRegistry::GoalHandle<interfaces::action::Arm>> & handle) {
if (static_cast<bool>(handle)) {
RCLCPP_INFO(node->get_logger(), "[%s] goal has been accepted by server", skill_name.c_str());
} else {
RCLCPP_WARN(node->get_logger(), "[%s] goal was rejected by server", skill_name.c_str());
}
};
node->skill_manager_->configure_action_hooks<interfaces::action::Arm>("Arm", std::move(hooks));
}
void CerebellumHooks::ConfigureHandControlHooks(CerebellumNode * node)
{
// HandControl
SkillManager::ActionHookOptions<interfaces::action::HandControl> hooks;
hooks.make_goal = [node](const std::string & skill_name) {
interfaces::action::HandControl::Goal goal{};
// Plan A: per-call YAML overrides
if (!node->TryParseCallPayload<interfaces::action::HandControl::Goal>(skill_name, goal)) {
RCLCPP_ERROR(node->get_logger(), "Failed to parse call payload for skill %s", skill_name.c_str());
return goal;
}
const std::string param = "hand_control.target_pose";
if (!node->has_parameter(param)) {
node->declare_parameter<std::vector<double>>(param, {0.0, 0.0, 0.0});
}
const auto values = node->get_parameter(param).as_double_array();
RCLCPP_INFO(node->get_logger(), "[%s] Target parameters size=%zu", skill_name.c_str(), values.size());
(void)values;
goal.mode = 1;
return goal;
};
hooks.on_feedback = [node](
const std::string & skill_name,
ActionClientRegistry::GoalHandle<interfaces::action::HandControl>::SharedPtr,
const std::shared_ptr<const interfaces::action::HandControl::Feedback> & feedback) {
if (!feedback) {return;}
(void)feedback;
RCLCPP_INFO(node->get_logger(), "[%s] Current progress: %.2f", skill_name.c_str(), feedback->progress);
};
hooks.on_result = [node](
const std::string & skill_name,
const ActionClientRegistry::GoalHandle<interfaces::action::HandControl>::WrappedResult & res) {
const bool success = res.code == rclcpp_action::ResultCode::SUCCEEDED && res.result && res.result->success;
const std::string message = res.result ? res.result->message : std::string("No return information");
if (success) {
RCLCPP_INFO(node->get_logger(), "[%s] Success: %s", skill_name.c_str(), message.c_str());
} else if (res.code == rclcpp_action::ResultCode::CANCELED) {
RCLCPP_WARN(node->get_logger(), "[%s] was cancelled", skill_name.c_str());
} else {
RCLCPP_ERROR(node->get_logger(), "[%s] failed (code=%d): %s",
skill_name.c_str(), static_cast<int>(res.code), message.c_str());
}
};
hooks.on_goal_response = [node](
const std::string & skill_name,
const std::shared_ptr<ActionClientRegistry::GoalHandle<interfaces::action::HandControl>> & handle) {
if (static_cast<bool>(handle)) {
RCLCPP_INFO(node->get_logger(), "[%s] goal has been accepted by server", skill_name.c_str());
} else {
RCLCPP_WARN(node->get_logger(), "[%s] goal was rejected by server", skill_name.c_str());
}
};
node->skill_manager_->configure_action_hooks<interfaces::action::HandControl>("HandControl", std::move(hooks));
}
void CerebellumHooks::ConfigureCameraTakePhotoHooks(CerebellumNode * node)
{
// CameraTakePhoto
SkillManager::ActionHookOptions<interfaces::action::CameraTakePhoto> hooks;
hooks.make_goal = [node](const std::string & skill_name) {
interfaces::action::CameraTakePhoto::Goal goal{};
if (!node->TryParseCallPayload<interfaces::action::CameraTakePhoto::Goal>(skill_name, goal)) {
RCLCPP_ERROR(node->get_logger(), "Failed to parse call payload for skill %s", skill_name.c_str());
return goal;
}
return goal;
};
hooks.on_result = [node](
const std::string & skill_name,
const ActionClientRegistry::GoalHandle<interfaces::action::CameraTakePhoto>::WrappedResult & res) {
const bool success = res.code == rclcpp_action::ResultCode::SUCCEEDED && res.result && res.result->success;
const std::string message = res.result ? res.result->message : std::string("No return information");
if (success) {
RCLCPP_INFO(node->get_logger(), "[%s] Success: %s", skill_name.c_str(), message.c_str());
} else if (res.code == rclcpp_action::ResultCode::CANCELED) {
RCLCPP_WARN(node->get_logger(), "[%s] was cancelled", skill_name.c_str());
} else {
RCLCPP_ERROR(node->get_logger(), "[%s] failed (code=%d): %s",
skill_name.c_str(), static_cast<int>(res.code), message.c_str());
}
};
hooks.on_goal_response = [node](
const std::string & skill_name,
const std::shared_ptr<ActionClientRegistry::GoalHandle<interfaces::action::CameraTakePhoto>> & handle) {
if (static_cast<bool>(handle)) {
RCLCPP_INFO(node->get_logger(), "[%s] goal has been accepted by server", skill_name.c_str());
} else {
RCLCPP_WARN(node->get_logger(), "[%s] goal was rejected by server", skill_name.c_str());
}
};
node->skill_manager_->configure_action_hooks<interfaces::action::CameraTakePhoto>("CameraTakePhoto", std::move(hooks));
}
void CerebellumHooks::ConfigureLegControlHooks(CerebellumNode * node)
{
(void)node;
}
void CerebellumHooks::ConfigureVisionGraspObjectHooks(CerebellumNode * node)
{
(void)node;
}
void CerebellumHooks::ConfigureSlamModeHooks(CerebellumNode * node)
{
(void)node;
}
void CerebellumHooks::ConfigureNavigateToPoseHooks(CerebellumNode * node)
{
(void)node;
}
void CerebellumHooks::ConfigureMoveWaistHooks(CerebellumNode * node)
{
// MoveWaist
SkillManager::ActionHookOptions<interfaces::action::MoveWaist> hooks;
hooks.make_goal = [node](const std::string & skill_name) {
interfaces::action::MoveWaist::Goal goal{};
// Plan A: load per-call YAML payload into goal if present
if (!node->TryParseCallPayload<interfaces::action::MoveWaist::Goal>(skill_name, goal)) {
RCLCPP_INFO(node->get_logger(), "Loaded MoveWaist goal from YAML payload");
return goal;
}
RCLCPP_INFO(node->get_logger(), "[%s] move_pitch_degree: %f, move_yaw_degree: %f",
skill_name.c_str(), goal.move_pitch_degree, goal.move_yaw_degree);
return goal;
};
hooks.on_feedback = [node](
const std::string & skill_name,
ActionClientRegistry::GoalHandle<interfaces::action::MoveWaist>::SharedPtr,
const std::shared_ptr<const interfaces::action::MoveWaist::Feedback> & feedback) {
if (!feedback) {return;}
(void)skill_name;
};
hooks.on_result = [node](
const std::string & skill_name,
const ActionClientRegistry::GoalHandle<interfaces::action::MoveWaist>::WrappedResult & res) {
const bool success = res.code == rclcpp_action::ResultCode::SUCCEEDED && res.result && res.result->success;
const std::string message = res.result ? res.result->message : std::string("No return information");
if (success) {
RCLCPP_INFO(node->get_logger(), "[%s] Success: %s", skill_name.c_str(), message.c_str());
} else if (res.code == rclcpp_action::ResultCode::CANCELED) {
RCLCPP_WARN(node->get_logger(), "[%s] was cancelled", skill_name.c_str());
} else {
RCLCPP_ERROR(node->get_logger(), "[%s] failed (code=%d): %s",
skill_name.c_str(), static_cast<int>(res.code), message.c_str());
}
};
hooks.on_goal_response = [node](
const std::string & skill_name,
const std::shared_ptr<ActionClientRegistry::GoalHandle<interfaces::action::MoveWaist>> & handle) {
if (static_cast<bool>(handle)) {
RCLCPP_INFO(node->get_logger(), "[%s] goal has been accepted by server", skill_name.c_str());
} else {
RCLCPP_WARN(node->get_logger(), "[%s] goal was rejected by server", skill_name.c_str());
}
};
node->skill_manager_->configure_action_hooks<interfaces::action::MoveWaist>("MoveWaist", std::move(hooks));
}
void CerebellumHooks::ConfigureMoveLegHooks(CerebellumNode * node)
{
// MoveLeg
SkillManager::ActionHookOptions<interfaces::action::MoveLeg> hooks;
hooks.make_goal = [node](const std::string & skill_name) {
interfaces::action::MoveLeg::Goal goal{};
if (!node->TryParseCallPayload<interfaces::action::MoveLeg::Goal>(skill_name, goal)) {
RCLCPP_ERROR(node->get_logger(), "Failed to parse call payload for skill %s", skill_name.c_str());
return goal;
}
return goal;
};
hooks.on_feedback = [node](
const std::string & skill_name,
ActionClientRegistry::GoalHandle<interfaces::action::MoveLeg>::SharedPtr,
const std::shared_ptr<const interfaces::action::MoveLeg::Feedback> & feedback) {
if (!feedback) {return;}
(void)skill_name;
};
hooks.on_result = [node](
const std::string & skill_name,
const ActionClientRegistry::GoalHandle<interfaces::action::MoveLeg>::WrappedResult & res) {
const bool success = res.code == rclcpp_action::ResultCode::SUCCEEDED && res.result && res.result->success;
const std::string message = res.result ? res.result->message : std::string("No return information");
if (success) {
RCLCPP_INFO(node->get_logger(), "[%s] Success: %s", skill_name.c_str(), message.c_str());
} else if (res.code == rclcpp_action::ResultCode::CANCELED) {
RCLCPP_WARN(node->get_logger(), "[%s] was cancelled", skill_name.c_str());
} else {
RCLCPP_ERROR(node->get_logger(), "[%s] failed (code=%d): %s",
skill_name.c_str(), static_cast<int>(res.code), message.c_str());
}
};
hooks.on_goal_response = [node](
const std::string & skill_name,
const std::shared_ptr<ActionClientRegistry::GoalHandle<interfaces::action::MoveLeg>> & handle) {
if (static_cast<bool>(handle)) {
RCLCPP_INFO(node->get_logger(), "[%s] goal has been accepted by server", skill_name.c_str());
} else {
RCLCPP_WARN(node->get_logger(), "[%s] goal was rejected by server", skill_name.c_str());
}
};
node->skill_manager_->configure_action_hooks<interfaces::action::MoveLeg>("MoveLeg", std::move(hooks));
}
void CerebellumHooks::ConfigureMoveWheelHooks(CerebellumNode * node)
{
// MoveWheel
SkillManager::ActionHookOptions<interfaces::action::MoveWheel> hooks;
hooks.make_goal = [node](const std::string & skill_name) {
interfaces::action::MoveWheel::Goal goal{};
// Plan A: per-call YAML overrides
if (!node->TryParseCallPayload<interfaces::action::MoveWheel::Goal>(skill_name, goal)) {
RCLCPP_ERROR(node->get_logger(), "Failed to parse call payload for skill %s", skill_name.c_str());
return goal;
}
RCLCPP_INFO(node->get_logger(), "[%s] move_distance=%f, move_angle=%f",
skill_name.c_str(), goal.move_distance, goal.move_angle);
return goal;
};
hooks.on_feedback = [node](
const std::string & skill_name,
ActionClientRegistry::GoalHandle<interfaces::action::MoveWheel>::SharedPtr,
const std::shared_ptr<const interfaces::action::MoveWheel::Feedback> & feedback) {
if (!feedback) {return;}
(void)skill_name;
};
hooks.on_result = [node](
const std::string & skill_name,
const ActionClientRegistry::GoalHandle<interfaces::action::MoveWheel>::WrappedResult & res) {
const bool success = res.code == rclcpp_action::ResultCode::SUCCEEDED && res.result && res.result->success;
const std::string message = res.result ? res.result->message : std::string("No return information");
if (success) {
RCLCPP_INFO(node->get_logger(), "[%s] Success: %s", skill_name.c_str(), message.c_str());
} else if (res.code == rclcpp_action::ResultCode::CANCELED) {
RCLCPP_WARN(node->get_logger(), "[%s] was cancelled", skill_name.c_str());
} else {
RCLCPP_ERROR(node->get_logger(), "[%s] failed (code=%d): %s",
skill_name.c_str(), static_cast<int>(res.code), message.c_str());
}
};
hooks.on_goal_response = [node](
const std::string & skill_name,
const std::shared_ptr<ActionClientRegistry::GoalHandle<interfaces::action::MoveWheel>> & handle) {
if (static_cast<bool>(handle)) {
RCLCPP_INFO(node->get_logger(), "[%s] goal has been accepted by server", skill_name.c_str());
} else {
RCLCPP_WARN(node->get_logger(), "[%s] goal was rejected by server", skill_name.c_str());
}
};
node->skill_manager_->configure_action_hooks<interfaces::action::MoveWheel>("MoveWheel", std::move(hooks));
}
void CerebellumHooks::ConfigureMoveHomeHooks(CerebellumNode * node)
{
// MoveHome
SkillManager::ActionHookOptions<interfaces::action::MoveHome> hooks;
hooks.make_goal = [node](const std::string & skill_name) {
interfaces::action::MoveHome::Goal goal{};
// Plan A: per-call YAML overrides (even though goal is empty)
if (!node->TryParseCallPayload<interfaces::action::MoveHome::Goal>(skill_name, goal)) {
RCLCPP_ERROR(node->get_logger(), "Failed to parse call payload for skill %s", skill_name.c_str());
return goal;
}
return goal;
};
hooks.on_feedback = [node](
const std::string & skill_name,
ActionClientRegistry::GoalHandle<interfaces::action::MoveHome>::SharedPtr,
const std::shared_ptr<const interfaces::action::MoveHome::Feedback> & feedback) {
if (!feedback) {return;}
(void)skill_name;
};
hooks.on_result = [node](
const std::string & skill_name,
const ActionClientRegistry::GoalHandle<interfaces::action::MoveHome>::WrappedResult & res) {
const bool success = res.code == rclcpp_action::ResultCode::SUCCEEDED && res.result && res.result->success;
const std::string message = res.result ? res.result->message : std::string("No return information");
if (success) {
RCLCPP_INFO(node->get_logger(), "[%s] Success: %s", skill_name.c_str(), message.c_str());
} else if (res.code == rclcpp_action::ResultCode::CANCELED) {
RCLCPP_WARN(node->get_logger(), "[%s] was cancelled", skill_name.c_str());
} else {
RCLCPP_ERROR(node->get_logger(), "[%s] failed (code=%d): %s",
skill_name.c_str(), static_cast<int>(res.code), message.c_str());
}
};
hooks.on_goal_response = [node](
const std::string & skill_name,
const std::shared_ptr<ActionClientRegistry::GoalHandle<interfaces::action::MoveHome>> & handle) {
if (static_cast<bool>(handle)) {
RCLCPP_INFO(node->get_logger(), "[%s] goal has been accepted by server", skill_name.c_str());
} else {
RCLCPP_WARN(node->get_logger(), "[%s] goal was rejected by server", skill_name.c_str());
}
};
node->skill_manager_->configure_action_hooks<interfaces::action::MoveHome>("MoveHome", std::move(hooks));
}
void CerebellumHooks::ConfigureGripperCmdControlHooks(CerebellumNode * node)
{
// GripperCmd
SkillManager::ActionHookOptions<interfaces::action::GripperCmd> hooks;
hooks.make_goal = [node](const std::string & skill_name) {
interfaces::action::GripperCmd::Goal goal{};
// Plan A: per-call YAML overrides
if (!node->TryParseCallPayload<interfaces::action::GripperCmd::Goal>(skill_name, goal)) {
RCLCPP_ERROR(node->get_logger(), "Failed to parse call payload for skill %s", skill_name.c_str());
return goal;
}
// goal.loc = 30; //grab_width_; //TODO transfer
RCLCPP_INFO(node->get_logger(), "[%s] action make_goal params, loc: %d, speed: %d, torque: %d, mode: %d",
skill_name.c_str(), goal.loc, goal.speed, goal.torque, goal.mode);
return goal;
};
hooks.on_feedback = [node](
const std::string & skill_name,
ActionClientRegistry::GoalHandle<interfaces::action::GripperCmd>::SharedPtr,
const std::shared_ptr<const interfaces::action::GripperCmd::Feedback> & feedback) {
if (!feedback) {return;}
RCLCPP_INFO(node->get_logger(), "[%s] action feedback, loc: %d, speed: %d, torque: %d",
skill_name.c_str(), feedback->loc, feedback->speed, feedback->torque);
};
hooks.on_result = [node](
const std::string & skill_name,
const ActionClientRegistry::GoalHandle<interfaces::action::GripperCmd>::WrappedResult & res) {
if (res.code == rclcpp_action::ResultCode::CANCELED) {
RCLCPP_WARN(node->get_logger(), "[%s] was cancelled", skill_name.c_str());
} else if (res.code == rclcpp_action::ResultCode::SUCCEEDED) {
RCLCPP_INFO(node->get_logger(), "[%s] action on_result, state: %s",
skill_name.c_str(), res.result->state.c_str());
} else {
RCLCPP_ERROR(node->get_logger(), "[%s] was failed(code=%d)", skill_name.c_str(), static_cast<int>(res.code));
}
};
hooks.on_goal_response = [node](
const std::string & skill_name,
const std::shared_ptr<ActionClientRegistry::GoalHandle<interfaces::action::GripperCmd>> & handle) {
if (static_cast<bool>(handle)) {
RCLCPP_INFO(node->get_logger(), "[%s] goal has been accepted by server", skill_name.c_str());
} else {
RCLCPP_WARN(node->get_logger(), "[%s] goal was rejected by server", skill_name.c_str());
}
};
node->skill_manager_->configure_action_hooks<interfaces::action::GripperCmd>("GripperCmd", std::move(hooks));
}
#define DEBUG_GRASP_POSE_CALCULATION
void CerebellumHooks::ConfigureServiceHooks(CerebellumNode * node)
{
if (!node->skill_manager_) {
RCLCPP_WARN(node->get_logger(), "SkillManager unavailable, skipping service hook configuration");
return;
}
ConfigureVisionObjectRecognitionHooks(node);
ConfigureMapLoadHooks(node);
ConfigureMapSaveHooks(node);
}
void CerebellumHooks::ConfigureVisionObjectRecognitionHooks(CerebellumNode * node)
{
using VisionSrv = interfaces::srv::VisionObjectRecognition;
auto UpdateTargetPose = [node](const interfaces::msg::PoseClassAndID & obj, const std_msgs::msg::Header & header) -> bool {
if (node->target_frame_ == obj.class_name) {
node->target_pose_[node->target_frame_].header = header;
node->target_pose_[node->target_frame_].header.frame_id = "RightLink6";
node->target_pose_[node->target_frame_].pose = obj.pose;
// node->target_pose_[node->target_frame_].pose.position.x -= obj.grab_width / 2;
// node->target_pose_[node->target_frame_].pose.position.z -= 0.175;
node->grab_width_ = obj.grab_width;
RCLCPP_INFO(node->get_logger(), "target_frame_: %s, grab_width_: %f, updated target_pose", node->target_frame_.c_str(), node->grab_width_);
return true;
} else {
RCLCPP_WARN(node->get_logger(), "object class_name[%s] not match target_frame_[%s]",
obj.class_name.c_str(), node->target_frame_.c_str());
return false;
}
};
auto CalculateGraspPoses = [node](const std::string & skill_name) -> bool {
const auto & p = node->target_pose_[node->target_frame_].pose;
RCLCPP_INFO(node->get_logger(), "target_pose: %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f",
p.position.x, p.position.y, p.position.z, p.orientation.x, p.orientation.y, p.orientation.z, p.orientation.w);
geometry_msgs::msg::PoseStamped pose_in_arm;
const bool transformed = node->TransformPoseToArmFrame(node->target_pose_[node->target_frame_], pose_in_arm);
if (!transformed) {
RCLCPP_WARN(node->get_logger(), "[%s] coordinate transformation failed, continuing with %s frame data",
skill_name.c_str(), pose_in_arm.header.frame_id.c_str());
return false;
}
{
RCLCPP_INFO(node->get_logger(), "[%s] target pose transformed to %s frame", skill_name.c_str(), node->arm_target_frame_.c_str());
RCLCPP_INFO(node->get_logger(), "%.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f",
pose_in_arm.pose.position.x, pose_in_arm.pose.position.y, pose_in_arm.pose.position.z,
pose_in_arm.pose.orientation.x, pose_in_arm.pose.orientation.y, pose_in_arm.pose.orientation.z, pose_in_arm.pose.orientation.w);
tf2::Vector3 target_pos(pose_in_arm.pose.position.x, pose_in_arm.pose.position.y, pose_in_arm.pose.position.z);
tf2::Quaternion target_quat(pose_in_arm.pose.orientation.x, pose_in_arm.pose.orientation.y, pose_in_arm.pose.orientation.z, pose_in_arm.pose.orientation.w);
std::vector<double> best_angles = brain::GraspPoseCalculator::find_optimal_grasp_angles(target_pos, node->arm_target_frame_, 3);
if (best_angles.empty()) {
RCLCPP_WARN(node->get_logger(), "[%s]no valid grasp angles found", skill_name.c_str());
return false;
}
best_angles.clear();
best_angles.push_back(0);
{
std::vector<std::string> palm_facings = {"up"}; //{"up", "down"};
std::vector<double> current_joint_angles;
{
std::lock_guard<std::mutex> lk(node->joint_state_mutex_);
if ((node->arm_target_frame_.find("right") != std::string::npos)) {
current_joint_angles = node->right_arm_joint_angles_;
} else {
current_joint_angles = node->left_arm_joint_angles_;
}
}
for (double angle : best_angles) {
for (const auto& palm : palm_facings) {
auto result = brain::GraspPoseCalculator::calculate(target_pos,
target_quat, "side", angle, palm, 0.06, 0.175, node->arm_target_frame_);
geometry_msgs::msg::Pose pre_grasp_msg;
pre_grasp_msg.position.x = result.pre_grasp_pose.position.x();
pre_grasp_msg.position.y = result.pre_grasp_pose.position.y();
pre_grasp_msg.position.z = result.pre_grasp_pose.position.z();
pre_grasp_msg.orientation.x = result.pre_grasp_pose.orientation.x();
pre_grasp_msg.orientation.y = result.pre_grasp_pose.orientation.y();
pre_grasp_msg.orientation.z = result.pre_grasp_pose.orientation.z();
pre_grasp_msg.orientation.w = result.pre_grasp_pose.orientation.w();
std::vector<double> pre_grasp_joint_angles = current_joint_angles;
std::vector<double> grasp_joint_angles = current_joint_angles;
#ifdef DEBUG_GRASP_POSE_CALCULATION
RCLCPP_INFO(node->get_logger(), "[%s] angle %f, palm %s, pre_grasp_pose[%.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f]",
skill_name.c_str(), angle, palm.c_str(),
result.pre_grasp_pose.position.x(), result.pre_grasp_pose.position.y(), result.pre_grasp_pose.position.z(),
result.pre_grasp_pose.orientation.x(), result.pre_grasp_pose.orientation.y(), result.pre_grasp_pose.orientation.z(), result.pre_grasp_pose.orientation.w());
#endif
if (brain::GraspPoseCalculator::rm_arm_inverse_kinematics(result.pre_grasp_pose.position,
result.pre_grasp_pose.orientation, pre_grasp_joint_angles) != 0) {
RCLCPP_WARN(node->get_logger(), "[%s] angle %f, palm %s pre_grasp_pose rm arm inverse kinematics failed.",
skill_name.c_str(), angle, palm.c_str());
continue;
}
geometry_msgs::msg::Pose grasp_msg;
grasp_msg.position.x = result.grasp_pose.position.x();
grasp_msg.position.y = result.grasp_pose.position.y();
grasp_msg.position.z = result.grasp_pose.position.z();
grasp_msg.orientation.x = result.grasp_pose.orientation.x();
grasp_msg.orientation.y = result.grasp_pose.orientation.y();
grasp_msg.orientation.z = result.grasp_pose.orientation.z();
grasp_msg.orientation.w = result.grasp_pose.orientation.w();
#ifdef DEBUG_GRASP_POSE_CALCULATION
RCLCPP_INFO(node->get_logger(), "[%s] angle %f, palm %s, grasp_pose[%.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f]",
skill_name.c_str(), angle, palm.c_str(),
result.grasp_pose.position.x(), result.grasp_pose.position.y(), result.grasp_pose.position.z(),
result.grasp_pose.orientation.x(), result.grasp_pose.orientation.y(), result.grasp_pose.orientation.z(), result.grasp_pose.orientation.w());
#endif
if (brain::GraspPoseCalculator::rm_arm_inverse_kinematics(result.grasp_pose.position,
result.grasp_pose.orientation, grasp_joint_angles) != 0) {
RCLCPP_WARN(node->get_logger(), "[%s] angle %f, palm %s grasp_pose rm arm inverse kinematics failed.",
skill_name.c_str(), angle, palm.c_str());
continue;
}
node->pre_grasp_poses_.push_back(pre_grasp_msg);
node->grasp_poses_.push_back(grasp_msg);
RCLCPP_INFO(node->get_logger(), "Generated Pose (Angle: %.1f, Palm: %s)", angle, palm.c_str());
RCLCPP_INFO(node->get_logger(), " Pre: %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f",
result.pre_grasp_pose.position.x(), result.pre_grasp_pose.position.y(), result.pre_grasp_pose.position.z(),
result.pre_grasp_pose.orientation.x(), result.pre_grasp_pose.orientation.y(), result.pre_grasp_pose.orientation.z(), result.pre_grasp_pose.orientation.w());
RCLCPP_INFO(node->get_logger(), " Grasp: %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f",
result.grasp_pose.position.x(), result.grasp_pose.position.y(), result.grasp_pose.position.z(),
result.grasp_pose.orientation.x(), result.grasp_pose.orientation.y(), result.grasp_pose.orientation.z(), result.grasp_pose.orientation.w());
}
}
}
}
return true;
};
{
SkillManager::ServiceHookOptions<VisionSrv> hooks;
hooks.wait_timeout = [](const std::string &) {
return std::chrono::seconds(2);
};
hooks.call_timeout = [](const std::string &) {
return std::chrono::seconds(5);
};
hooks.make_request = [node](const std::string & skill_name) {
auto req = std::make_shared<VisionSrv::Request>();
// Prefer payload_yaml from calls
if (!node->TryParseCallPayload<VisionSrv::Request>(skill_name, *req)) {
RCLCPP_ERROR(node->get_logger(), "[%s] Payload Parse failed", skill_name.c_str());
return req;
}
// fallback to parameter
// const std::string param = "vision_object_recognition.camera_position";
// if (!node->has_parameter(param)) {
// node->declare_parameter<std::string>(param, std::string("right"));
// }
// req->camera_position = node->get_parameter(param).as_string();
RCLCPP_INFO(node->get_logger(), "[%s] camera_position: %s", skill_name.c_str(), req->camera_position.c_str());
return req;
};
hooks.on_response = [node, UpdateTargetPose, CalculateGraspPoses](
const std::string & skill_name,
const SkillManager::ServiceHookOptions<VisionSrv>::ClientPtr &,
const SkillManager::ServiceHookOptions<VisionSrv>::ResponsePtr & resp,
std::string & detail) {
if (!resp->success) {
detail = resp->info.empty() ? std::string("Not success") : resp->info;
RCLCPP_WARN(node->get_logger(), "[%s] VisionObjectRecognition failed: %s",
skill_name.c_str(), detail.c_str());
return false;
}
detail = resp->info.empty() ? std::string("success pose") : resp->info;
const auto stamp_sec = rclcpp::Time(resp->header.stamp).seconds();
RCLCPP_INFO(node->get_logger(), "[%s] success=%d classes=%zu frame_id=%s stamp=%.3f info=%s",
skill_name.c_str(), resp->success, resp->objects.size(),
resp->header.frame_id.c_str(), stamp_sec, resp->info.c_str());
node->pre_grasp_poses_.clear();
node->grasp_poses_.clear();
for (size_t i = 0; i < resp->objects.size(); ++i) {
const auto & obj = resp->objects[i];
RCLCPP_INFO(node->get_logger(), " [%zu] class='%s' id=%d", i, obj.class_name.c_str(), obj.class_id);
if (!UpdateTargetPose(obj, resp->header)) {
continue;
}
if (!CalculateGraspPoses(skill_name)) {
continue;
}
}
if (node->pre_grasp_poses_.empty() || node->grasp_poses_.empty()) {
RCLCPP_WARN(node->get_logger(), "[%s] no valid grasp poses generated", skill_name.c_str());
return false;
}
if (node->pre_grasp_poses_.size() != node->grasp_poses_.size()) {
RCLCPP_WARN(node->get_logger(), "[%s] pre_grasp_poses_ size %zu not match grasp_poses_ size %zu",
skill_name.c_str(), node->pre_grasp_poses_.size(), node->grasp_poses_.size());
return false;
}
return true;
};
node->skill_manager_->configure_service_hooks<VisionSrv>("VisionObjectRecognition", std::move(hooks));
}
}
void CerebellumHooks::ConfigureMapLoadHooks(CerebellumNode * node)
{
using MapLoadSrv = interfaces::srv::MapLoad;
SkillManager::ServiceHookOptions<MapLoadSrv> hooks;
hooks.wait_timeout = [](const std::string &) { return std::chrono::seconds(2); };
hooks.call_timeout = [](const std::string &) { return std::chrono::seconds(5); };
hooks.make_request = [node](const std::string & skill_name) {
auto req = std::make_shared<MapLoadSrv::Request>();
if (!node->TryParseCallPayload<MapLoadSrv::Request>(skill_name, *req)) { return req; }
// Fallback to parameter
if (!node->has_parameter("map_load_url")) {
node->declare_parameter<std::string>("map_load_url", std::string(""));
}
req->map_url = node->get_parameter("map_load_url").as_string();
return req;
};
hooks.on_response = [node](
const std::string & skill_name,
const SkillManager::ServiceHookOptions<MapLoadSrv>::ClientPtr &,
const SkillManager::ServiceHookOptions<MapLoadSrv>::ResponsePtr & resp,
std::string & detail) {
// MapLoad response schema may not include a message field; rely on success only
detail = resp->success ? std::string("ok") : std::string("failed");
if (!resp->success) {
RCLCPP_WARN(node->get_logger(), "[%s] MapLoad failed: %s", skill_name.c_str(), detail.c_str());
} else {
RCLCPP_INFO(node->get_logger(), "[%s] MapLoad: %s", skill_name.c_str(), detail.c_str());
}
return resp->success;
};
node->skill_manager_->configure_service_hooks<MapLoadSrv>("MapLoad", std::move(hooks));
}
void CerebellumHooks::ConfigureMapSaveHooks(CerebellumNode * node)
{
using MapSaveSrv = interfaces::srv::MapSave;
SkillManager::ServiceHookOptions<MapSaveSrv> hooks;
hooks.wait_timeout = [](const std::string &) { return std::chrono::seconds(2); };
hooks.call_timeout = [](const std::string &) { return std::chrono::seconds(5); };
hooks.make_request = [node](const std::string & skill_name) {
auto req = std::make_shared<MapSaveSrv::Request>();
if (!node->TryParseCallPayload<MapSaveSrv::Request>(skill_name, *req)) { return req; }
// Fallback to parameter group
if (!node->has_parameter("map_save_url")) {
node->declare_parameter<std::string>("map_save_url", std::string("/tmp/humanoid_map"));
}
req->map_url = node->get_parameter("map_save_url").as_string();
return req;
};
hooks.on_response = [node](
const std::string & skill_name,
const SkillManager::ServiceHookOptions<MapSaveSrv>::ClientPtr &,
const SkillManager::ServiceHookOptions<MapSaveSrv>::ResponsePtr & resp,
std::string & detail) {
// MapSave response schema may not include a message field; rely on success only
detail = resp->success ? std::string("ok") : std::string("failed");
if (!resp->success) {
RCLCPP_WARN(node->get_logger(), "[%s] MapSave failed: %s", skill_name.c_str(), detail.c_str());
} else {
RCLCPP_INFO(node->get_logger(), "[%s] MapSave: %s", skill_name.c_str(), detail.c_str());
}
return resp->success;
};
node->skill_manager_->configure_service_hooks<MapSaveSrv>("MapSave", std::move(hooks));
}
} // namespace brain

View File

@@ -4,6 +4,7 @@
// Own header first.
#include "brain/cerebellum_node.hpp"
#include "brain/cerebellum_hooks.hpp"
// C / C++ standard library.
#include <chrono>
@@ -12,6 +13,7 @@
#include <cmath>
#include <future>
#include <iostream>
#include <iomanip>
#include <limits>
#include <memory>
#include <cstdint>
@@ -54,7 +56,7 @@
namespace brain
{
static thread_local int tls_arm_body_id = -1;
thread_local int CerebellumNode::tls_arm_body_id = -1;
// 线程局部当前ExecuteBtAction goal的calls用于payload解析与topic解析
thread_local const std::vector<interfaces::msg::SkillCall>* CerebellumNode::tls_current_calls_ = nullptr;
@@ -86,14 +88,17 @@ CerebellumNode::CerebellumNode(const rclcpp::NodeOptions & options)
tf_buffer_->setUsingDedicatedThread(true);
tf_listener_ = std::make_shared<tf2_ros::TransformListener>(*tf_buffer_);
joint_state_sub_ = this->create_subscription<sensor_msgs::msg::JointState>(
"/joint_states", 10, std::bind(&CerebellumNode::JointStateCallback, this, std::placeholders::_1));
action_registry_ = std::make_unique<ActionServerRegistry>(this);
action_clients_ = std::make_unique<ActionClientRegistry>(this);
skill_manager_ = std::make_unique<SkillManager>(this, action_clients_.get(), nullptr);
DeclareAndLoadParameters();
// Register action/service hook overrides first so SkillManager will pick them up
ConfigureActionHooks();
ConfigureServiceHooks();
CerebellumHooks::ConfigureActionHooks(this);
CerebellumHooks::ConfigureServiceHooks(this);
// Then load skills which will register action/service clients and honor the overrides
LoadSkillsFile();
SetupStatsTimerAndPublisher();
@@ -110,589 +115,6 @@ CerebellumNode::CerebellumNode(const rclcpp::NodeOptions & options)
#endif
}
/**
* @brief Configure action hooks for arm control.
*
*/
void CerebellumNode::ConfigureArmHooks()
{
// Arm
SkillManager::ActionHookOptions<interfaces::action::Arm> hooks;
hooks.make_goal = [this](const std::string & skill_name) {
interfaces::action::Arm::Goal goal{};
if (!TryParseCallPayload<interfaces::action::Arm::Goal>(skill_name, goal)) {
RCLCPP_ERROR(this->get_logger(), "Failed to parse call payload for skill %s", skill_name.c_str());
return goal;
}
RCLCPP_INFO(this->get_logger(), "[%s]Received Arm goal: body_id=%d, data_type=%d, data_length=%d, command_id=%ld, frame_time_stamp=%ld, data_array=",
skill_name.c_str(), goal.body_id, goal.data_type, goal.data_length, goal.command_id, goal.frame_time_stamp);
goal.body_id = (tls_arm_body_id == LEFT_ARM ||
tls_arm_body_id == RIGHT_ARM) ? tls_arm_body_id : LEFT_ARM;
goal.command_id = ++command_id_;
goal.frame_time_stamp = this->get_clock()->now().nanoseconds();
if (goal.data_type == ARM_COMMAND_TYPE_POSE_DIRECT_MOVE) {
if (goal.data_length == POSE_DIMENSION*2 && goal.data_array.size() == POSE_DIMENSION*2) {
if (goal.body_id == LEFT_ARM) {
goal.data_array = {goal.data_array[0], goal.data_array[1], goal.data_array[2],
goal.data_array[3], goal.data_array[4], goal.data_array[5], goal.data_array[6]};
} else if (goal.body_id == RIGHT_ARM) {
goal.data_array = {goal.data_array[7], goal.data_array[8], goal.data_array[9],
goal.data_array[10], goal.data_array[11], goal.data_array[12], goal.data_array[13]};
}
goal.data_length = POSE_DIMENSION;
}
} else if (goal.data_type == ARM_COMMAND_TYPE_ANGLE_DIRECT_MOVE) {
if (goal.data_length == USED_ARM_DOF*2 && goal.data_array.size() == USED_ARM_DOF*2) {
if (goal.body_id == LEFT_ARM) {
goal.data_array = {goal.data_array[0], goal.data_array[1], goal.data_array[2],
goal.data_array[3], goal.data_array[4], goal.data_array[5]};
} else if (goal.body_id == RIGHT_ARM) {
goal.data_array = {goal.data_array[6], goal.data_array[7], goal.data_array[8], goal.data_array[9],
goal.data_array[10], goal.data_array[11]};
}
goal.data_length = USED_ARM_DOF;
}
}
RCLCPP_INFO(this->get_logger(), "[%s] Control params: body_id=%d, data_type=%d, data_length=%d, command_id=%ld, frame_time_stamp=%ld, data_array=[%.6f, %.6f, %.6f, %.6f, %.6f, %.6f]",
skill_name.c_str(), goal.body_id, goal.data_type, goal.data_length, goal.command_id, goal.frame_time_stamp,
goal.data_array[0], goal.data_array[1], goal.data_array[2], goal.data_array[3], goal.data_array[4], goal.data_array[5]);
return goal;
};
hooks.on_feedback = [this](
const std::string & skill_name,
ActionClientRegistry::GoalHandle<interfaces::action::Arm>::SharedPtr,
const std::shared_ptr<const interfaces::action::Arm::Feedback> & feedback) {
if (!feedback) {
RCLCPP_WARN(this->get_logger(), "[%s] feedback is empty", skill_name.c_str());
return;
}
(void)feedback;
//TODO :
// RCLCPP_INFO(this->get_logger(), "[%s] command_id: %ld, current progress: %d",
// skill_name.c_str(), feedback->command_id, feedback->int_lenth);
};
hooks.on_result = [this](
const std::string & skill_name,
const ActionClientRegistry::GoalHandle<interfaces::action::Arm>::WrappedResult & res) {
const bool success = (res.code == rclcpp_action::ResultCode::SUCCEEDED && res.result && res.result->result == OK);
const std::string message = res.result ? std::string("action end") : std::string("No return information");
geometry_msgs::msg::Pose pose = res.result->pose;
RCLCPP_WARN(this->get_logger(), "[%s] Final position: %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f",
skill_name.c_str(), pose.position.x, pose.position.y, pose.position.z, pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w);
if (success) {
RCLCPP_INFO(this->get_logger(), "[%s] command_id %ld completed: %s", skill_name.c_str(), res.result->command_id, message.c_str());
} else if (res.code == rclcpp_action::ResultCode::CANCELED) {
RCLCPP_WARN(this->get_logger(), "[%s] was cancelled", skill_name.c_str());
} else {
RCLCPP_ERROR(this->get_logger(), "[%s] failed (code=%d): %s",
skill_name.c_str(), static_cast<int>(res.code), message.c_str());
}
};
hooks.on_goal_response = [this](
const std::string & skill_name,
const std::shared_ptr<ActionClientRegistry::GoalHandle<interfaces::action::Arm>> & handle) {
if (static_cast<bool>(handle)) {
RCLCPP_INFO(this->get_logger(), "[%s] goal has been accepted by server", skill_name.c_str());
} else {
RCLCPP_WARN(this->get_logger(), "[%s] goal was rejected by server", skill_name.c_str());
}
};
skill_manager_->configure_action_hooks<interfaces::action::Arm>("Arm", std::move(hooks));
}
/**
* @brief Configure action hooks for hand control.
*
*/
void CerebellumNode::ConfigureHandControlHooks()
{
// HandControl
SkillManager::ActionHookOptions<interfaces::action::HandControl> hooks;
hooks.make_goal = [this](const std::string & skill_name) {
interfaces::action::HandControl::Goal goal{};
// Plan A: per-call YAML overrides
if (!TryParseCallPayload<interfaces::action::HandControl::Goal>(skill_name, goal)) {
RCLCPP_ERROR(this->get_logger(), "Failed to parse call payload for skill %s", skill_name.c_str());
return goal;
}
const std::string param = "hand_control.target_pose";
if (!this->has_parameter(param)) {
this->declare_parameter<std::vector<double>>(param, {0.0, 0.0, 0.0});
}
const auto values = this->get_parameter(param).as_double_array();
RCLCPP_INFO(this->get_logger(), "[%s] Target parameters size=%zu", skill_name.c_str(), values.size());
(void)values;
goal.mode = 1;
return goal;
};
hooks.on_feedback = [this](
const std::string & skill_name,
ActionClientRegistry::GoalHandle<interfaces::action::HandControl>::SharedPtr,
const std::shared_ptr<const interfaces::action::HandControl::Feedback> & feedback) {
if (!feedback) {return;}
(void)feedback;
//TODO :
RCLCPP_INFO(this->get_logger(), "[%s] Current progress: %.2f", skill_name.c_str(), feedback->progress);
};
hooks.on_result = [this](
const std::string & skill_name,
const ActionClientRegistry::GoalHandle<interfaces::action::HandControl>::WrappedResult & res) {
const bool success = res.code == rclcpp_action::ResultCode::SUCCEEDED && res.result && res.result->success;
const std::string message = res.result ? res.result->message : std::string("No return information");
if (success) {
RCLCPP_INFO(this->get_logger(), "[%s] Success: %s", skill_name.c_str(), message.c_str());
} else if (res.code == rclcpp_action::ResultCode::CANCELED) {
RCLCPP_WARN(this->get_logger(), "[%s] was cancelled", skill_name.c_str());
} else {
RCLCPP_ERROR(this->get_logger(), "[%s] failed (code=%d): %s",
skill_name.c_str(), static_cast<int>(res.code), message.c_str());
}
};
hooks.on_goal_response = [this](
const std::string & skill_name,
const std::shared_ptr<ActionClientRegistry::GoalHandle<interfaces::action::HandControl>> & handle) {
if (static_cast<bool>(handle)) {
RCLCPP_INFO(this->get_logger(), "[%s] goal has been accepted by server", skill_name.c_str());
} else {
RCLCPP_WARN(this->get_logger(), "[%s] goal was rejected by server", skill_name.c_str());
}
};
skill_manager_->configure_action_hooks<interfaces::action::HandControl>("HandControl", std::move(hooks));
}
/**
* @brief Configure action hooks for waist movement.
*
*/
void CerebellumNode::ConfigureMoveWaistHooks()
{
// MoveWaist
SkillManager::ActionHookOptions<interfaces::action::MoveWaist> hooks;
hooks.make_goal = [this](const std::string & skill_name) {
interfaces::action::MoveWaist::Goal goal{};
// Plan A: load per-call YAML payload into goal if present
if (!TryParseCallPayload<interfaces::action::MoveWaist::Goal>(skill_name, goal)) {
RCLCPP_INFO(this->get_logger(), "Loaded MoveWaist goal from YAML payload");
return goal;
}
RCLCPP_INFO(this->get_logger(), "[%s] move_pitch_degree: %f, move_yaw_degree: %f",
skill_name.c_str(), goal.move_pitch_degree, goal.move_yaw_degree);
return goal;
};
hooks.on_feedback = [this](
const std::string & skill_name,
ActionClientRegistry::GoalHandle<interfaces::action::MoveWaist>::SharedPtr,
const std::shared_ptr<const interfaces::action::MoveWaist::Feedback> & feedback) {
if (!feedback) {return;}
//TODO
(void)skill_name;
};
hooks.on_result = [this](
const std::string & skill_name,
const ActionClientRegistry::GoalHandle<interfaces::action::MoveWaist>::WrappedResult & res) {
const bool success = res.code == rclcpp_action::ResultCode::SUCCEEDED && res.result && res.result->success;
const std::string message = res.result ? res.result->message : std::string("No return information");
if (success) {
RCLCPP_INFO(this->get_logger(), "[%s] Success: %s", skill_name.c_str(), message.c_str());
} else if (res.code == rclcpp_action::ResultCode::CANCELED) {
RCLCPP_WARN(this->get_logger(), "[%s] was cancelled", skill_name.c_str());
} else {
RCLCPP_ERROR(this->get_logger(), "[%s] failed (code=%d): %s",
skill_name.c_str(), static_cast<int>(res.code), message.c_str());
}
};
hooks.on_goal_response = [this](
const std::string & skill_name,
const std::shared_ptr<ActionClientRegistry::GoalHandle<interfaces::action::MoveWaist>> & handle) {
if (static_cast<bool>(handle)) {
RCLCPP_INFO(this->get_logger(), "[%s] goal has been accepted by server", skill_name.c_str());
} else {
RCLCPP_WARN(this->get_logger(), "[%s] goal was rejected by server", skill_name.c_str());
}
};
skill_manager_->configure_action_hooks<interfaces::action::MoveWaist>("MoveWaist", std::move(hooks));
}
/**
* @brief Configure action hooks for leg movement.
*
*/
void CerebellumNode::ConfigureMoveLegHooks()
{
// MoveLeg
SkillManager::ActionHookOptions<interfaces::action::MoveLeg> hooks;
hooks.make_goal = [this](const std::string & skill_name) {
interfaces::action::MoveLeg::Goal goal{};
if (!TryParseCallPayload<interfaces::action::MoveLeg::Goal>(skill_name, goal)) {
RCLCPP_ERROR(this->get_logger(), "Failed to parse call payload for skill %s", skill_name.c_str());
return goal;
}
return goal;
};
hooks.on_feedback = [this](
const std::string & skill_name,
ActionClientRegistry::GoalHandle<interfaces::action::MoveLeg>::SharedPtr,
const std::shared_ptr<const interfaces::action::MoveLeg::Feedback> & feedback) {
if (!feedback) {return;}
//TODO
(void)skill_name;
};
hooks.on_result = [this](
const std::string & skill_name,
const ActionClientRegistry::GoalHandle<interfaces::action::MoveLeg>::WrappedResult & res) {
const bool success = res.code == rclcpp_action::ResultCode::SUCCEEDED && res.result && res.result->success;
const std::string message = res.result ? res.result->message : std::string("No return information");
if (success) {
RCLCPP_INFO(this->get_logger(), "[%s] Success: %s", skill_name.c_str(), message.c_str());
} else if (res.code == rclcpp_action::ResultCode::CANCELED) {
RCLCPP_WARN(this->get_logger(), "[%s] was cancelled", skill_name.c_str());
} else {
RCLCPP_ERROR(this->get_logger(), "[%s] failed (code=%d): %s",
skill_name.c_str(), static_cast<int>(res.code), message.c_str());
}
};
hooks.on_goal_response = [this](
const std::string & skill_name,
const std::shared_ptr<ActionClientRegistry::GoalHandle<interfaces::action::MoveLeg>> & handle) {
if (static_cast<bool>(handle)) {
RCLCPP_INFO(this->get_logger(), "[%s] goal has been accepted by server", skill_name.c_str());
} else {
RCLCPP_WARN(this->get_logger(), "[%s] goal was rejected by server", skill_name.c_str());
}
};
skill_manager_->configure_action_hooks<interfaces::action::MoveLeg>("MoveLeg", std::move(hooks));
}
/**
* @brief Configure action hooks for wheel movement.
*
*/
void CerebellumNode::ConfigureMoveWheelHooks()
{
// MoveWheel
SkillManager::ActionHookOptions<interfaces::action::MoveWheel> hooks;
hooks.make_goal = [this](const std::string & skill_name) {
interfaces::action::MoveWheel::Goal goal{};
// Plan A: per-call YAML overrides
if (!TryParseCallPayload<interfaces::action::MoveWheel::Goal>(skill_name, goal)) {
RCLCPP_ERROR(this->get_logger(), "Failed to parse call payload for skill %s", skill_name.c_str());
return goal;
}
return goal;
};
hooks.on_feedback = [this](
const std::string & skill_name,
ActionClientRegistry::GoalHandle<interfaces::action::MoveWheel>::SharedPtr,
const std::shared_ptr<const interfaces::action::MoveWheel::Feedback> & feedback) {
if (!feedback) {return;}
//TODO
(void)skill_name;
};
hooks.on_result = [this](
const std::string & skill_name,
const ActionClientRegistry::GoalHandle<interfaces::action::MoveWheel>::WrappedResult & res) {
const bool success = res.code == rclcpp_action::ResultCode::SUCCEEDED && res.result && res.result->success;
const std::string message = res.result ? res.result->message : std::string("No return information");
if (success) {
RCLCPP_INFO(this->get_logger(), "[%s] Success: %s", skill_name.c_str(), message.c_str());
} else if (res.code == rclcpp_action::ResultCode::CANCELED) {
RCLCPP_WARN(this->get_logger(), "[%s] was cancelled", skill_name.c_str());
} else {
RCLCPP_ERROR(this->get_logger(), "[%s] failed (code=%d): %s",
skill_name.c_str(), static_cast<int>(res.code), message.c_str());
}
};
hooks.on_goal_response = [this](
const std::string & skill_name,
const std::shared_ptr<ActionClientRegistry::GoalHandle<interfaces::action::MoveWheel>> & handle) {
if (static_cast<bool>(handle)) {
RCLCPP_INFO(this->get_logger(), "[%s] goal has been accepted by server", skill_name.c_str());
} else {
RCLCPP_WARN(this->get_logger(), "[%s] goal was rejected by server", skill_name.c_str());
}
};
skill_manager_->configure_action_hooks<interfaces::action::MoveWheel>("MoveWheel", std::move(hooks));
}
/**
* @brief Configure action hooks for home movement.
*
*/
void CerebellumNode::ConfigureMoveHomeHooks()
{
// MoveHome
SkillManager::ActionHookOptions<interfaces::action::MoveHome> hooks;
hooks.make_goal = [this](const std::string & skill_name) {
interfaces::action::MoveHome::Goal goal{};
// Plan A: per-call YAML overrides (even though goal is empty)
if (!TryParseCallPayload<interfaces::action::MoveHome::Goal>(skill_name, goal)) {
RCLCPP_ERROR(this->get_logger(), "Failed to parse call payload for skill %s", skill_name.c_str());
return goal;
}
return goal;
};
hooks.on_feedback = [this](
const std::string & skill_name,
ActionClientRegistry::GoalHandle<interfaces::action::MoveHome>::SharedPtr,
const std::shared_ptr<const interfaces::action::MoveHome::Feedback> & feedback) {
if (!feedback) {return;}
//TODO
(void)skill_name;
};
hooks.on_result = [this](
const std::string & skill_name,
const ActionClientRegistry::GoalHandle<interfaces::action::MoveHome>::WrappedResult & res) {
const bool success = res.code == rclcpp_action::ResultCode::SUCCEEDED && res.result && res.result->success;
const std::string message = res.result ? res.result->message : std::string("No return information");
if (success) {
RCLCPP_INFO(this->get_logger(), "[%s] Success: %s", skill_name.c_str(), message.c_str());
} else if (res.code == rclcpp_action::ResultCode::CANCELED) {
RCLCPP_WARN(this->get_logger(), "[%s] was cancelled", skill_name.c_str());
} else {
RCLCPP_ERROR(this->get_logger(), "[%s] failed (code=%d): %s",
skill_name.c_str(), static_cast<int>(res.code), message.c_str());
}
};
hooks.on_goal_response = [this](
const std::string & skill_name,
const std::shared_ptr<ActionClientRegistry::GoalHandle<interfaces::action::MoveHome>> & handle) {
if (static_cast<bool>(handle)) {
RCLCPP_INFO(this->get_logger(), "[%s] goal has been accepted by server", skill_name.c_str());
} else {
RCLCPP_WARN(this->get_logger(), "[%s] goal was rejected by server", skill_name.c_str());
}
};
skill_manager_->configure_action_hooks<interfaces::action::MoveHome>("MoveHome", std::move(hooks));
}
/**
* @brief Configure action hooks for various movements.
*
*/
void CerebellumNode::ConfigureActionHooks()
{
if (!skill_manager_) {
RCLCPP_WARN(this->get_logger(), "SkillManager unavailable, skipping action hook configuration");
return;
}
ConfigureArmHooks();
ConfigureHandControlHooks();
ConfigureCameraTakePhotoHooks();
ConfigureMoveWaistHooks();
ConfigureMoveLegHooks();
ConfigureMoveWheelHooks();
ConfigureMoveHomeHooks();
}
/**
* @brief Configure action hooks for vision object recognition.
*
*/
void CerebellumNode::ConfigureVisionObjectRecognitionHooks()
{
using VisionSrv = interfaces::srv::VisionObjectRecognition;
{
SkillManager::ServiceHookOptions<VisionSrv> hooks;
hooks.wait_timeout = [](const std::string &) {
return std::chrono::seconds(2);
};
hooks.call_timeout = [](const std::string &) {
return std::chrono::seconds(5);
};
hooks.make_request = [this](const std::string & skill_name) {
auto req = std::make_shared<VisionSrv::Request>();
// Prefer payload_yaml from calls
if (TryParseCallPayload<VisionSrv::Request>(skill_name, *req)) {
return req;
}
// fallback to parameter
const std::string param = "vision_object_recognition.camera_position";
if (!this->has_parameter(param)) {
this->declare_parameter<std::string>(param, std::string("left"));
}
req->camera_position = this->get_parameter(param).as_string();
return req;
};
hooks.on_response = [this](
const std::string & skill_name,
const SkillManager::ServiceHookOptions<VisionSrv>::ClientPtr &,
const SkillManager::ServiceHookOptions<VisionSrv>::ResponsePtr & resp,
std::string & detail) {
if (!resp->success) {
detail = resp->info.empty() ? std::string("Not success") : resp->info;
RCLCPP_WARN(this->get_logger(), "[%s] VisionObjectRecognition failed: %s",
skill_name.c_str(), detail.c_str());
return false;
}
detail = resp->info.empty() ? std::string("success pose") : resp->info;
const auto stamp_sec = rclcpp::Time(resp->header.stamp).seconds();
RCLCPP_INFO(
this->get_logger(),
"[%s] success=%d classes=%zu frame_id=%s stamp=%.3f info=%s",
skill_name.c_str(), resp->success, resp->objects.size(), resp->header.frame_id.c_str(),
stamp_sec, resp->info.c_str());
for (size_t i = 0; i < resp->objects.size(); ++i) {
const auto & obj = resp->objects[i];
if (target_frame_ == obj.class_name) {
target_pose_[target_frame_].header = resp->header;
target_pose_[target_frame_].pose = obj.pose_list[0];
RCLCPP_INFO(this->get_logger(), "target_frame_: %s, updated target_pose", target_frame_.c_str());
}
RCLCPP_INFO(
this->get_logger(), " [%zu] class='%s' id=%d poses=%zu", i,
obj.class_name.c_str(), obj.class_id, obj.pose_list.size());
for (size_t j = 0; j < obj.pose_list.size() && j < 2; ++j) {
const auto & p = obj.pose_list[j];
RCLCPP_INFO(
this->get_logger(),
" pose[%zu]: pos(%.3f, %.3f, %.3f) quat(%.3f, %.3f, %.3f, %.3f)", j,
p.position.x, p.position.y, p.position.z,
p.orientation.x, p.orientation.y, p.orientation.z, p.orientation.w);
}
if (obj.pose_list.size() > 2) {
RCLCPP_INFO(
this->get_logger(), " ... (%zu more poses omitted)", obj.pose_list.size() - 2);
}
}
return true;
};
skill_manager_->configure_service_hooks<VisionSrv>("VisionObjectRecognition", std::move(hooks));
}
}
void CerebellumNode::ConfigureCameraTakePhotoHooks()
{
// CameraTakePhoto
SkillManager::ActionHookOptions<interfaces::action::CameraTakePhoto> hooks;
hooks.make_goal = [this](const std::string & skill_name) {
interfaces::action::CameraTakePhoto::Goal goal{};
if (!TryParseCallPayload<interfaces::action::CameraTakePhoto::Goal>(skill_name, goal)) {
RCLCPP_ERROR(this->get_logger(), "Failed to parse call payload for skill %s", skill_name.c_str());
return goal;
}
return goal;
};
hooks.on_result = [this](
const std::string & skill_name,
const ActionClientRegistry::GoalHandle<interfaces::action::CameraTakePhoto>::WrappedResult & res) {
const bool success = res.code == rclcpp_action::ResultCode::SUCCEEDED && res.result && res.result->success;
const std::string message = res.result ? res.result->message : std::string("No return information");
if (success) {
RCLCPP_INFO(this->get_logger(), "[%s] Success: %s", skill_name.c_str(), message.c_str());
} else if (res.code == rclcpp_action::ResultCode::CANCELED) {
RCLCPP_WARN(this->get_logger(), "[%s] was cancelled", skill_name.c_str());
} else {
RCLCPP_ERROR(this->get_logger(), "[%s] failed (code=%d): %s",
skill_name.c_str(), static_cast<int>(res.code), message.c_str());
}
};
hooks.on_goal_response = [this](
const std::string & skill_name,
const std::shared_ptr<ActionClientRegistry::GoalHandle<interfaces::action::CameraTakePhoto>> & handle) {
if (static_cast<bool>(handle)) {
RCLCPP_INFO(this->get_logger(), "[%s] goal has been accepted by server", skill_name.c_str());
} else {
RCLCPP_WARN(this->get_logger(), "[%s] goal was rejected by server", skill_name.c_str());
}
};
skill_manager_->configure_action_hooks<interfaces::action::CameraTakePhoto>("CameraTakePhoto", std::move(hooks));
}
void CerebellumNode::ConfigureMapLoadHooks()
{
using MapLoadSrv = interfaces::srv::MapLoad;
SkillManager::ServiceHookOptions<MapLoadSrv> hooks;
hooks.wait_timeout = [](const std::string &) { return std::chrono::seconds(2); };
hooks.call_timeout = [](const std::string &) { return std::chrono::seconds(5); };
hooks.make_request = [this](const std::string & skill_name) {
auto req = std::make_shared<MapLoadSrv::Request>();
if (TryParseCallPayload<MapLoadSrv::Request>(skill_name, *req)) { return req; }
// Fallback to parameter
if (!this->has_parameter("map_load_url")) {
this->declare_parameter<std::string>("map_load_url", std::string(""));
}
req->map_url = this->get_parameter("map_load_url").as_string();
return req;
};
hooks.on_response = [this](
const std::string & skill_name,
const SkillManager::ServiceHookOptions<MapLoadSrv>::ClientPtr &,
const SkillManager::ServiceHookOptions<MapLoadSrv>::ResponsePtr & resp,
std::string & detail) {
// MapLoad response schema may not include a message field; rely on success only
detail = resp->success ? std::string("ok") : std::string("failed");
if (!resp->success) {
RCLCPP_WARN(this->get_logger(), "[%s] MapLoad failed: %s", skill_name.c_str(), detail.c_str());
} else {
RCLCPP_INFO(this->get_logger(), "[%s] MapLoad: %s", skill_name.c_str(), detail.c_str());
}
return resp->success;
};
skill_manager_->configure_service_hooks<MapLoadSrv>("MapLoad", std::move(hooks));
}
void CerebellumNode::ConfigureMapSaveHooks()
{
using MapSaveSrv = interfaces::srv::MapSave;
SkillManager::ServiceHookOptions<MapSaveSrv> hooks;
hooks.wait_timeout = [](const std::string &) { return std::chrono::seconds(2); };
hooks.call_timeout = [](const std::string &) { return std::chrono::seconds(5); };
hooks.make_request = [this](const std::string & skill_name) {
auto req = std::make_shared<MapSaveSrv::Request>();
if (TryParseCallPayload<MapSaveSrv::Request>(skill_name, *req)) { return req; }
// Fallback to parameter group
if (!this->has_parameter("map_save_url")) {
this->declare_parameter<std::string>("map_save_url", std::string("/tmp/humanoid_map"));
}
req->map_url = this->get_parameter("map_save_url").as_string();
return req;
};
hooks.on_response = [this](
const std::string & skill_name,
const SkillManager::ServiceHookOptions<MapSaveSrv>::ClientPtr &,
const SkillManager::ServiceHookOptions<MapSaveSrv>::ResponsePtr & resp,
std::string & detail) {
// MapSave response schema may not include a message field; rely on success only
detail = resp->success ? std::string("ok") : std::string("failed");
if (!resp->success) {
RCLCPP_WARN(this->get_logger(), "[%s] MapSave failed: %s", skill_name.c_str(), detail.c_str());
} else {
RCLCPP_INFO(this->get_logger(), "[%s] MapSave: %s", skill_name.c_str(), detail.c_str());
}
return resp->success;
};
skill_manager_->configure_service_hooks<MapSaveSrv>("MapSave", std::move(hooks));
}
/**
* @brief Configure Service hooks for various movements.
*
*/
void CerebellumNode::ConfigureServiceHooks()
{
if (!skill_manager_) {
RCLCPP_WARN(this->get_logger(), "SkillManager unavailable, skipping service hook configuration");
return;
}
ConfigureVisionObjectRecognitionHooks();
ConfigureMapLoadHooks();
ConfigureMapSaveHooks();
}
/**
* @brief Declare all configurable ROS parameters and load their initial values.
*
@@ -1179,9 +601,32 @@ bool CerebellumNode::ExecuteActionSkill(
if (timeout_ms_override > 0) {
effective_timeout = std::chrono::milliseconds(timeout_ms_override);
}
return (skill == "Arm") ?
ExecuteBilateralArmAction(skill, topic, effective_timeout, instance_name, timeout_ms_override, index, total_skills, goal_handle, detail) :
RunActionSkillWithProgress(skill, topic, effective_timeout, instance_name, timeout_ms_override, index, total_skills, goal_handle, detail);
if (skill == "Arm") {
interfaces::action::Arm::Goal goal{};
if (!TryParseCallPayload<interfaces::action::Arm::Goal>(skill, goal)) {
RCLCPP_ERROR(this->get_logger(), "Failed to parse call payload for skill %s", skill.c_str());
return false;
}
if (goal.body_id >= 2) { //left and right
return ExecuteBilateralArmAction(skill, topic, effective_timeout,
instance_name, timeout_ms_override, index, total_skills, goal_handle, detail);
} else if (goal.body_id == 0 || goal.body_id == 1) { //left or right
tls_arm_body_id = goal.body_id;
} else {
RCLCPP_ERROR(this->get_logger(), "Invalid body_id: %d for skill %s", goal.body_id, skill.c_str());
return false;
}
}
return RunActionSkillWithProgress(skill, topic, effective_timeout,
instance_name, timeout_ms_override, index, total_skills, goal_handle, detail);
// return (skill == "Arm") ?
// ExecuteBilateralArmAction(skill, topic, effective_timeout,
// instance_name, timeout_ms_override, index, total_skills, goal_handle, detail) :
// RunActionSkillWithProgress(skill, topic, effective_timeout,
// instance_name, timeout_ms_override, index, total_skills, goal_handle, detail);
}
/**
@@ -1275,15 +720,14 @@ bool CerebellumNode::ExecuteBilateralArmAction(
RCLCPP_INFO(this->get_logger(), "[Arm] send two goals: body_id=1 and body_id=0, index=%d, total_skills=%d", index, total_skills);
RCLCPP_INFO(this->get_logger(), "[Arm] instance='%s' timeout=%ld ms", instance_name.c_str(), static_cast<long>(timeout_ms_override));
std::atomic<bool> finished1{false}, finished2{false};
bool ok1 = false, ok2 = false;
std::string d1, d2;
std::atomic<bool> right_arm_finished{false}, left_arm_finished{false};
bool right_arm_ok = false, left_arm_ok = false;
std::string right_arm_d, left_arm_d;
auto left_arm_time_steady = std::chrono::steady_clock::now();
auto right_arm_time_steady = std::chrono::steady_clock::now();
auto worker = [this, &ok_out = ok1, &done_flag = finished1, &d_out = d1,
skill, topic, timeout, &goal_handle, &time_steady = left_arm_time_steady](int body_id) {
auto left_arm_worker = [this, &ok_out = left_arm_ok, &done_flag = left_arm_finished, &d_out = left_arm_d,
skill, topic, timeout, &goal_handle, &time_steady = left_arm_time_steady](int body_id) {
tls_arm_body_id = body_id;
const auto * prev_calls = CerebellumNode::tls_current_calls_;
CerebellumNode::tls_current_calls_ = &goal_handle->get_goal()->calls;
@@ -1294,10 +738,10 @@ bool CerebellumNode::ExecuteBilateralArmAction(
time_steady = std::chrono::steady_clock::now();
RCLCPP_WARN(this->get_logger(), "left_arm_time_steady2");
};
std::thread left_arm_thread(left_arm_worker, LEFT_ARM); // LEFT_ARM=0
std::thread t1(worker, RIGHT_ARM); // RIGHT_ARM=1
auto worker2 = [this, &ok_out = ok2, &done_flag = finished2, &d_out = d2,
skill, topic, timeout, &goal_handle, &time_steady = right_arm_time_steady](int body_id) {
auto right_arm_worker = [this, &ok_out = right_arm_ok, &done_flag = right_arm_finished, &d_out = right_arm_d,
skill, topic, timeout, &goal_handle, &time_steady = right_arm_time_steady](int body_id) {
tls_arm_body_id = body_id;
const auto * prev_calls = CerebellumNode::tls_current_calls_;
CerebellumNode::tls_current_calls_ = &goal_handle->get_goal()->calls;
@@ -1308,11 +752,11 @@ bool CerebellumNode::ExecuteBilateralArmAction(
time_steady = std::chrono::steady_clock::now();
RCLCPP_WARN(this->get_logger(), "right_arm_time_steady2");
};
std::thread t2(worker2, LEFT_ARM); // LEFT_ARM=0
std::thread right_arm_thread(right_arm_worker, RIGHT_ARM); // RIGHT_ARM=1
const auto start_steady = std::chrono::steady_clock::now();
while (!(finished1.load(std::memory_order_acquire) && finished2.load(std::memory_order_acquire))) {
while (!(right_arm_finished.load(std::memory_order_acquire) && left_arm_finished.load(std::memory_order_acquire))) {
double elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start_steady).count();
PublishFeedbackStage(goal_handle, "RUN", skill, elapsed_ms, "");
std::this_thread::sleep_for(std::chrono::milliseconds(100));
@@ -1322,30 +766,30 @@ bool CerebellumNode::ExecuteBilateralArmAction(
RCLCPP_WARN(this->get_logger(), "arm_exe_bias: %f", arm_exe_bias);
if (timeout_ms_override > 0 && std::abs(arm_exe_bias) > timeout_ms_override) {
ok1 = false;
ok2 = false;
right_arm_ok = false;
left_arm_ok = false;
RCLCPP_ERROR(this->get_logger(), "Fail in Arm arm_exe_bias: %f, timeout_ms_override=%ld", arm_exe_bias, static_cast<long>(timeout_ms_override));
}
if (t1.joinable()) {
if (right_arm_thread.joinable()) {
try {
t1.join();
right_arm_thread.join();
} catch (const std::exception& e) {
RCLCPP_ERROR(this->get_logger(), "Exception in Arm thread 1 join: %s", e.what());
}
}
if (t2.joinable()) {
if (left_arm_thread.joinable()) {
try {
t2.join();
left_arm_thread.join();
} catch (const std::exception& e) {
RCLCPP_ERROR(this->get_logger(), "Exception in Arm thread 2 join: %s", e.what());
}
}
if (!ok1 || !ok2) {
if (!right_arm_ok || !left_arm_ok) {
std::ostringstream oss;
if (!ok1) { oss << "Arm(body_id=1) failed"; if (!d1.empty()) oss << ": " << d1; }
if (!ok2) { if (!oss.str().empty()) oss << "; "; oss << "Arm(body_id=0) failed"; if (!d2.empty()) oss << ": " << d2; }
if (!right_arm_ok) { oss << "Arm(body_id=1) failed"; if (!right_arm_d.empty()) oss << ": " << right_arm_d; }
if (!left_arm_ok) { if (!oss.str().empty()) oss << "; "; oss << "Arm(body_id=0) failed"; if (!left_arm_d.empty()) oss << ": " << left_arm_d; }
detail = oss.str();
return false;
}
@@ -1383,7 +827,7 @@ bool CerebellumNode::ExecuteServiceSkill(
for (const auto & iface : spec.interfaces) {
const auto pos = iface.rfind('.');
if (pos == std::string::npos || pos + 1 >= iface.size()) {continue;}
if (to_lower_str(iface.substr(pos + 1)) == "service") {
if (to_lower_str(iface.substr(pos + 1)) == "srv") {
service_base = iface.substr(0, pos);
break;
}
@@ -1433,7 +877,7 @@ void CerebellumNode::LogStatsPeriodic()
for (const auto & kv : skill_success_counts_) {
size_t fails = skill_failure_counts_[kv.first];
RCLCPP_INFO(
this->get_logger(), " skill %s: OK=%zu FAIL=%zu", kv.first.c_str(), kv.second,
this->get_logger(), "skill %s: OK=%zu FAIL=%zu", kv.first.c_str(), kv.second,
fails);
}
if (stats_pub_) {
@@ -1648,6 +1092,23 @@ std::string CerebellumNode::ResolveTopicForSkill(const std::string & skill) cons
return c.topic;
}
}
if (skill_manager_) {
const auto & skills = skill_manager_->skills();
auto it = skills.find(skill);
if (it != skills.end()) {
const auto & spec = it->second;
for (const auto & iface : spec.interfaces) {
if (iface.find(".action") != std::string::npos || iface.find(".srv") != std::string::npos) {
auto topic_it = spec.interface_default_topics.find(iface);
if (topic_it != spec.interface_default_topics.end()) {
return topic_it->second;
}
}
}
}
}
const std::string snake = ToSnake(skill);
if (action_clients_->has_client(snake)) {return snake;}
if (action_clients_->has_client(skill)) {return skill;}
@@ -1811,4 +1272,31 @@ brain::CerebellumData::ExecResult CerebellumNode::ExecuteSequenceConcurrent(
return result;
}
void CerebellumNode::JointStateCallback(const sensor_msgs::msg::JointState::SharedPtr msg)
{
std::lock_guard<std::mutex> lk(joint_state_mutex_);
if (msg->position.size() >= 21) {
left_arm_joint_angles_.clear();
// left_arm_subset = positions[9:15] -> indices 9 to 14
for (size_t i = 9; i < 15; ++i) {
double angle_deg = msg->position[i] * 180.0 / 3.14;
left_arm_joint_angles_.push_back(angle_deg);
}
right_arm_joint_angles_.clear();
// right_arm_subset = positions[15:21] -> indices 15 to 20
for (size_t i = 15; i < 21; ++i) {
double angle_deg = msg->position[i] * 180.0 / 3.14;
right_arm_joint_angles_.push_back(angle_deg);
}
#ifdef DEBUG_JOINT_STATE
RCLCPP_INFO(this->get_logger(), "Received left arm joint angle[%f, %f, %f, %f, %f, %f] ",
left_arm_joint_angles_[0], left_arm_joint_angles_[1], left_arm_joint_angles_[2],
left_arm_joint_angles_[3], left_arm_joint_angles_[4], left_arm_joint_angles_[5]);
RCLCPP_INFO(this->get_logger(), "Received right arm joint angle[%f, %f, %f, %f, %f, %f] ",
right_arm_joint_angles_[0], right_arm_joint_angles_[1], right_arm_joint_angles_[2],
right_arm_joint_angles_[3], right_arm_joint_angles_[4], right_arm_joint_angles_[5]);
#endif
}
}
} // namespace brain

View File

@@ -78,12 +78,12 @@ private:
pose1.orientation.z = -0.19539139;
pose1.orientation.w = 0.73153153;
obj1.pose_list.push_back(pose1);
// obj1.pose_list.push_back(pose1);
// 第二个同类实例(示例)
geometry_msgs::msg::Pose pose1b = pose1;
pose1b.position.y += 0.05;
obj1.pose_list.push_back(pose1b);
// obj1.pose_list.push_back(pose1b);
interfaces::msg::PoseClassAndID obj2;
obj2.class_name = "cup";
@@ -93,15 +93,15 @@ private:
pose2.position.y = -0.2;
pose2.position.z = 0.78;
pose2.orientation.w = 1.0;
obj2.pose_list.push_back(pose2);
// obj2.pose_list.push_back(pose2);
res->objects.clear();
res->objects.emplace_back(obj1);
res->objects.emplace_back(obj2);
std::ostringstream oss;
oss << "Recognized " << res->objects.size() << " object classes (total instances: "
<< (obj1.pose_list.size() + obj2.pose_list.size()) << ")";
// oss << "Recognized " << res->objects.size() << " object classes (total instances: "
// << (obj1.pose_list.size() + obj2.pose_list.size()) << ")";
res->info = oss.str();
} else {
res->success = false;

0
src/scripts/euler_to_equa.py Normal file → Executable file
View File

0
src/scripts/euler_to_quaternion.py Normal file → Executable file
View File

16
src/scripts/gen_bt_params.py Normal file → Executable file
View File

@@ -116,25 +116,25 @@ def parse_action_file(path: str) -> List[Tuple[str, str]]:
return fields
def locate_action_file(interfaces_root: str, base: str) -> Optional[str]:
action_dir = resolve_interface_subdir(interfaces_root, 'action')
def locate_action_file(interfaces_root: str, base: str, type: str) -> Optional[str]:
action_dir = resolve_interface_subdir(interfaces_root, type)
# 1) Try as-is (CamelCase)
candidate = os.path.join(action_dir, f'{base}.action')
candidate = os.path.join(action_dir, f'{base}.{type}')
if os.path.exists(candidate):
return candidate
# 2) Try CamelCase of snake
camel = snake_to_camel(base)
if camel != base:
candidate2 = os.path.join(action_dir, f'{camel}.action')
candidate2 = os.path.join(action_dir, f'{camel}.{type}')
if os.path.exists(candidate2):
return candidate2
# 3) Try snake_case
snake = to_snake(base)
candidate3 = os.path.join(action_dir, f'{snake}.action')
candidate3 = os.path.join(action_dir, f'{snake}.{type}')
if os.path.exists(candidate3):
return candidate3
# 4) Case-insensitive lookup
found = find_file_case_insensitive(action_dir, f'{base}.action')
found = find_file_case_insensitive(action_dir, f'{base}.{type}')
if found:
return found
# Not found
@@ -239,7 +239,9 @@ def main():
for instance_name, node_tag in nodes:
# Map tag to interface base by stripping trailing '_H' if present
base = node_tag[:-2] if node_tag.endswith('_H') else node_tag
action_file = locate_action_file(interfaces_root, base)
action_file = locate_action_file(interfaces_root, base, "action")
if action_file == None:
action_file = locate_action_file(interfaces_root, base, "srv")
fields: List[Tuple[str, str]] = parse_action_file(action_file) if action_file else []
yaml_params = build_yaml_params_from_fields(fields)
results.append({'name': instance_name, 'params': yaml_params})

0
src/scripts/gen_payload_converters.py Normal file → Executable file
View File

0
src/scripts/gen_robot_config.py Normal file → Executable file
View File

0
src/scripts/hand_eye_cali.py Normal file → Executable file
View File

0
src/scripts/images_to_quaternions.py Normal file → Executable file
View File

0
src/scripts/rotate_pose.py Normal file → Executable file
View File

View File

@@ -0,0 +1,65 @@
#ifndef ARM_DEFINE_H
#define ARM_DEFINE_H
#include "arm_action_define.h"
#define MAX_ARM_GOAL_COUNT 16
#define MAX_TRAJECTORY_HISTORY 64
#define ARM_FOLLOWING_PERIOD (5e6) //ns == 5ms
#define ARM_FOLLOWING_CHECKING_STEP (4)
#define ARM_FOLLOWING_CHECKING (ARM_FOLLOWING_PERIOD * ARM_FOLLOWING_CHECKING_STEP) //ns == 20ms
#define GET_FUNC_LINE_STR() \
(std::string(__func__ ) + ":" + std::to_string(__LINE__))
#define LEFT_ARM_IP_ADDRESS "192.168.2.2"
#define RIGHT_ARM_IP_ADDRESS "192.168.2.18"
#define ARM_IP_PORT (8080)
#define TRAJECTORY_COMPLETE 1
#define TRAJECTORY_ONGOING 0
#define TRAJECTORY_ERROR -1
/*typedef enum {
ARM_COMMAND_TYPE_ANGLE_STEP_ON = 0,
ARM_COMMAND_TYPE_POSE_STEP_ON,
ARM_COMMAND_TYPE_ANGLE_DIRECT_MOVE,
ARM_COMMAND_TYPE_POSE_DIRECT_MOVE,
ARM_COMMAND_TYPE_ERR
} ArmCommandTypeE;
typedef enum {
POSE_POSITION_X = 0,
POSE_POSITION_Y,
POSE_POSITION_Z,
POSE_QUATERNION_X,
POSE_QUATERNION_Y,
POSE_QUATERNION_Z,
POSE_QUATERNION_W,
POSE_DIMENSION, // should be 7
} PoseDimensionE;
typedef enum {
LEFT_ARM = 0,
RIGHT_ARM,
ARM_ID_ERR
} ArmIdE;
typedef enum {
OK = 0,
UNKNOWN_ERR = -1,
ARM_NOW_FORCE_MOVING = -2,
ARM_COLLISION = -3,
ARM_AIM_CANNOT_REACH = -4,
ARM_NOW_NO_GOAL = -5,
ARM_GOAL_CANCELLED = -6,
} ErrCodeE;*/
typedef enum {
GOAL_TYPE_MOVING = 0,
GOAL_TYPE_STEPPING,
GOAL_TYPE_POSE_STEPPING,
GOAL_TYPE_ERROR
} GoalTypeE;
#endif // ARM_DEFINE_H

1035
src/thirdparty/rm_arm/include/rm_define.h vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
#ifndef RM_INTERFACE_GLOBAL_H
#define RM_INTERFACE_GLOBAL_H
#ifdef __linux
#define RM_INTERFACE_EXPORT
#endif
#if _WIN32
#if defined(RM_INTERFACE_LIBRARY)
# define RM_INTERFACE_EXPORT __declspec(dllexport)
#else
# define RM_INTERFACE_EXPORT __declspec(dllexport)
#endif
#endif
#endif // RM_INTERFACE_GLOBAL_H

4553
src/thirdparty/rm_arm/include/rm_service.h vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
#ifndef RM_VERSION_H
#define RM_VERSION_H
#ifdef __cplusplus
extern "C" {
#endif
#define SDK_VERSION ("1.1.1")
#ifdef __cplusplus
}
#endif
#endif

BIN
src/thirdparty/rm_arm/lib/api_cpp.dll vendored Normal file

Binary file not shown.

BIN
src/thirdparty/rm_arm/lib/api_cpp.lib vendored Normal file

Binary file not shown.

BIN
src/thirdparty/rm_arm/lib/libapi_cpp.so vendored Normal file

Binary file not shown.