๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ

11. [Option] Action Server & Client ์ž‘์„ฑํ•ด๋ณด๊ธฐ – python

๐Ÿ–‹๏ธ

11. [Option] Action Server & Client ์ž‘์„ฑํ•ด๋ณด๊ธฐ โ€“ python

์ด๋ฒˆ ์‹œ๊ฐ„์—๋Š”, ์ง€๋‚œ ๊ฐ•์˜์—์„œ ์‚ดํŽด๋ณด์•˜๋˜ ๋ฏธ๋กœ์ฐพ๊ธฐ ์˜ˆ์ œ์˜ ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•ด๋ณด๊ณ ์ž ํ•˜๋ฉฐ, ๋ถ„์„ ๊ณผ์ •์—์„œ, ์ œ๊ฐ€ ๋งˆ์ฃผ์ณค๋˜ ๋ฌธ์ œ๋“ค๊ณผ, ๋””๋ฒ„๊น… ํฌ์ธํŠธ๋“ค๋„ ํ•จ๊ป˜ ๊ณต์œ ํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

$ roslaunch gcamp_gazebo maze_world.launch

# ์ƒˆ ํ„ฐ๋ฏธ๋„
$ rosrun action_tutorial maze_action_server.py
==== MazeActionClass Constructed ====
==== Waiting for Client Goal...  ====

# ์ƒˆ ํ„ฐ๋ฏธ๋„
$ rosrun action_tutorial maze_action_client.py
[INFO] [1610254457.453111, 322.000000]: Action Server Found.../maze_action_server
Enter numbers [or stop] : 

  • ์šฐ์„ , ํŒจํ‚ค์ง€ ํŒŒ์ผ ๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณผ๊นŒ์š”??
action_tutorial/
โ”œโ”€โ”€ CMakeLists.txt
โ”œโ”€โ”€ action
โ”‚   โ””โ”€โ”€ Maze.action
โ”œโ”€โ”€ launch
โ”‚   โ”œโ”€โ”€ fibonacci_server.launch
โ”‚   โ””โ”€โ”€ maze_runner.launch
โ”œโ”€โ”€ package.xml
โ””โ”€โ”€ src
    โ”œโ”€โ”€ fibonacci_action_client.py
    โ”œโ”€โ”€ fibonacci_action_server.py
    โ”œโ”€โ”€ gazebo_handler_test.py
    โ”œโ”€โ”€ maze_action_client.py
    โ”œโ”€โ”€ maze_action_server.py
    โ””โ”€โ”€ mazepkg
        โ”œโ”€โ”€ __init__.py
        โ”œโ”€โ”€ basic_cmd_vel.py
        โ”œโ”€โ”€ gazebo_handler.py
        โ””โ”€โ”€ image_converter.py

gcamp_gazebo/
โ”œโ”€โ”€ CMakeLists.txt
โ”œโ”€โ”€ launch
โ”‚   โ”œโ”€โ”€ gazebo_world.launch
โ”‚   โ”œโ”€โ”€ maze_world.launch
โ”‚   โ””โ”€โ”€ robot_description.launch
โ””โ”€โ”€ ...
    

  • maze_world.launch ๋ฅผ ํ†ตํ•ด ์‚ฌ์ง„๊ณผ ๊ฐ™์€ ์ƒˆ๋กœ์šด world๋ฅผ gazebo_ros์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • maze_runner.launch ํŒŒ์ผ์€ ๋‹จ์ˆœํžˆ action server๋ฅผ ์‹คํ–‰์‹œํ‚ค๊ธฐ ์œ„ํ•œ ๊ฐ„๋‹จํ•œ launch file์ž…๋‹ˆ๋‹ค.
<launch>
    <node pkg="action_tutorial" type="maze_action_server.py" name="maze_action_server" output="screen" />
</launch>

