코드를 분석해보자.
일단 함수가 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를 사용하기 위한 선언이라고 보면 될 듯
두번째줄 : 함수에 대입되는 값 두 개 중 하나를 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
'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 |