본문 바로가기

LAB/ROS

ROS(2) Wall following algorithm python

728x90
반응형

 

 

코드를 분석해보자. 

일단 함수가 3개, main에선 try와 except를 사용하여 구별한다.

 

첫번째 함수 : def update_command_vel(linear_vel, angular_vel)

두번째 함수 : def scan_callback(msg)

세번째 함수 : def load_arguments()

 

reactive system architecture에만 집중하는, 

virtual triangle wall follwer algorithm을 기본으로 한 알고리즘이다. 

 

 

reactive system architecture

 

 

+) 반응형 시스템 아키텍처는 반응형 프로그래밍에서 제공되는 응답성, 유연성 및 복원력을 활용하여 다양한 구성 요소가 계속 작동할 수 있고, 만약 구성 요소 중 하나가 손상된 경우에도 성공할 수 있도록 하는 컴퓨터 시스템 패러다임이다. 

+) 다양한 구성 요소의 예시로는 소프트웨어 응용 프로그램, 데이터베이스 및 서버 등이 있다. 

 

 

virtual triangle wall follower algorithm

 

[센서가 포착한 거리]를 사용하여 [직각 삼각형 모델]을 만든다.

로봇이 벽에서부터 원하는 거리에 도달하기 위해 얼마나 회전해야 하는 지 계산할 수 있다.

 

두 가지 값을 사용한다. 

(1) Dwall(wall_distance) : 로봇이 벽에서부터 얼마나 멀리 떨어지려고 하는지를 나타냄

(2) Wall_lead : 로봇의 회전속도에 영향을 줌

 

 

두 개의 다른 각도에서부터 벽까지의 거리를 구하면, 

모델링된 삼각형의 카테투스(직각삼각형에서, 직각과 인접한 두 변)의 길이를 구할 수 있다. 

결과적으로 로봇에 가장 가까운 각도를 얻을 수 있다. 

 

 

(+) 세 가지 다른 상태가 정의되며, 로봇은 감지하는 것에 따라 그 상태를 거친다. 

(1) state 0 : Wandering, 로봇은 벽을 감지할 때까지 6초마다 방향을 무작위로 바꾼다. 

(2) state 1 : 로봇은 벽 쪽으로 돈다. 그리고 벽에 더 가까워질 때까지 이동한다. 

(3) state 2 : 벽에 부딪히지 않도록, 작은 고정장치로 전술한 공식을 적용한다. 

     -> 벽에 너무 가까우면(0.5m) 각도 속도가 빨라져 더 공격적으로 회전함

 

 

algorithm 분석

 

 

#!/usr/bin/env python

 

1. python script를 실행 가능하게 해준다.

 

모든 ROS 노드는 이와 같은 코드를 가장 위에 선언하고, 이렇게 함으로써 스크립트가 실행가능한지를 확인한다.

-> 스크립트가 실행되는 장치에서 [python이 설치된 위치]에 영향을 받지 X

-> 즉, python의 위치를 동적으로 찾아 실행하겠다는 의미  

 

 

 

import math
import random
import sys
import time
import rospy

 

2. import 명령어를 사용하여 모듈을 가져올 수 있다. 

 

첫번째줄 : C 표준에서 정의된 수학 함수에 대한 엑세스를 제공한다. 

두번째줄 : random 모듈로, randrange() 함수와 같이 사용하면 범위 안 정수 중 하나를 무작위로 얻을 수 있다. 

세번째줄 : sys 모듈로, 파이썬 인터프리터를 제어할 수 있는 모듈이다. 

-> python test.py 1 2 3 4 5 이라고 치면 -> ['test.py', '1', '2', '3', '4', '5'] 이 출력된다. 

네번째줄 : time 모듈로, 다양한 함수를 이용하여 현재 시간을 나타내곤 한다. 

다섯째줄 : ROS 노드 작성 시 rospy를 import 해주어야 하므로 필수적이다. 

-> rospy를 통해 ROS Topics, Services 및 Parameters를 python으로 접근 가능하다. 

 

 

 

from geometry_msgs.msg import Twist
from sensor_msgs.msg import LaserScan
from rospy.exceptions import ROSInterruptException

 

3. from A import B : A 모듈로부터 B 함수를 가져오라는 의미