server ๋ถ€ํ„ฐ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. class ์„ค๊ณ„๋ฅผ ํ•˜๊ธฐ ์•ž์„œ, ํ•ด๋‹น ํด๋ž˜์Šค์—๊ฒŒ ํ•„์š”ํ•œ instance, method๋ฅผ ๊ธฐ๋Šฅ์— ๋งž์ถ”์–ด ์ƒ๊ฐํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  1. client๋กœ๋ถ€ํ„ฐ ๊ฒฝ๋กœ์˜ ๋ฐฐ์—ด์ด ๋“ค์–ด์˜ค๋ฉด
  1. ํ•ด๋‹น ๋ฐฉํ–ฅ์œผ๋กœ ์šฐ์„  ์œ„์น˜๋ฅผ ํ‹€๊ณ  (์ด์ „ service ์‹œ๊ฐ„์— ํ–ˆ๋˜ ๊ธฐ๋Šฅ๊ณผ ๋™์ผํ•˜๋„ค์š”)
  1. ๋ฒฝ์ด ๋‚˜์˜ฌ๋•Œ๊นŒ์ง€ ์ „์ง„ํ•ฉ๋‹ˆ๋‹ค. (์ด๊ฑด, topic์‹œ๊ฐ„์— parking ์˜ˆ์ œ๋กœ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค.)
  1. ๊ฒฝ๋กœ์˜ ๋ฐฐ์—ด ๋์— ๋‹ค๋‹ค๋ฅผ ๋•Œ๊นŒ์ง€ ์œ„ ๊ณผ์ •์„ ๋ฐ˜๋ณตํ•ฉ๋‹ˆ๋‹ค. (action์œผ๋กœ ์„ค๊ณ„ํ•˜๋ฉด, feedback์œผ๋กœ ์ง„ํ–‰ ์ƒํ™ฉ์„ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๊ณ , ๊ทธ๋™์•ˆ client๋Š” ๋‹ค๋ฅธ ์ผ์„ ํ•  ์ˆ˜ ์žˆ๊ฒ ์ง€์š”.)
  1. ์ตœ์ข… ์œ„์น˜์— ๋„์ฐฉํ•˜๋ฉด, ์ดˆ๋ก ๋ฐ•์Šค๊ฐ€ ๋ณด์ด๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. (์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ค„์•ผ ํ•ฉ๋‹ˆ๋‹ค.)
  1. ์ดˆ๋ก ๋ฐ•์Šค์— ๋„๋‹ฌํ–ˆ๋‹ค๋ฉด success, ์‹คํŒจํ–ˆ๋‹ค๋ฉด fail!!

์ž, ์œ„ ์ƒํ™ฉ๋“ค์„ ์œ ๋…ํ•˜๋ฉด์„œ server์˜ MazeActionClass๋ฅผ ๋ถ„์„ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Import


import math
import time
import rospy
import actionlib

from mazepkg.basic_cmd_vel import GoForward, Stop, Turn
from mazepkg.gazebo_handler import GazeboResetSimulation
from mazepkg.image_converter import ImageConverter

from nav_msgs.msg import Odometry
from geometry_msgs.msg import Twist
from sensor_msgs.msg import LaserScan
from tf.transformations import euler_from_quaternion

from action_tutorial.msg import MazeAction, MazeFeedback, MazeResult

Constructor ๋ถ„์„


