Study/[ROS] ROS Basic

ROS Basics(5. Action, Server and Client)

soohwan_justin 2021. 8. 29. 09:47

이 포스트는 theconstructsim.com의 ROS BASICS 를 참고하였습니다.

https://youtu.be/DBFYZRMLr70

 

 

이번 포스트에서 다룰 내용은

- ROS action이란 무엇인가?

- 로봇의 action을 어떻게 다루는가?

- action  server를 어떻게 호출하는가?

 

이 포스트에서 사용된 ROS 시뮬레이션 git code는 없는 것 같습니다. 일단은 내용 정리를 위해 참고 자료 그대로 포스팅 해두겠습니다. 

 

 

 

 

 

What are actions?

action이란, service에 대한 비동기(asynchronous) 호출과 같다고 보면 됩니다.

 

action은 service와 매우 비슷합니다. 우리가 action을 호출하면, service처럼 다른 node가 제공하는 기능을 호출하는 것과 같습니다. 차이점이라면, node가 service를 호출하면 service는  그 service가 끝날 때 까지 기다려야 하지만, action은 그럴 필요가 없습니다. 따라서, action은 다른 노드의 기능에 대한 비동기 호출입니다.

 

- 기능을 제공하는 node는 action server를 포함해야 합니다. action server는 다른 노드들이 action 기능을 호출할 수 있도록 합니다.

 

- 기능을 호출하는 node는 action client를 포함해야 합니다. action client는 어떤 node가 다른 node의 action server에 연결할 수 있도록 해줍니다.

 

 

로봇이 action을 제공할 때, 우리는 topic들의 list에서 확인할 수 있습니다. 우리가 사용할 action topic의 목록에는 cancel, feedback, goal, result, status가 있습니다.

 

- /fibonacci_as/cancel

- /fibonacci_as/feedback

- /fibonacci_as/goal

- /fibonacci_as/result

- /fibonacci_as/status

 

모든 action server는 저 다섯 가지의 topic들을 생성합니다. 위의 경우,

- fibonacci_as는 action server의 이름입니다.

- cancel, feedback, goal, result, status는 action server와 통신하기 위해 사용된 message입니다.

 

Calling an action server

fibonacci_as는 우리가 호출할 수 있는 action입니다. 만약 호출하면, 전방의 카메라로 사진을 찍기 시작하며, 초당 1장의 사진을 호출할 때 전달한 파라미터의 값 만큼 계속 촬영합니다.

 

action server를 호출한다는 것은 action server로 message를 보내는 것을 의미합니다. topic과 service와 같은 방식으로 동작합니다.

- topic message는 topic이 제공하는 정보 하나로 구성됩니다

- service message는 요청, 응답 부분 두 부분으로 구성됩니다.

- action message는 목표(goal), 결과(result), 피드백 세 부분으로 구성됩니다.

 

모든 action message는 그 패키지 안의 action 디렉토리에 정의된 message를 사용합니다.

 

앞으로의 예제에서 사용할 action message의 예시를 보겠습니다.

 

#goal definition
int32 order
---
#result definition
int32[] sequence
---
#feedback
int32[] sequence

 

- goal : Int32 타입의 nsecond라는 변수로 구성됩니다. 이 Int32는 ROS의 기본 메시지 중 하나입니다. 따라서, 이 메시지는 std_msgs_package에서 찾을 수 있습니다. 그리고 이는 ROS의 기본 패키지이기 때문에, Int32를 찾기 위해 이 메시지가 속한 패키지를 가리킬 필요도 없습니다.

 

- result : sequence라는 이름의 변수로 구성됩니다. int32의 배열 형태이며, 이 메시지도 std_msgs_package에 포함되어있습니다.

 

- feedback : result와 같습니다.

 

Actions provide feedback

action server를 호출하는 것은 우리 thread를 중단(interrupt)하지 않기 때문에, action server는 feedback이라는 message를 제공할 수 있습니다. 이 feedback은 action server가 그 action이 어떻게 진행되는 중인지 계속 알려줍니다. 이 메시지는 는 action이 진행되는 과정중에 생성됩니다.

 