, 필요한 것들을 import 한다. 

 

(from A import * 라고 작성하면, A 모듈 내의 모든 함수를 불러온다.)

첫번째줄 : ROS 메세지 중 geometry_msg 모듈을 import한다.(/cmd_vel에 필요한 msgs)(로봇의 속도를 다룰 때 필요)

-> Twist는 드론의 x, y, z축의 각도나 선속도 정보를 담고 있는 클래스이다. (아래에 상세 설명 有)

두번째줄 : LaserScan 메시지 타입을 추가한다. (/scan에 필요한 msg)(Lidar를 사용할 때 필요)

세번째줄 : ROSInterruptException은 중단된 작업에 대한 exception으로, rospy.sleep()과 rospy.Rate와 같이 쓰인다. 

 

#Laser scans angles are measured counter clockwise, with 0 facing forward
# (along the x-axis) of the device frame
#
#
Header header
float32 angle_min        # start angle of the scan [rad]
float32 angle_max        # end angle of the scan [rad]
float32 angle_increment  # angular distance between measurements [rad]
float32 time_increment   # time between measurements [seconds]
float32 scan_time        # time between scans [seconds]
float32 range_min        # minimum range value [m]
float32 range_max        # maximum range value [m]
float32[] ranges         # range data [m] (Note: values < range_min or > range_max should be discarded)
float32[] intensities    # intensity data [device-specific units]

 

geometry_msgs와 sensor_msgs를 import했을 때 사용할 수 있는 것들이다. 

 

 

g_distance_wall = 0.2
g_wall_lead = 0.4
g_pub = None
g_sub = None
g_side = 0
g_alpha = 0
g_linear_speed = 0.1
g_state = 0
g_turn_start_time = 0
g_wall_direction = None
g_start_sim_time = None

 

4. 필요한 변수 선언

 

첫번째줄 : 터틀봇이 벽에 가까이 갈 때, 벽과 터틀봇 사이의 최소 거리를 나타내는 변수 

두번째줄 : 로봇의 터닝 속도(turning speed) 

세번째줄 : g_pub는 ros의 Publisher

네번째줄 : g_sub는 ros의 Subscriber

-> g_pub는 마스터에 등록된 토픽의 이름, g_sub는 g_pub와 직접 연결하여 메시지를 토픽으로 송수신하게 됨

다섯째줄 : left로부터 wall follow할 경우 +1, right로부터 wall follow할 경우 -1을 해줄 값이다.

여섯째줄 : 회전속도(각속도) 값이다.

일곱째줄 : 직선속도 값이다. 

여덟째줄 : 상태(state)를 나타낸다. 

-> state 0 = wall을 찾으며 주변 배회, state 1 = wall을 찾았으므로 그 위치로 이동, state 2 = wall을 따라간다. 

아홉째줄 : 로봇이 마지막으로 회전했을 때의 start_time을 저장할 값. state 0과 1에서 사용된다. 

열번째줄 : 벽이 발견된 bearing, 방향을 저장할 값이다. state 0과 1에서 사용된다.

열한번째줄 : [어떤 알고리즘이 시작하는 가에 대한] 시뮬레이션 time을 저장할 값 

 

** NoneType class의 유일한 객체 'None'으로 생성된  것이 'None' 데이터 타입이다. 임의의 자료형을 대입할 수 있다. 

 

 

 

def update_command_vel(linear_vel, angular_vel):
    msg = Twist()
    msg.linear.x = linear_vel
    msg.angular.z = angular_vel
    g_pub.publish(msg)

 

5. 첫번째 함수, update_command_vel

 

함수 안에 들어있는 'Twist()'란 무엇일까?

Twist는 메시지의 타입을 의미한다. Twist 메시지 형식은 Vector3 형식을 기반으로 한다.

Twist는 x, y, z축 방향으로의 선속도(linear), x, y, z축에 대한 각속도(angular) 데이터를 포함한다.

-> 터미널 창에 rosmsg show geometry_msgs/Twist를 입력해보면 Vector로 이루어짐을 알 수 있다. 

-> linear, angular를 사용하기 위한 선언이라고 보면 될 듯

rosmsg : ros메세지 선언 정보를 확인하는 명령어

 

두번째줄 : 함수에 대입되는 값 두 개 중 하나를 linear.x 변수에 넣어둔다. 