class MazeActionClass(object):

    _feedback = MazeFeedback()
    _result = MazeResult()

    def __init__(self, name):
        self._action_name = name
        self._yaw = 0.0
        self._scan = []
        self._current_direction = 3  # 0 1 2 3

        self._cmd_pub = rospy.Publisher("/cmd_vel", Twist, queue_size=1)
        self._odom_sub = rospy.Subscriber("/odom", Odometry, self.odom_callback)
        self._scan_sub = rospy.Subscriber("/scan", LaserScan, self.scan_calback)
        # self._image_sub = rospy.Subscriber('')
        self._action_server = actionlib.SimpleActionServer(
            self._action_name, MazeAction, execute_cb=self.ac_callback, auto_start=False
        )
        self._action_server.start()

        self._rate = rospy.Rate(5)

        print("==== MazeActionClass Constructed ====")
        print("==== Waiting for Client Goal...  ====")

  • ์™ธ๋ถ€์—์„œ๋„ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ static variable๋กœ _feedback, _result๋ฅผ ๋‹ด๊ณ  ์žˆ์œผ๋ฉฐ, ActionServer๋ฅผ ์ œ์™ธํ•˜๊ณ  ํฌ๊ฒŒ ์„ธ๊ฐ€์ง€ class๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
    • _cmd_pub : ๋กœ๋ด‡์—๊ฒŒ ์ด๋™ ๋ช…๋ น์„ ์ค๋‹ˆ๋‹ค.
    • _odom_sub: ๋’ค์— ์ด์•ผ๊ธฐํ•˜๊ฒ ์ง€๋งŒ ๋กœ๋ด‡์˜ ํšŒ์ „์„ ๋ณด๋‹ค ์ •ํ™•ํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    • _scan_sub : parking ์˜ˆ์ œ์™€ ๊ฐ™์ด lidar data๋ฅผ ๋ฐ›๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

self._odom_sub = rospy.Subscriber("/odom", Odometry, self.odom_callback)

์œ„ ๋ถ€๋ถ„์ด ์™œ ๋“ค์–ด๊ฐ”๋Š”์ง€์— ๋Œ€ํ•ด ์„ค๋ช…ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

while time.time() - start_time < request.time_duration:
        velocity_publisher.publish(cmd_vel)

    print("Done ...")
    cmd_vel.angular.z = 0.0
    velocity_publisher.publish(cmd_vel)

์ด์ „, turning service server์˜ ์ผ๋ถ€์ธ๋ฐ์š”, python time ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ์‹œ๊ฐ„๋งŒํผ ๋กœ๋ด‡์„ ์ด๋™์‹œํ‚ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์œ„ ๋ฐฉ์‹๋„ ๋‚˜์˜์ง€๋Š” ์•Š์ง€๋งŒ, ๋ฌธ์ œ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๋ฐ”๋กœ ์˜ค์ฐจ๊ฐ€ ๋ˆ„์ ๋œ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. (์ •ํ™•์„ฑ)

์˜ˆ๋ฅผ ๋“ค์–ด, ๋ˆˆ์„ ๊ฐ๊ณ , ๋˜‘๋ฐ”๋กœ ๊ฑธ์–ด๋ณด๋ฉด, ์•„๋ฌด๋ฆฌ ๋‚ด๊ฐ€ ๋ฐ˜๋“ฏํ•˜๊ฒŒ ๊ฑธ์—ˆ๋‹ค ํ• ์ง€๋ผ๊ณ  ๋ˆˆ์„ ๋– ๋ณด๋ฉด ์‹ค์ƒ ๊ฑธ์–ด์˜จ ๊ธธ์ด ๋ฐ˜๋“ฏํ•˜์ง€ ์•Š์ง€์š”?

ํ•œ๊ฑธ์Œ ํ•œ๊ฑธ์Œ์€ ๋˜๋ฐ”๋กœ ๊ฑธ์—ˆ์„์ง€ ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ, ์กฐ๊ธˆ์”ฉ์˜ ์˜ค์ฐจ๊ฐ€ ๋ชจ์—ฌ ๊ฒฐ๊ณผ์˜ ํฐ ์ฐจ์ด๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค.

์šฐ๋ฆฌ ๋กœ๋ด‡๋„ ๋ฏธ๋กœ ์•ˆ์—์„œ ๊ณ„์† ํšŒ์ „์„ ํ•˜๋ฉด์„œ ์˜ค์ฐจ๊ฐ€ ๋ˆ„์ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ทธ๋Ÿผ, ์ด๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ• ๊นŒ์š”?