Writing an action server

 


 

- 예제 1.

아래 예제에서는 action server가 호출되면, 주어진 order 만큼의 피보나치 수열을 생성합니다. action server goal message는 반드시 계산할 order를 가리켜야 합니다. feedback message는 현재 계산되는 중인 수열을 나타내며, result는 최종 피보나치 수열을 리턴합니다.

 

#! /usr/bin/env python
import rospy
 
import actionlib
 
from actionlib_tutorials.msg import FibonacciFeedback, FibonacciResult, FibonacciAction
 
class FibonacciClass(object):
 
  # create messages that are used to publish feedback/result
  _feedback = FibonacciFeedback()
  _result   = FibonacciResult()
 
  def __init__(self):
    # creates the action server
    self._as = actionlib.SimpleActionServer("fibonacci_as", FibonacciAction, self.goal_callback, False)
    self._as.start()
 
  def goal_callback(self, goal):
    # this callback is called when the action server is called.
    # this is the function that computes the Fibonacci sequence
    # and returns the sequence to the node that called the action server
 
    # helper variables
    r = rospy.Rate(1)
    success = True
 
    # append the seeds for the fibonacci sequence
    self._feedback.sequence = []
    self._feedback.sequence.append(0)
    self._feedback.sequence.append(1)
 
    # publish info to the console for the user
    rospy.loginfo('"fibonacci_as": Executing, creating fibonacci sequence of order %i with seeds %i, %i' % ( goal.order, self._feedback.sequence[0], self._feedback.sequence[1]))
 
    # starts calculating the Fibonacci sequence
    fibonacciOrder = goal.order
    for i in xrange(1, fibonacciOrder):
 
      # check that preempt (cancelation) has not been requested by the action client
      if self._as.is_preempt_requested():
        rospy.loginfo('The goal has been cancelled/preempted')
        # the following line, sets the client in preempted state (goal cancelled)
        self._as.set_preempted()
        success = False
        # we end the calculation of the Fibonacci sequence
        break
 
      # builds the next feedback msg to be sent
      self._feedback.sequence.append(self._feedback.sequence[i] + self._feedback.sequence[i-1])
      # publish the feedback
      self._as.publish_feedback(self._feedback)
      # the sequence is computed at 1 Hz frequency
      r.sleep()
 
    # at this point, either the goal has been achieved (success==true)
    # or the client preempted the goal (success==false)
    # If success, then we publish the final result
    # If not success, we do not publish anything in the result
    if success:
      self._result.sequence = self._feedback.sequence
      rospy.loginfo('Succeeded calculating the Fibonacci of order %i' % fibonacciOrder )
      self._as.set_succeeded(self._result)
 
if __name__ == '__main__':
  rospy.init_node('fibonacci')
  FibonacciClass()
  rospy.spin()

 

코드를 분석해보겠습니다.

 

이 예제의 경우, action server는 Fibonacci.action에서 정의된 action message를 사용합니다. 이 message는 ROS의 actionlib_tutorials 패키지에 있습니다.

 

feedback과 result로 publish할 message object들을 생성합니다.

 

FibonacciClass 클래스의 생성자 부분입니다. "fibonacci_as"라는 이름의 action server를 사용하며, 이 message는 FibonacciAction이라고 정의된 message를 사용합니다. 따라서, goal, feedback, result는 /fibonacci_as/goal, /fibonacci_as/feedback, /fibonacci_as/result 라는 topic을 사용하게 될 것이며, 호출 될 때마다 goal_callback 이라는 콜백 함수를 호출합니다.

 

 goal_callback 함수를 정의하는 부분입니다. 새로운 goal이 action server로 보내질 때마다, 이 함수가 호출될 것입니다.

line 29~31에서는 피보나치 수열을 초기화합니다.

line 34에서는 어떤 피보나치 수열을 계산하는지 출력해줍니다.

 

 

 

goal.order 값에 도달할 때까지 피보나치 수열을 계산합니다.

 

 

취소 요청이 들어올 경우에 대한 부분입니다. 취소 요청이 들어오면 취소되었다는 출력을 띄우고, success에는 False 값을 할당하며 반복문을 빠져나갑니다.

 

 