세번째줄 : 함수에 대입되는 값 두 개 중 하나를 angular.z 변수에 넣어둔다. 

네번째줄 : g_pub.publish()를 통해 publish하며, 이 안에는 publish할 message가 들어간다. 

-> 이전에 선언한 g_pub로 하여금, Twist라는 인수를 'cmd_vel'에 값을 전하게 한다. 

 

** publisher는 메세지를 발행하는 역할. 그러나 수신인이 없다는 특징이 있다.

** subscriber는 메세지를 경청해주는 역할이다. 

** message는 Topic이 데이터나 정보를 주고 받을 때 사용하는 포멧이다. 

   [토픽은 데이터, 메세지는 정의된 자료형이라고 생각하면 될 듯]

 

 

 

 

def scan_callback(msg):
    scan_max_value = msg.range_max

    # (1) regions
    regions = {
        'N':  min(min(msg.ranges[len(msg.ranges) - 4 : len(msg.ranges) - 1] + msg.ranges[0:5]), scan_max_value),
        'NNW':  min(min(msg.ranges[11:20]), scan_max_value),
        'NW':  min(min(msg.ranges[41:50]), scan_max_value),
        'WNW':  min(min(msg.ranges[64:73]), scan_max_value),
        'W':  min(min(msg.ranges[86:95]), scan_max_value),
        'E':  min(min(msg.ranges[266:275]), scan_max_value),
        'ENE':  min(min(msg.ranges[289:298]), scan_max_value),
        'NE':  min(min(msg.ranges[311:320]), scan_max_value),
        'NNE':  min(min(msg.ranges[341:350]), scan_max_value),
    }

	# (2) global
    global g_side, g_alpha, g_state, g_turn_start_time, g_wall_direction

	# (3) robot : wander
    if g_state == 0:  
        for r, v in regions.items():
            if r in ["N", "W", "E", "NW", "NE"] and v < scan_max_value:
                print('Will change to state 1: drive towards wall ({})'.format(r))
                g_state = 1
                g_wall_direction = r
                g_turn_start_time = time.time()
                return

        delta_time = time.time() - g_turn_start_time

        if delta_time > 6:
            rand = random.randrange(0, 5)
            g_alpha = math.pi / 2 - rand * math.pi / 4
            g_turn_start_time = time.time()
        elif delta_time > 1:
            g_alpha = 0
     
    # (4) robot : drive towards wall
    elif g_state == 1:  
        min_distance = g_distance_wall + 0.3

        if regions['N'] < min_distance or regions['NW'] < min_distance or regions['NE'] < min_distance:
            g_state = 2

            if g_side == 0:
                left = (regions['W'] + regions['NW']) / 2
                right = (regions['E'] + regions['NE']) / 2

                if left < scan_max_value or right < scan_max_value:
                    g_side = -1 if right < left else 1
                else:
                    g_side = random.randrange(-1, 2, 2)

            print('Will change to state 2: follow wall from the {}'.format('left' if g_side == 1 else 'right'))
            return

        delta_time = time.time() - g_turn_start_time

        if delta_time <= 1:
            if g_wall_direction == 'W':
                g_alpha = math.pi / 2
            elif g_wall_direction == 'NW':
                g_alpha = math.pi / 4
            elif g_wall_direction == 'N':
                g_alpha = 0
            elif g_wall_direction == 'NE':
                g_alpha = -math.pi / 4
            elif g_wall_direction == 'E':
                g_alpha = -math.pi / 2
        else:
            g_alpha = 0
    
    # (5) robot : follow wall 
    elif g_state == 2:  

        y0 = regions['E'] if g_side == -1 else regions['W']
        x1 = (regions['ENE'] if g_side == -1 else regions['WNW']) * math.sin(math.radians(23))
        y1 = (regions['ENE'] if g_side == -1 else regions['WNW']) * math.cos(math.radians(23))

        if y0 >= g_distance_wall * 2 and regions['N'] < scan_max_value:
            g_alpha = -math.pi / 4 * g_side
        else:
            front_scan = min([regions['N'], regions['NNW'] + (scan_max_value - regions['WNW']), regions['NNE'] + (scan_max_value - regions['ENE'])])

            turn_fix = (0 if front_scan >= 0.5 else 1 - front_scan)

            abs_alpha = math.atan2(y1 - g_distance_wall,
                                x1 + g_wall_lead - y0) - turn_fix * 1.5
            
            g_alpha = g_side * abs_alpha

 