Odom์˜ ๋“ฑ์žฅ


image from : answer.ros.org

self._odom_sub = rospy.Subscriber("/odom", Odometry, self.odom_callback)

(...)

def odom_callback(self, data):
        orientation_q = data.pose.pose.orientation
        orientation_list = [
            orientation_q.x,
            orientation_q.y,
            orientation_q.z,
            orientation_q.w,
        ]
        _, _, self._yaw = euler_from_quaternion(orientation_list)

์ง€๊ธˆ ์šฐ๋ฆฌ๋Š” gazebo ์‹œ๋ฎฌ๋ ˆ์ด์…˜์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋กœ๋ด‡์˜ ์ ˆ๋Œ€์ ์ธ ์œ„์น˜, ๋ฐฉํ–ฅ์„ ์•Œ์•„๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ‘๐Ÿ‘ ๊ทธ๋ฆฌ๊ณ , ์ด๋ ‡๊ฒŒ ๋„์ถœ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ”๋กœ /odom topic์•ˆ์— ๋‹ด๊ฒจ์žˆ์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  ๊ณ„์‚ฐ์˜ ํŽธ์˜๋ฅผ ์œ„ํ•ด quaternion์œผ๋กœ ๋“ค์–ด์žˆ์œผ๋ฏ€๋กœ ๋‹ค์‹œ๊ธˆ euler angle๋กœ ๋ฐ”๊ฟ”์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ’ก
ROS์—์„œ ์ด๋ฏธ ๋‘ ๊ฐ๋„ ์‚ฌ์ด์˜ ๋ณ€ํ™˜์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. โ‡’ tf.transformations
from tf.transformations import euler_from_quaternion, quaternion_from_euler

์‹œ๋ฎฌ๋ ˆ์ด์…˜์ด ์•„๋‹Œ, ์‹ค์ œ ๋กœ๋ด‡์€ ์ด๋ฅผ ์–ด๋–ป๊ฒŒ ์•Œ์•„๋‚ผ๊นŒ์š”?

์‚ฌ์‹ค ์ด๋Š” ๋กœ๋ณดํ‹ฑ์Šค์—์„œ ํ•ด๊ฒฐํ•˜๊ณ ํ•˜๋Š” ์ฃผ๋œ ๋ฌธ์ œ ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. Localization in Robotics์ด๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๊ฒ€์ƒ‰ํ•ด ๋ณด์‹œ๋ฉด ํ™œ๋ฐœํ•˜๊ฒŒ ์ง„ํ–‰์ค‘์ธ ์—ฐ๊ตฌ๋“ค์„ ์ฐพ์•„๋ณด์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

def robot_turn(self, euler_angle):
        target_rad = euler_angle * math.pi / 180

        turn_offset = 100
        self._rate.sleep()

        while abs(turn_offset) > 0.005:
            turn_offset = 0.7 * (target_rad - self._yaw)

            Turn.angular.z = turn_offset
            self._cmd_pub.publish(Turn)

        self._cmd_pub.publish(Stop)

๊ฒฐ๋ก ์ ์œผ๋กœ, ๋ฏธ๋กœ์ฐพ๊ธฐ ์˜ˆ์ œ์—์„œ๋Š” ๋กœ๋ด‡์˜ ํšŒ์ „์„ odom์„ ์ด์šฉํ•ด ๋ณด๋‹ค ์ •ํ™•ํ•˜๊ฒŒ ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

  • ์—ฌ๊ธฐ์„œ์˜ turn_offset = 0.7 * (target_rad - self._yaw) ์€ P Gain Control ์ด๋ฉฐ,

    ์ด ๋˜ํ•œ PID Control์ด๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๊ฒ€์ƒ‰ํ•ด ๋ณด์‹œ๋ฉด ํฅ๋ฏธ๋กœ์šด ๋‚ด์šฉ๋“ค์„ ์Šต๋“ํ•˜์‹ค ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐Ÿ˜‰

  • Matlab - Understanding PID Control, Part 1: What is PID Control?
