실질적으로 C++을 통해 ROS 2 개발이 많이 이루어진다 이야기하였습니다. Topic Publish와 Subscriber의 C++코드들도 turtlesim 예제와 함꼐 간단하게 정리해보았습니다.
cbp cpp_topic_tutorial
source install/local_setup.bash
# Terminal 1
ros2 run turtlesim turtlesim_node
# Terminal 2
ros2 run cpp_topic_tutorial topic_pub_node
⇒ 거북이가 마구잡이로 움직이기 시작할 것입니다
#include "geometry_msgs/msg/twist.hpp"
#include "rclcpp/rclcpp.hpp"
단, 파이썬과 달리 c++ 헤더는 snake_case를 취하며, 코드 사용 시 CamelCase를 사용합니다.
rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr twist_publisher;
...
twist_publisher = this->create_publisher<geometry_msgs::msg::Twist>("turtle1/cmd_vel", 10);
publisher는 create_publisher 함수를 통해 생성할 수 있습니다.
geometry_msgs::msg::Twist와 같이 타입이 길기 때문에 using을 사용하여 축약하곤 합니다.
# Terminal 1
ros2 run turtlesim turtlesim_node
# Terminal 2
ros2 run cpp_topic_tutorial topic_sub_node
[INFO] [1666434438.709117200] [turtlepose_sub_node]: x: 5.544/ y: 5.544/ theta: 0.000/ lin_vel: 0.000, ang_vel: 0.000
[INFO] [1666434438.725522800] [turtlepose_sub_node]: x: 5.544/ y: 5.544/ theta: 0.000/ lin_vel: 0.000, ang_vel: 0.000
[INFO] [1666434438.741218300] [turtlepose_sub_node]: x: 5.544/ y: 5.544/ theta: 0.000/ lin_vel: 0.000, ang_vel: 0.000
...
TwistPubNode() : Node("twist_pub_node") {
twist_publisher = this->create_publisher<geometry_msgs::msg::Twist>("turtle1/cmd_vel", 10);
timer = this->create_wall_timer(
std::chrono::milliseconds(500),
std::bind(&TwistPubNode::timer_callback, this)
);
}
subscriber는 create_subscription 함수를 통해 생성할 수 있습니다.
callback의 매개변수가 1개이기에 이를 알려야 하며, std::placeholders::_1이 사용되었습니다.
sub_callback의 첫번째 매개변수인 데이터는 SharedPtr 타입이 사용된다는 것에 주의하며, 때문에 레퍼런스를 사용할 수 없습니다.
Service Client와 Server의 C++코드들도 분석해봅시다.
cbp cpp_service_tutorial
source install/local_setup.bash
# Terminal 1
ros2 run turtlesim turtlesim_node
# Terminal 2
ros2 run cpp_service_tutorial turtle_spawn_client
> Turtle X position : 5.0
> Turtle Y position : 5.0
> Turtle Angle : 0.0
> Turtle Name : my_turtle
[INFO] [1666434554.012857700] [spawn_turtle_node]: Turtle Named : my_turtle Spawned Successfully.
create_client는 다음과 같은 정보를 요합니다.
wait_for_service를 통해 Service Server의 존재 여부를 체크할 수 있습니다.
...
public:
SpawnTurtle() : Node("spawn_turtle_node"){
spawn_client = this->create_client<Spawn>("spawn");
while (!spawn_client->wait_for_service(1s)) {
if (!rclcpp::ok()) {
RCLCPP_ERROR(this->get_logger(),
"Interrupted while waiting for the service. Exiting.");
exit(0);
}
RCLCPP_INFO(this->get_logger(), "service not available, waiting again...");
}
}
auto send_request(){
get_user_input("> Turtle X position : ", spawn_request->x);
get_user_input("> Turtle Y position : ", spawn_request->y);
get_user_input("> Turtle Angle : ", spawn_request->theta);
get_user_input("> Turtle Name : ", spawn_request->name);
return spawn_client->async_send_request(spawn_request);
}
int main(int argc, char **argv) {
rclcpp::init(argc, argv);
auto node = std::make_shared<SpawnTurtle>();
auto result = node->send_request();
// Wait for the result.
if (rclcpp::spin_until_future_complete(node, result) == rclcpp::FutureReturnCode::SUCCESS) {
RCLCPP_INFO(node->get_logger(), "Turtle Named : %s Spawned Successfully.", result.get()->name.c_str());
} else {
RCLCPP_ERROR(node->get_logger(), "Failed to call service add_two_ints");
}
rclcpp::shutdown();
return 0;
}
image from : docs.ros2.org
# Terminal 1
ros2 run turtlesim turtlesim_node
# Terminal 2
ros2 run cpp_service_tutorial turtle_circle_server
# Terminal 3
[INFO] [1666435009.330619900] [turtle_circle_server]: Turtle Turning Server Started, Waiting for Request...
[INFO] [1666435026.497839300] [turtle_circle_server]: 0.00 Seconds Passed
[INFO] [1666435026.498143700] [turtle_circle_server]: 0.00 Seconds Passed
[INFO] [1666435026.498297800] [turtle_circle_server]: 0.00 Seconds Passed
[INFO] [1666435026.498427400] [turtle_circle_server]: 0.00 Seconds Passed
...
class TurtleCircleNode : public rclcpp::Node {
private:
rclcpp::Service<SetBool>::SharedPtr bool_server;
void turtle_circle(){
...
}
...
}
void server_callback(const std::shared_ptr<SetBool::Request> request,
const std::shared_ptr<SetBool::Response> response){
if (request->data) {
turtle_circle();
}
response->success = true;
response->message = "Turtle successfully drawed Circle";
}
public:
TurtleCircleNode() : Node("turtle_circle_server"){
bool_server = this->create_service<SetBool>(
"turtle_circle",
std::bind(&TurtleCircleNode::server_callback, this, std::placeholders::_1, std::placeholders::_2)
create_service는 다음과 같은 정보를 필요로 합니다.
service callback은 request와 response 두 데이터를 매개변수로 받습니다. 때문에, std::placeholders::_1, _2가 명시된 것이 보입니다. 더불어, 거북이를 움직이기 위해 Publisher도 하나 생성하였습니다.
이렇게 C++ 코드까지 살펴보았는데요, Action의 C++ 코드는 이번 강의에서는 넘어가도록 하겠으며, 링크를 남겨두겠습니다.
https://docs.ros.org/en/foxy/Tutorials/Intermediate/Writing-an-Action-Server-Client/Cpp.html
지금까지 우리의 실습을 책임졌던 src 로봇이 어떻게 만들어졌는지 살펴보면서, 실제 로봇 제품의 개발 과정을 살펴봅시다.
항상 로봇 시스템을 제작하기 전, 저는 부품의 수급부터 계획합니다.
이러한 이유로 로봇 제작에는 일정 비용과 경험이 요구됩니다. 하지만 모두 피와 살이 되니 아낌없이 투자하시기 바랍니다.
로봇 개발의 초기부터 너무 모든 것을 고려할 필요는 없습니다. 작은 사이즈의 테스트 베드를 구축하여 기능과 경험을 충분히 쌓은 뒤, 점차 하드웨어를 개선해 나가면서 업그레이드를 진행합니다.
함께하는 개발은 언제나 즐겁습니다.
시뮬레이션을 통해 제어기와 Odometry Driver, 자율 주행 로직의 검증이 완료되면, 실제 로봇에 이를 적용합니다. 적용과 검증을 반복하면서 로봇을 계속해서 개선해나갑니다.
작은 팁을 이야기하자면, 발생하는 모든 기록들을 문서화하는 것을 추천드립니다. 하드웨어를 다루다보면, 방금까지 되던 기능이 갑자기 말썽을 일으켜서 하루를 날리는 날도 있습니다. 항상 모든 것을 기록합시다.
강의의 마지막으로, ROS / ROS 2에 대한 프로젝트들, 센서들, 도전해볼 수 있는 것들을 제시해보고자 합니다.
Tutorial & Courses
Certification
Open Source Contribution
김수영 / Kim Soo Young