6. 두번째 함수, scan_callback

 

 

(1) regions

 

지역을 9개의 region으로 나누고, 지역별로 가능한 거리를 측정한다. dictionary 형태이다. 

위의 9개의 구역을 북쪽(N), 동쪽(E), 서쪽(W)을 이용하여 표현할 수 있다. 

(1) W

(2) WNW

(3) NW

(4) NNW

(5) N

(6) NNE

(7) NE

(8) ENE

(9) E

 

dictionary란, [대응 관계], 즉 [한 쌍]으로 존재하는 자료형을 의미한다. 

순차적일 필요가 없고 [Key를 통해 Value를 얻는다]는 것이 핵심 포인트다. 

위의 코드에서 'N', 'NNW', 'NW' 등이 key이고, 그에 대응하는 우측의 min 값들이 value이다. 

** 참고로 dictionary 내에서 'N' : min(2,7) 이라면 'N' : 2 의 한 쌍으로 해석하면 된다. 

-> import math를 미리 선언해두었기에, min함수를 사용하면 괄호 안의 값들 중 작은 값을 출력한다. 

 

 

(2) global

 

전역 변수 선언.

scan_callback 함수 뿐만 아니라 스크립트 전체에서 해당 변수에 접근할 수 있도록 한다. 

즉 어디에서나 g_side, g_alpha, g_state, g_turn_start_time, g_wall_direction 변수에 접근 가능. 

 

 

(3) g_state == 0 : robot이 배회할 때의 상태(wander)

'regions'라는 dictionary 안에서 [wall이 탐지될 때까지] for문을 돌려보자. 

[1] key 값이 N, W, E, NW, NE일 때 (즉 1, 3, 5, 7, 9번 영역 중 한 군데일 때)

[2] value 값이 scan_max_value보다 작을 때

-> 이 두 조건을 모두 만족한다면 state 1(벽을 향해 이동) 상태로 상태 변경을 한다.

-> 방향을 g_wall_direction 값에 담아 고정하고, 터닝 포인트의 시간을 기록한다. 

 

robot 벽을 탐지하기 전까지, 6초마다 랜덤으로 direction을 바꾼다. 

(delta_time은 6초를 세아려 주는 변수라고 생각하면 된다.)

 

6초가 넘는다면, 또다시 랜덤 방향을 지정해준다. 

-> random.randrange(0, 5)를 통해 0이상 5미만의 난수(정수) 출력을 한다. 

-> 각속도값(g_alpha) = 3.14/2 - 랜덤 * (3.14/4) 로 나타낸다.                                                            -> 그 이유가 뭐일깡???

    ** math.pi는 3.14(원주율)을 출력한다. import math를 작성하였으므로 사용 가능한 함수다. 

-> 또 간격(6초)을 세아리기 위해 방향 터닝한 시간을 g_turm_start_time에 기록해둔다. 

    ** time.time() : 현재 시각을 기준으로, 초를 나타내는 실수형으로 반환한다. 유닉스 시간이라고도 한다. 

 

delta_time이 6보다 작거나 같고 1보다 크다면(elif이므로 이렇게 해석해줘야 한다.)

-> 각속도값(g_alpha)은 0이다.

-> 즉 방향을 변경할 때만 각속도값이 0보다 크고, 방향을 변경한 후 6초 동안은 g_alpha = 0으로 둔다고 보면 된다. 

 

 

(4) g_state == 1 : robot이 벽을 향해 이동할 때의 상태(drive towards wall)

 

wall로 방향을 틀고, 벽과 가까워질 때까지 주행한다. 

 

min_distance : 벽과 로봇 사이의 최소 거리(g_distance wall) + 0.3 

 

following wall을 시작하기에 충분히 가까운 지 체크하자. 