Understanding PID Control, Part 1: What is PID Control?
Explore the fundamentals behind PID control. - Download Code Examples to Learn How to Automatically Tune PID Controller Gainshttps: http://bit.ly/2HKBh12Chan...
https://www.youtube.com/watch?v=wkfEZmsQqiA

Action Callback ๋ถ„์„


  • sequence loop

Maze.action

int32[] turning_sequence
---
bool success
---
string feedback_msg
def ac_callback(self, goal):
        success = True
        print("==== Maze Action Server Executing ====")

				# python enumerate
        for i, val in enumerate(goal.turning_sequence):
            # check that preempt has not been requested by the client
            if self._action_server.is_preempt_requested():
                rospy.logwarn("%s: Preempted" % self._action_name)
                self._action_server.set_preempted()
                success = False
                break
						
						# ํ„ฐ๋ฏธ๋„์— ํ‘œ์‹œ๋  ๋ฉ”์„ธ์ง€
            self._feedback.feedback_msg = "Turning to " + direction_str_dict[val]
            self._action_server.publish_feedback(self._feedback)

						# ํšŒ์ „๊ณผ ์ „์ง„์„ ๋ฐ˜๋ณตํ•ฉ๋‹ˆ๋‹ค.
            print("Turning Sequence : " + str(val))
            self.robot_turn(direction_dict[val])

            self._feedback.feedback_msg = "Moving Forward ..."
            self._action_server.publish_feedback(self._feedback)
            self.robot_go_forward()

            self._rate.sleep()

๋Œ€๋ถ€๋ถ„์˜ ์ฝ”๋“œ๋Š” ์ด์ „ ์‹œ๊ฐ„์— ์‚ดํŽด๋ณด์•˜์œผ๋ฉฐ, ์–‘์€ ๋งŽ์ง€๋งŒ ๋‹จ์ˆœํžˆ goal.turning_sequence๋ฅผ ์„ ํšŒํ•˜๋ฉด์„œ ํšŒ์ „, ์ „์ง„์„ ๋ฐ˜๋ณต์‹œํ‚ค๋Š” ๋ถ€๋ถ„์ž„์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • robot_go_forward
def robot_go_forward(self):
        self._rate.sleep()

        while self._scan[360] > 0.9:
            self._cmd_pub.publish(GoForward)
        self._cmd_pub.publish(Stop)

  • Success Condition
				if success:
            ic = ImageConverter()
            center_pixel = ic.center_pixel

            if sum(center_pixel) < 300 and center_pixel[1] > 100:
                self._result.success = True
                rospy.loginfo("Maze Escape Succeeded")
            else:
                self._result.success = False
                rospy.logerr("Maze Escape Failed")

            self._action_server.set_succeeded(self._result)

์ฃผ์–ด์ง„ ์ด๋™๊ฒฝ๋กœ๋Œ€๋กœ ๋กœ๋ด‡์ด ์ž„๋ฌด๋ฅผ ์™„๋ฃŒํ•œ ์ˆœ๊ฐ„!! ์ดˆ๋ก ๋ฐ•์Šค์˜ ์œ ๋ฌด์— ๋”ฐ๋ผ ์ตœ์ข… ์ž„๋ฌด ์„ฑํŒจ๋ฅผ ๊ฒฐ์ •์ง“๋Š”๋‹ค๊ณ  ํ–ˆ์—ˆ์ง€์š”? ์œ„ ์ฝ”๋“œ๊ฐ€ ๋ฐ”๋กœ ๊ทธ ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค.

  • center_pixel์€ ๋„์ฐฉ ์‹œ, ๋กœ๋ด‡์— ์žฅ์ฐฉ๋œ ์นด๋ฉ”๋ผ ์ด๋ฏธ์ง€ ์ค‘์‹ฌ ํ”ฝ์…€๊ฐ’์ž…๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€ ํ”ฝ์…€์„ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•ด OpenCV๋ฅผ ์‚ฌ์šฉํ•˜์˜€์œผ๋ฉฐ, cv::Mat ํฌ๋งท์— ๋”ฐ๋ผ ๋…น์ƒ‰์— ํ•ด๋‹นํ•˜๋Š” ๊ฐ’์„ ๊ธฐ์ค€์œผ๋กœ ์กฐ๊ฑด์„ ๊ฑธ์—ˆ์Šต๋‹ˆ๋‹ค.