실제로 피보나치 수열을 계산하는 부분입니다. 피보나치 수열을 계산하며, 설정된 rate의 주기로 feedback 메시지를 publish해줍니다.

 

 

취소 요청 없이 계산이 다 끝나면, 지금까지 계산된 피보나치 수열을 result에 저장하고, success값 또한 true로 저장합니다.

 

 

위와 같이 요청을 보내게 되면,(전부 다 똑같이 입력할 필요는 없습니다. TAB 키를 활용하세요)

 

 

위와 같은 feedback 결과가 나오고

 

 

 

 

최종적으로 이런 result가 나옵니다.

 

- 예제 1. 끝

 


 

How does all that work?

사실, action은 디버깅을 할 때 유용하기 때문에 우리는  action이 동작할 때 이 안에서 어떻게 통신이 이루어지는지 이해해야합니다. 

 

이전에 보셨다시피, action은 3개의 부분으로 나뉘어집니다.

- goal

- result

- feedback

 

각각은 topic과 message의 타입에 대응됩니다. 예를 들어, fibonacci_as의 경우 다음과 같은 topic들이 포함됩니다.

 

- goal topic : /fibonacci_as/goal

- result topic : /fibonacci_as/result

- feedback topic : /fibonacci_as/feedback

 

action client와 action server의 통신을 나타낸 다이어그램을 다시 보겠습니다.

 

 

즉, action server가 호출될 때마다, 다음과 같은 절차를 순차적으로 진행합니다.

 

1. 어떤 node에서 action client가 action server를 호출할 때, 실제로 일어나는 일은 action client가 /fibonacci_as/goal topic을 통해, client로부터 요청된 goal을 action server로 보내는 것입니다.

 

2. action server가 goal을 시작할 때, action server는 /fibonacci_as/feedback topic을 통해 action client로 feedback을 보냅니다.

 

3. 마지막으로, action server가 goal을 마칠 때, /fibonacci_as/result topic을 통해 action client로 result를 보냅니다.

 

 

How to create your own action server message

topic, service, action 모두 ROS에서 기본적으로 제공하는 message들을 권장합니다. action의 경우, 다음 패키지들에서 제공하는 message들을 사용하는 것을 권장합니다

-action lib

1. Test.action

2. TestRequest.action

3. Twolnts.action

- actionlib_tutorials

1. Fibonacci.action

2. Averaging.action

 

하지만, 꼭 필요한 경우에는 직접 만들어야 합니다. 만드는 법은 topic, service와 비슷합니다.

 

1. package 디렉토리에 action 디렉토리를 생성합니다.

 

2. Name.action이라는 message file을 생성합니다.

 

- action message file의 이름은 나중에 action server나 action client에 쓰일 클래스의 이름을 결정합니다. 

- Name.action 파일은  3개의 하이픈(---)으로 구분된 세 개의 부분으로 나뉩니다.

 

#goal
package_where_message_is/message_type goal_var_name
---
#result
package_where_message_is/message_type result_var_name
---
#feedback
package_where_message_is/message_type feedback_var_name

 

- 만약 특정 부분이 필요가 없으면 그 부분은 비워두면 되지만, 그래도 하이픈으로 구분은 해줘야 합니다.

 

3. CMakeLists.txt와 package.xml 파일을 수정합니다.

 

 

How to prepare CMakeLists.txt and package.xml files for custom action messages compilation

CMakeLists.txt 파일에서는 다음 4가지를 수정합니다.

- find_package()

- add_action_files()

- generate_messages()

- catkin_package()

 

1. find_package()

topic, service, action의 message를 컴파일하기 위해서 필요한 모든 패키지들을 기술합니다. 여기에 기술된 패키지들은 package.xml 파일에서 build 의존성에 기술해야 합니다.

find_package(catkin REQUIRED COMPONENTS
      # your packages are listed here
      actionlib_msgs
      std_msgs
)

 

2. add_action_files()

이 함수는 이 패키지에서 만든, action 디렉토리에 있는 action message들을 포함합니다. FILES 태그 밑에 기술합니다.

