지난시간에는 RViz에 3D모델링 된 자동차를 띄우고, 바퀴를 제어하는 프로그래밍을 진행하였다. 이번 시간에는 odom
이라는 토픽을 만들어, RViz 오도메트리 프로그램에 전송하여 자동차를 가상공간에서 직접 움직여보고, 궤적을 남기는것을 추가하여 주행하는 프로그램을 작성하고 실제로 어떤식으로 움직이는지 확인해보자. (아래 사진은 지난시간 제어프로그래밍 사진이다.)
동작 방식은 위와 같다. driver
노드에서 xycar_motor
라는 모터 제어 토픽을 convertor
노드에 전송하고, 전송 받은 토픽을 가공하여, joint_states
토픽으로 RViz viewer
및 rviz_odom
이라는 노드에 전송한다. RViz viewer
는 가상 자동차 모델의 바퀴의 움직임을 제어하는 역할을 하고, rviz_odom
노드는 가상 자동차 모델을 실제로 움직이는 odom
토픽으로 전송한다.
각 토픽이 어떤 메세지를 주고받는지 터미널 명령어를 통해 알아보자.
xycar_motor
에는 header
및 angle
, speed
로 구성되어있다.
JointState
는 header
및 name
, position
,effort
로 구성되어있으며, xycar_motor 으로 부터 받은 토픽을 해석하여 다시 토픽에 담아서 다음 토픽으로 전달한다.
odem
은 position
,orientation
및 선속도
, 각속도
정보를 담고 있다.
코드를 통해 해당 메세지들이 어떻게 동작하는지 알아보자.
🚀rviz_odom.launch
<launch>
<param name="robot_description" textfile="$(find rviz_xycar)/urdf/xycar_3d.urdf"/>
<param name="use_gui" value="true"/>
<!-- rviz display -->
<node name="rviz_visualizer" pkg="rviz" type="rviz" required="true"
args="-d $(find rviz_xycar)/rviz/rviz_odom.rviz"/>
<node name="robot_state_publisher" pkg="robot_state_publisher"
type="state_publisher"/>
<node name="driver" pkg="rviz_xycar" type="odom_8_drive.py" />
<node name="odometry" pkg="rviz_xycar" type="rviz_odom.py" />
<node name="converter" pkg="rviz_xycar" type="converter.py" />
</launch>
🐍odom_8_drive.py
import rospy
import time
from xycar_motor.msg import xycar_motor
motor_control = xycar_motor()
rospy.init_node('driver')
pub = rospy.Publisher('xycar_motor', xycar_motor, queue_size=1)
def motor_pub(angle, speed):
global pub
global motor_control
motor_control.angle = angle
motor_control.speed = speed
pub.publish(motor_control)
speed = 3
while not rospy.is_shutdown():
angle = -50
for i in range(70):
motor_pub(angle, speed)
time.sleep(0.1)
angle = 0
for i in range(20):
motor_pub(angle, speed)
time.sleep(0.1)
angle = 50
for i in range(70):
motor_pub(angle, speed)
time.sleep(0.1)
angle = 0
for i in range(20):
motor_pub(angle, speed)
time.sleep(0.1)
8자 주행을 할 코드를 작성한다. angle
, speed
값을 xycar_motor
토픽으로 전달한다.
🐍convertor.py
import rospy, rospkg
import math
from xycar_motor.msg import xycar_motor
from sensor_msgs.msg import JointState
from std_msgs.msg import Header
global pub
global msg_joint_states
global l_wheel, r_wheel
def callback(data):
global msg_joint_states,l_wheel, r_wheel, pub
Angle = data.angle
msg_joint_states.header.stamp = rospy.Time.now()
steering = math.radians(Angle * -0.4)
if l_wheel > 3.14:
l_wheel = -3.14
r_wheel = -3.14
else:
l_wheel += 0.01
r_wheel += 0.01
msg_joint_states.position = [steering, steering, r_wheel, l_wheel,
r_wheel, l_wheel]
pub.publish(msg_joint_states)
def start():
global msg_joint_states,l_wheel, r_wheel, pub
rospy.init_node('converter')
pub = rospy.Publisher('joint_states', JointState, queue_size=10)
msg_joint_states = JointState()
msg_joint_states.header = Header()
msg_joint_states.name = ['front_right_hinge_joint',
'front_left_hinge_joint',
'front_right_wheel_joint',
'front_left_wheel_joint',
'rear_right_wheel_joint',
'rear_left_wheel_joint']
msg_joint_states.velocity = []
msg_joint_states.effort = []
l_wheel = -3.14
r_wheel = -3.14
rospy.Subscriber("xycar_motor", xycar_motor, callback)
rospy.spin()
if __name__ == '__main__':
start()
xycar_motor
의 토픽을 받아서 joint_states
토픽 형태로 가공하여 토픽을 보낸다.
🐍rviz_odom.py
import math
from math import sin, cos, pi
import rospy
import tf
from nav_msgs.msg import Odometry
from geometry_msgs.msg import Point, Pose, Quaternion, Twist, Vector3
from sensor_msgs.msg import JointState
global Angle
def callback(msg):
global Angle
Angle = msg.position[msg.name.index("front_left_hinge_joint")]
rospy.Subscriber('joint_states', JointState, callback)
rospy.init_node('odometry_publisher')
odom_pub = rospy.Publisher("odom", Odometry, queue_size=50)
odom_broadcaster = tf.TransformBroadcaster()
current_time = rospy.Time.now()
last_time = rospy.Time.now()
r = rospy.Rate(30.0)
current_speed = 0.4
wheel_base = 0.2
x_ = 0
y_ = 0
yaw_ = 0
Angle = 0
while not rospy.is_shutdown():
current_time = rospy.Time.now()
# compute odometry in a typical way given the velocities of the robot
dt = (current_time - last_time).to_sec()
current_steering_angle = Angle
current_angular_velocity = current_speed * math.tan(current_steering_angle) / wheel_base
x_dot = current_speed * cos(yaw_)
y_dot = current_speed * sin(yaw_)
x_ += x_dot * dt;
y_ += y_dot * dt;
yaw_ += current_angular_velocity * dt
# since all odometry is 6DOF we'll need a quaternion created from yaw
odom_quat = tf.transformations.quaternion_from_euler(0, 0, yaw_)
# first, we'll publish the transform over tf
odom_broadcaster.sendTransform(
(x_, y_, 0.),
odom_quat,
current_time,
"base_link",
"odom"
)
# next, we'll publish the odometry message over ROS
odom = Odometry()
odom.header.stamp = current_time
odom.header.frame_id = "odom"
# set the position
odom.pose.pose = Pose(Point(x_, y_, 0.), Quaternion(*odom_quat))
# set the velocity
odom.child_frame_id = "base_link"
#odom.twist.twist = Twist(Vector3(vx, vy, 0), Vector3(0, 0, yaw_))
# publish the message
odom_pub.publish(odom)
last_time = current_time
r.sleep()
joint_states
토픽에서 받은 데이터를 가공하여 odom
토픽을 오도메트리 프로그램으로 전달한다. 메세지에는 속도를 기반으로 계산한 x
,y
좌표 및 오일러값을 쿼터니언 값으로 변환한 yaw_
을 담고 있고, Odometry의 헤더를 만든다.
위 사진과 같이 8자 모양을 그리면서 운행되는 모습을 확인 할 수 있다. 만약 주행을 변경하고 싶으면 odom_8_drive.py
에서 값을 일부 수정하면 된다.
rqt_graph
로 노드간 통신을 확인.
RViz를 실행하면 위 화면과 같이 시뮬레이션 차가 이동해도 궤적이 남지 않는다. 궤적을 확인하기 위해서는 RViz의 Odmoetry 설정을 추가해야한다.
RViz의 화면에서 순서대로 버튼을 눌러 Odometry
를 추가해준다.
붉은 화살표가 추가된 모습.
왼쪽의 설정을 통해 궤적의 옵션을 조절 할 수 있다.
이중 Shaft Length
항목을 조절하여 화살표의 크기를 작게 만들어 준다.
적절한 크기로 조절된 모습 . 입맛에 맞춰 조절하면 된다.