OpenCV์— ๋Œ€ํ•œ ๊นŠ์€ ์„ค๋ช…์€ ์ƒ๋žตํ•˜๊ณ , ROS์˜ image message๋ฅผ cv::Mat๋กœ ์ „ํ™˜ํ•˜๋Š” ๋ถ€๋ถ„์„ ์งš๊ณ  ๋„˜์–ด๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค.

Image Converter


#! /usr/bin/env python

import rospy
import rospy
import cv2

from cv_bridge import CvBridge, CvBridgeError
from sensor_msgs.msg import Image


class ImageConverter:
    def __init__(self):
        # self.image_pub = rospy.Publisher("image_topic_2", Image, queue_size=1)

        self._center_pixel = []
        self._bridge = CvBridge()
        self._image_sub = rospy.Subscriber("camera/rgb/image_raw", Image, self.callback)

    def callback(self, data):
        try:
            cv_image = self._bridge.imgmsg_to_cv2(data, "bgr8")
        except CvBridgeError as e:
            print(e)

        # green
        # [  0 110   0]
        # gray
        # [105 105 105]

        self._center_pixel = cv_image[400, 400]

    @property
    def center_pixel(self):
        self._rate = rospy.Rate(5)
        self._rate.sleep()
        return self._center_pixel

bgr8 ํ˜•์‹์œผ๋กœ ๋ฐ”๊พธ์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, center_pixel์•ˆ์—๋Š” blue, green, red ์ˆœ์œผ๋กœ pixel ๊ฐ’์ด ์ฑ„์›Œ์ ธ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๋‚˜๋จธ์ง€ ์ฝ”๋“œ๋“ค์€ ๊ธฐ์กด๊ณผ ํฐ ์ฐจ์ด๊ฐ€ ์—†์œผ๋ฉฐ, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฐฉํ–ฅ์„ ๋‚˜ํƒ€๋‚ด๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•œ dictionary๋“ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
direction_dict = {0: -90, 1: 180, 2: 90, 3: 0}
direction_str_dict = {0: "Up", 1: "Right", 2: "Down", 3: "Left"}

์ข‹์Šต๋‹ˆ๋‹ค! ๊ทธ๋Ÿผ client๋กœ ์ด๋™ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค!! ๐Ÿฅณ๐Ÿฅณ

Maze Action Client


#! /usr/bin/env python

"""
action client node for maze gazebo example

referenced from robotigniteacademy
url : https://get-help.robotigniteacademy.com/t/understanding-ros-actions-client-class-exercise/1498

list input part is referenced from geeksforgeeks
url : https://www.geeksforgeeks.org/python-get-a-list-as-input-from-user/
"""

import time
import rospy
import actionlib

from enum import IntEnum
from action_tutorial.msg import MazeAction, MazeGoal
from tf.transformations import euler_from_quaternion, quaternion_from_euler

# For more detail, search actionlib_msgs/GoalStatus
class ActionState(IntEnum):
    PENDING = 0
    ACTIVE = 1
    PREEMPTED = 2
    SUCCEEDED = 3
    ABORTED = 4
    REJECTED = 5
    PREEMPTING = 6
    RECALLING = 7
    RECALLED = 8
    LOST = 9


