DynamixelSDK 설치 필요 (Github에서 clone 받은 후 워크스페이스에서 빌드)
아래 코드를 터미널에서 한 줄씩 실행한다
cd ${HOME}/ros2_ws/src
git clone https://github.com/MACMORNING-TEAM/jimbot_node.git
cd ${HOME}/ros2_ws
colcon build --packages-select jimbot_gazebo
source /opt/ros/humble/setup.bash
현재 다이나믹셀 휠 모터만 연결 가능
- 다이나믹셀 연결 노드
ros2 run jimbot_node dynamixel_node
(라이다, 카메라 추가 예정)
최대 속도 제한은 0.37m/s이며 감당할 수 있는 토크 내에서 더 빠르게 주행할 수도 있다. /cmd_vel 토픽을 받아 주행하므로 아래 명령어를 다른 터미널에서 실행하여 시뮬레이션과 같이 로봇을 움직이게 할 수 있다.(좀 재밌음)
ros2 run teleop_twist_keyboard teleop_twist_keyboard
실행하면 아래와 같이 출력된다. 터미널을 클릭한 후 조향하면 짐봇이 현실에서 움직인다.
This node takes keypresses from the keyboard and publishes them
as Twist/TwistStamped messages. It works best with a US keyboard layout.
---------------------------
Moving around:
u i o
j k l
m , .
For Holonomic mode (strafing), hold down the shift key:
---------------------------
U I O
J K L
M < >
t : up (+z)
b : down (-z)
anything else : stop
q/z : increase/decrease max speeds by 10%
w/x : increase/decrease only linear speed by 10%
e/c : increase/decrease only angular speed by 10%
CTRL-C to quit
currently: speed 0.5 turn 1.0
jimbot_msgs가 필요하다.
Action: jimbot_msgs::action::BeltOperate
Service: jimbot_msgs::srv::DoorOperate
BeltOperate의 Goal에는 int형의 action가 있다. 0(후진), 1(전진)으로 세팅할 수 있다. 완료된 경우 True를 리턴한다.
DoorOperate의 Request에는 int형의 action이 있다. 0(닫힘), 1(열림)으로 세팅할 수 있다. 전달된 경우 Response는 True 이다.
액션 클라이언트 셍팅의 경우, 아래 템플릿을 변형해서 사용하면 된다.
// 단축어 지정
using NavigateToPose = nav2_msgs::action::NavigateToPose;
using GoalHandleNavigateToPose = rclcpp_action::ClientGoalHandle<NavigateToPose>;
void send_compute_path_goal(const geometry_msgs::msg::PoseStamped & goal_pose) // 타입은 재량
{
if (!this->action_path_client_->wait_for_action_server(std::chrono::seconds(5))) {
RCLCPP_ERROR(this->get_logger(), "Planning server not available after waiting");
return;
}
auto goal_msg = nav2_msgs::action::NavigateToPose::Goal();
// Goal 설정...
RCLCPP_INFO(this->get_logger(), "Sending goal to server...");
// Action Client 함수 정의
auto send_goal_options = rclcpp_action::Client<NavigateToPose>::SendGoalOptions();
// Response를 받을 시 수행되는 함수, 아래는 에러 발생 여부 확인
send_goal_options.goal_response_callback =
[this](GoalHandleNavigateToPose::SharedPtr future) {
auto goal_handle = future.get();
if (!goal_handle) {
RCLCPP_ERROR(this->get_logger(), "Goal was rejected by server");
} else {
RCLCPP_INFO(this->get_logger(), "Goal was accepted by server. Waiting for result...");
}
};
// 피드백을 받는 경우 실행, 피드백 보내지 않도록 세팅해놓아서 아래 코드 그대로 사용
send_goal_options.feedback_callback =
[this](GoalHandleNavigateToPose::SharedPtr goal_handle,
const std::shared_ptr<const ComputePathToPose::Feedback> feedback) {
(void)goal_handle;
(void)feedback;
RCLCPP_INFO(this->get_logger(), "Received feedback (no specific data in ComputePathToPose feedback)");
};
// case 별로 성공 여부 확인, result.code = rclcpp_action::ResultCode::SUCCEEDED 일 때
// 다음 행동으로 넘어가도록 플래그를 바꾸거나 함수를 실행하도록 할 수 있음
send_goal_options.result_callback =
[this](const GoalHandleNavigateToPose::WrappedResult & result) {
switch (result.code) {
case rclcpp_action::ResultCode::SUCCEEDED:
break;
case rclcpp_action::ResultCode::CANCELED:
RCLCPP_INFO(this->get_logger(), "Goal was canceled");
break;
case rclcpp_action::ResultCode::ABORTED:
RCLCPP_ERROR(this->get_logger(), "Goal was aborted");
break;
default:
RCLCPP_ERROR(this->get_logger(), "Unknown result code");
break;
}
};
// Action Server에 목표를 보내는 코드
this->action_path_client_->async_send_goal(goal_msg, send_goal_options);
}