-> 'N', 'NW', 'NE'의 value 값들 중 하나라도 min_distance보다 작을 경우, 상태(g_state) = 2이다. 

    (즉 3, 5, 7번 영역의 value 값들 중 하나라도 min_distance보다 작을 경우)

    (g_state = 2라는 의미는, follow wall을 할 수 있다는 의미! 벽을 따라가면 된다는 의미!)

    -> g_side = 0일 경우 어느 방향으로부터 벽 따라 이동할 지 정하자.

        (left로부터 wall follow할 경우 +1, right로부터 wall follow할 경우 -1)

         -> left 변수 = ('W'의 value값 + 'NW'의 value값) / 2

             right 변수 = ('E'의 value값 + 'NE'의 value값) / 2

         -> 만약 left, right 중 하나라고 scan_max_value보다 작을 경우, g_side는 1 혹은 -1이다.(r<l이면 1)

         -> 그러나 left, right 둘다 scan_max_value보다 같거나 클 경우, -1에서부터 2까지 step = 2로 난수를 발생시킨다. 

              ( range(start, stop, step)의 문법을 가진 함수로, -1부터 2까지 2단계씩 뛰면 -1 혹은 1이 출력 가능하다.)

              (즉, left, right 둘다 scan_max_value보다 같거나 클 경우, g_side는 -1과 1 중 랜덤으로 정한다.)

 

state 0에서와 마찬가지로, delta_time 변수를 이용하여 turn to the wall, 방향을 트는 시간을 계산한다. 

 

delta_time이 1보다 작을 때 : g_wall_direction 방향에 따라 각각에 맞는 각속도 값을 부여한다. 

-> 1 번 영역일 때 : 각속도 = (3.14) / 2

-> 3 번 영역일 때 : 각속도 = (3.14) / 4

-> 7번 영역일 때 : 각속도 = - (3.14) / 4

-> 9 번 영역일 때 : 각속도 = - (3.14) / 2

-> 2, 4, 5, 6, 8번 영역일 때 : 각속도 = 0, 방향이 변하지 않는다는 의미

 

 

 

(5) g_state == 2 : robot이 벽을 따라갈 때의 상태(follw wall)

 

wall에 부딪히는 것을 피하기 위해, small fix로 이동한다. 

 

left로부터 wall follow할 경우, 

-> y0 = 'W'의 value, x1 = 'WNW'의 value * sin(23도), y1 = 'WNW'의 value * cos(23도)

-> x0, y0은 1번 영역에서의 point, x1, y1은 2번 영역에서의 point로 보면 된다. 

right로부터 wall follow할 경우, 

-> y0 = 'E'의 value, x1 = 'ENE'의 value * sin(23도), y1 = 'ENE'의 value * cos(23도)

-> x0, y0은 9번 영역에서의 point, x1, y1은 8번 영역에서의 point로 보면 된다. 

** math.radians(23)이란, 23도의 라디안값을 출력해주는 함수다.

** math.sin(a)란, a(라디안 말고, degree로 바뀐 a값)의 sin값을 출력해주는 함수다. 

 

만약, robot이 wall을 향해 돌진하고 있다면, sideways로 돌려야 한다. (turn시키자)

-> [1] y0이 [벽과 robot 사이 최소 거리의 2배] 이상일 때 

    [2] 'N'의 value가 scan_max_value보다 작을 때

    -> 이 두 조건을 모두 만족한다면 각속도 값(g_alpha)의 부호를 변경하자. [방향 변경을 의미한다.]

 

그렇지 않다면, 로봇 전면에서 스캔 정보를 확인한다. [front_scan]

-> 벽과 가깝다면, hit을 피하기 위해 turn하도록 조정한다. [turn_fix]

-> wall distance를 이어나갈 각속도를 계산하자. 이를 위해 abs_alpha라는 변수를 사용한다. [abs_alpha]

    ** math.atan2 : 두 점 사이의 상대좌표(x, y)를 받아 절대각을 -

    -> 각속도 값(g_alpha) = g_side(left, right 결정해줌) * abs_alpha로 구한다. 

 

 

 

def load_arguments():
    if len(sys.argv) > 1:
        if len(sys.argv) % 2 == 1:
            for i in range(1, len(sys.argv), 2):
                arg = sys.argv[i]
                value = sys.argv[i + 1]

                if arg == '--speed' or arg == '-s':
                    try:
                        value = float(value)
                        global g_linear_speed
                        g_linear_speed = value
                    except ValueError:
                        print('Error parsing speed value')
                        return False
                elif arg == '--wall_distance' or arg == '-d':
                    try:
                        value = float(value)
                        global g_distance_wall
                        g_distance_wall = value
                    except ValueError:
                        print('Error parsing wall distance value')
                        return False
                else:
                    print('Unrecognized argument: ', arg)
                    return False
        else:
            print('Incorrect number of arguments')
            return False

    return True

 