add_action_files(
      FILES
      Name.action
)

 

3. generate_messages()

이 패키지들은 import된 action message의 컴파일을 위해 필요합니다. find_package에서 기술한것과 같이 이 부분에도 기술해줍니다.

generate_messages(
      DEPENDENCIES
      actionlib_msgs std_msgs
      # Your packages go here
)

 

4. catkin_package()

우리 패키지로부터 뭔가를 실행할 때 필요한 패키지들입니다. 여기에 기술된 패키지들은 package.xml 파일에서 exec_depend태그에 기술해줘야 합니다.

catkin_package(
      CATKIN_DEPENDS
      rospy
      # Your package dependencies go here
)

 

요약하자면, CMakeLists.txt 파일은 다음과 같이 수정하면 됩니다.

cmake_minimum_required(VERSION 2.8.3)
project(my_custom_action_msg_pkg)
 
## Find catkin macros and libraries
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
## is used, also find other catkin packages
find_package(catkin REQUIRED COMPONENTS
  std_msgs 
  actionlib_msgs
)
 
## Generate actions in the 'action' folder
add_action_files(
   FILES
   Name.action
 )
 
## Generate added messages and services with any dependencies listed here
generate_messages(
   DEPENDENCIES
   std_msgs actionlib_msgs
 )
 
catkin_package(
 CATKIN_DEPENDS rospy
)
 
## Specify additional locations of header files
## Your package locations should be listed before other locations
# include_directories(include)
include_directories(
  ${catkin_INCLUDE_DIRS}
)

 

 

Modification of package.xml

 

1. message를 컴파일하기 위해 필요한 패키지들을 추가합니다. 예를 들어, 만약 .action 파일이 std_msgs패키지에서 정의 된 형식의 message를 하나 사용한다고 해보겠습니다. 만약 우리가 nav_msgs/Odometry를 import해야 한다면, <build_depend> 태그에서 nav_msgs 패키지를 다음과 같이 추가해야 합니다.

<build_depend>nav_msgs<build_depend>

 

 

2. 만약 우리 패키지 안에서 어떤 프로그램을 실행 할 것이라면, 다음과 같이 패키지를 <exec_depend>태그에 기술해줘야 합니다.

<build_export_depend>nav_msgs<build_export_depend>
<exec_depend>nav_msgs<exec_depend>

 

3. custom action message를 컴파일 할 때는, 반드시 actionlib_msgs를 <build_depend> 태그에 추가해줘야 합니다.

<build_depend>actionlib_msgs</build_depend>

 

4. 만약 python을 이용한다면, rospy 를 <exec_dependency>에 반드시 추가해줘야 합니다.

<build_export_depend>rospy<build_export_depend>
<exec_depend>rospy<exec_depend>

 

요약하자면, package.xml파일은 다음과 같이 수정되어야 합니다.

 

<?xml version="1.0"?>
<package format="2">
  <name>my_custom_action_msg_pkg</name>
  <version>0.0.0</version>
  <description>The my_custom_action_msg_pkg package</description>
  <maintainer email="user@todo.todo">user</maintainer>
  <license>TODO</license>
 
  <buildtool_depend>catkin</buildtool_depend>

  <build_depend>actionlib</build_depend>
  <build_depend>actionlib_msgs</build_depend>
  <build_depend>rospy</build_depend>
  <build_depend>std_msgs</build_depend>

  <build_export_depend>actionlib</build_export_depend>
  <build_export_depend>actionlib_msgs</build_export_depend>
  <build_export_depend>rospy</build_export_depend>

  <exec_depend>actionlib</exec_depend>
  <exec_depend>actionlib_msgs</exec_depend>
  <exec_depend>rospy</exec_depend>
 
  <export>
  </export>
</package>

'Study > [ROS] ROS Basic' 카테고리의 다른 글

ROS_Basics(4. Service. Server and Client)  (0) 2020.11.03
ROS Basics(3. Topic, Subscriber)  (0) 2020.11.02
ROS Basics(2. Topic, Publisher)  (0) 2020.10.31
ROS Basics(1)  (0) 2020.10.31