def fb_callback(feedback):
    print(feedback)


action_server_name = "/maze_action_server"
rospy.init_node("maze_action_client")
action_client = actionlib.SimpleActionClient(action_server_name, MazeAction)

rospy.loginfo("Action Server Found..." + action_server_name)

goal = MazeGoal()
user_list = []


# try block to handle the exception
try:
    print("Enter numbers [or stop] : ")

    while True:
        user_list.append(int(input()))
# if the input is not-integer, just print the list
except:
    print("Your sequence list : ", user_list)

goal.turning_sequence = user_list

action_client.send_goal(goal, feedback_cb=fb_callback)
state_result = action_client.get_state()

rate = rospy.Rate(1)
rospy.loginfo("State Result from Server : " + str(state_result))

while state_result < ActionState.PREEMPTED:
    # Doing Stuff while waiting for the Server to give a result....
    rate.sleep()
    state_result = action_client.get_state()

if state_result == ActionState.SUCCEEDED:
    rospy.loginfo("Action Done State Result : " + str(state_result))
else:
    rospy.logwarn("Something went wrong, result state : " + str(state_result))

ใ„ด ํ”ผ๋ณด๋‚˜์น˜ ์˜ˆ์ œ์™€ ํฌ๊ฒŒ ๋‹ค๋ฅด์ง€ ์•Š์ง€์š”?

  • ์ฐจ์ด๊ฐ€ ์žˆ๋‹ค๋ฉด , Action Status๋ฅผ enum class๋กœ ๋ฏธ๋ฆฌ ์„ค์ •ํ•ด ๋‘์—ˆ์Šต๋‹ˆ๋‹ค. (์‚ฌ์‹ค ๋‚ด๋ถ€์ ์œผ๋กœ๋Š” ์ด๋ฏธ ์•„๋ž˜์™€ ๊ฐ™์ด ์ •์˜๋˜์–ด ์žˆ๋‹ต๋‹ˆ๋‹ค.)
from enum import IntEnum

(...)

class ActionState(IntEnum):
    PENDING = 0
    ACTIVE = 1
    PREEMPTED = 2
    SUCCEEDED = 3
    ABORTED = 4
    REJECTED = 5
    PREEMPTING = 6
    RECALLING = 7
    RECALLED = 8
    LOST = 9

  • ์ด์™€ ๋”๋ถˆ์–ด ๋กœ๋ด‡ ํšŒ์ „ service ์˜ˆ์‹œ์—์„œ๋„ ๋ณด์•˜๋˜ ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ input์„ ๋ฐ›๋Š” ๋ถ€๋ถ„, ๊ทธ์— ๋”ฐ๋ฅธ ์˜ˆ์™ธ์ฒ˜๋ฆฌ๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ™‚
# try block to handle the exception
try:
    print("Enter numbers [or stop] : ")

    while True:
        user_list.append(int(input()))
# if the input is not-integer, just print the list
except:
    print("Your sequence list : ", user_list)

์ง€๊ธˆ๊นŒ์ง€ ๋ฏธ๋กœ์ฐพ๊ธฐ Action ์˜ˆ์ œ์˜ ์„ค๋ช…์ด์—ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ‘ ๋ถ€์กฑํ•œ ์ฝ”๋“œ์™€ ์„ค๋ช…์ด๊ธฐ์— ๋ถ€๋„๋Ÿฝ์ง€๋งŒ, ์žฌ๋ฏธ์žˆ๋Š” ์ž‘์—…์ด์—ˆ์Šต๋‹ˆ๋‹ค ๐Ÿ˜Ž๐Ÿ˜Ž
Contribution์€ ์–ธ์ œ๋‚˜ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค!!

Github Repo


Road-Balance/gcamp_ros_basic
Contribute to Road-Balance/gcamp_ros_basic development by creating an account on GitHub.
https://github.com/Road-Balance/gcamp_ros_basic