7. 세번째 함수, load_arguments

 

 

파이썬으로 작성된 파일을 시뮬레이션에서 실행할 때 인수(argument, 인자값)를 받아서 처리를 해야 된다. 

이처럼 로컬과 개발 등의 환경이 달라서 -> 인자값을 주어야 할 때  -> sys.argv에 값을 담아 처리할 수 있다. 

(즉, 파이썬에서는 sys 모듈을 이용하여 입력 인수를 직접 줄 수 있다.)

(그리고 sys 모듈의 argv는 명령창에서 입력한 인수들을 의미)

 

argument가 2개 이상일 때(if len(sys.argv) > 1:)

-> 또한 argument가 홀수 개일 때 

    -> [즉, argument가 2개 이상이면서 홀수 개일 때] = [3, 5, 7, 9 .. 개일 때]

         -> argv[1]부터 차곡차곡 arg와 value의 값을 담는다. 

         -> if arg == '--speed' or arg == '-s' 라면

             -> try : value의 float형을 value 변수에 넣고, 직선속도값(g_linear_speed)을 value값으로 둔다. 

             -> Value error(부적절한 값을 인자로 받았을 때) : error를 고지하고, False를 반환한다. 

         -> elif arg == '--wall _distance' or arg == '-d' 라면

             -> try : value의 float형을 value 변수에 넣고, 직선속도값(g_linear_speed)을 value값으로 둔다. 

             -> Value error(부적절한 값을 인자로 받았을 때) : error를 고지하고, False를 반환한다. 

        -> 다른 경우, 인식할 수 없는 인자임을 고지하고, False를 반환한다. 

** try : 실행할 코드/ except 예외이름 : 예외가 발생했을 때 처리하는 코드

-> 이런 경우, 실행할 코드를 try한다. 그다음 [특정 예외(예외 이름)가 발생했을 때만] except 구문을 실행한다. 

 

argument가 2개 이상일 때

-> 그러나 argument가 홀수 개가 아닐 때

     -> arguments의 개수가 부적절함을 고지하고, False를 반환한다. 

 

최종적으로 loop를 다 돌았을 때 False 반환을 하지 않았을 경우, True를 반환한다. 

 

 

 

try:
    if __name__ == '__main__':
        if load_arguments():
            print('Starting with values:')
            print('- linear speed: ', str(g_linear_speed))
            print('- distance to wall: ', str(g_distance_wall))
            print('')

            rospy.init_node('wall_following_robot')

            g_pub = rospy.Publisher('/cmd_vel', Twist, queue_size=1)
            g_sub = rospy.Subscriber('/scan', LaserScan, scan_callback)

            rate = rospy.Rate(20)

            g_start_sim_time = rospy.get_time()
            print('Started at {} seconds (sim time)'.format(g_start_sim_time))

            while not rospy.is_shutdown():
                update_command_vel(g_linear_speed, g_alpha)
                rate.sleep()
except ROSInterruptException:
    end_sim_time = rospy.get_time()
    print('Finished at {} seconds (sim time)'.format(end_sim_time))
    print('Ran for {} seconds'.format(end_sim_time - g_start_sim_time))

 

8. main

 

__name__ : 파이썬의 내장 변수 혹은 글로벌 변수

if __name__ == '__main__' :

-> 해당 파일 자체를 실행하는 경우에 아래의 코드를 실행한다. 

-> 즉, 다른 파일에서 해당 파일을 import해서 해당 함수를 실행한다면 아래의 코드들은 실행되지 않는다. 

 

 

(1) load arguments(세번째 함수)가 시행되면

 

rospy에게 해당 코드 부분을 알려준다  : rospy.init_node('wall_following_robot') 

-> rospy에게 코드의 고유한 이름(NAME)을 알려주는 부분으로, 매우 중요하다. 

-> rospy가 이 정보를 얻어야만 ROS Master와 통신이 시작된다. 

 

Publisher와 Subscriber

-> rospy.Publisher class를 사용하여 topic에 message를 게시하는 핸들을 만들 수 있다. 

-> 이것의 가장 일반적인 용도는 topic의 name, message class, type 등을 제공하는 것이다. 

-> 해당 핸들에서 publish()를 호출하여 메시지를 게시할 수 있다. 

-> rospy.Publihser를 만들기 위해 오직 필요한 것들은 the topic name, the Message class, the queue_size이다.

   ('/cmd_vel' 이름의 토픽에게 Twist(class)를 publish하겠다고 queue size를 1로 선언, Publisher 객체)

-> rospy.Subscirber는 Publisher와 유사하게 name, data_class(해당 토픽의 데이터 타입) 등이 필요하다.

    그러나 세 번째 파라미터인 callback 변수는 아래와 같은 의미이다. 콜백 함수는 두 번째 함수임!!

    [토픽이 publish될 때, 작동할 이벤트 리스너 함수를 콜백 함수의 형태로 요구함]

    [참고로 콜백 함수의 기본 argument는 구독한 메세지 객체임]

    (노드가 Laserscan 유형의 '/scan' topic에 가입했음을 선언한다.)

    (새 메세지가 수신되면 메시지를 첫번째 인수로 사용하여 콜백이 호출된다.)

 

rospy.Rate(20)은 무슨 의미일까?

-> Rate 명령어는 [loop 중에] [모든 작업에 사용되는 시간을 고려하여] loop를 20hz로 유지하려고 시도한다. 

-> 기본적으로 Rate 명령어는 loop를 지정한, 정확한 속도(hz단위)로 실행할 수 있다. 

-> 이것은 ROS로 looping하는 꽤나 표준적인 방법이다. 

 

rospy.get_time()이란 현재 시간을 float seconds로 받아오겠다는 뜻이다. 

-> 받아와서 g_start_sim_time 변수에 저장한다. 

 

 

(2) rospy.is_shutdown() 전까지

 

사용자가 Ctrl + C를 눌러 작동을 멈추기 전까지는 계속 루프가 돌려진다. 

 

직선속도값(g_linear_speed, 초기값 0.1)과 각속도값(g_alpha, 초기값 0)을 매개변수로 하는 

첫번째 함수 [update_command_vel]을 호출한다. 

 

rate.sleep()은 무한 루프에서 설정한 주기를 맞추기 위해 기다리는 함수이다. 

-> rate.sleep()은 [만약 종료로 인해 절전모드가 중단된 경우] [rospy.ROSInterruptException]을 발생시킬 수 있다. 

 

 

(3) except ROSInterruptException

 

이 예외는 발생하는 이유 : [sleep() 후 실수로 코드를 계속 실행하지 않기 위해서]

-> 표준 Python __main__ 체크 외에도 Ctrl + C를 누르거나, 노드가 종료될 때 발생할 수 있는 예외다. 

 

종료 시, [end_sim_time]에서 [g_start_sim_time]을 뺀 값을 Running time으로 출력한다. 

 

 

 

 

 

 

 

 

끝. 

 

** 아직 공부 단계라서 잘못된 정보가 있을 수 있습니다!! 있다면 댓글로 정정해주세요 :)

 

** 최대한 가독성 있게 작성했지만 가독성이 떨어진다면 ..

   창 두 개를 띄워, 코드와 설명 내용을 같이 읽는 것을 추천합니다

 

 

 

 

 

 

출처(전체 월팔로잉 깃허브 파일):

https://github.com/Octanas/Reactive-Wall-Following-Robot 

 

GitHub - Octanas/Reactive-Wall-Following-Robot: Algorithm for solving the robotics wall-following problem and a setup simulated

Algorithm for solving the robotics wall-following problem and a setup simulated environment for testing. - GitHub - Octanas/Reactive-Wall-Following-Robot: Algorithm for solving the robotics wall-fo...

github.com

 

728x90
반응형

'LAB > ROS' 카테고리의 다른 글

ROS(4) Turtlebot machine learning(DQN)  (0) 2024.02.05
ROS(3) simple driving code  (0) 2024.02.01
ROS(2-1) wall_following_robot 실행하기  (0) 2023.01.02
ROS(1) ROS 명령어 정리  (0) 2023.01.02