지난 1주간 TIL을 작성할 수 없었다. 하반기 취업 마감일들이 지난 주에 몰려 있어서 시간이 너무 없었다.
이번 주에는 ROS의 노드를 활용한 내용들로 구성되어 있는데, 이 내용을 TIL로 쪼개는 것보다는 하나로 정리하는 것이 나중에 돌아보기 좋을것 같아 오늘 내용을 내용을 하나로 정리했다.
ROS에서 노드는 네트워크를 활용해 통신하는데, 네트워크 통신은 생각보다 많은 제약이 발생한다. 따라서 다음과 같은 문제가 발생했을 때, ROS는 어떻게 동작하는지 알아볼 필요가 있다.
발송한 데이터가 누락되지 않게 모두 도착하는지 확인하기 위한 노드를 만들어 실험을 진행했다.
1부터 100까지 전송하는 노드인 sendor_serial.py와 메시지를 화면에 출력하는 receiver_serial.py를 만들고, 노드가 정상적으로 모두 도착하는지 확인했다.
지금까지 해본 과정들을 통해 예상해보면, 노드를 실행하는 주체가 사람이기에 실행 시간에 따라 일부 데이터를 수신할 수 없을 것이다.

이처럼, receiver_serial.py를 실행한 시점부터 수신한 데이터를 출력할 수 있다. 이는 보내는 노드에서 수신자가 있는지를 확인하는 과정이 없기 때문이다. 즉, 코드에서 수신자를 체크하면 이 문제를 해결할 수 있다.
# get_num_connections() // Subscriber의 수를 확인해줌
...
while pub.get_num_connections() != 0:
count += 1
...
ROS에서는 다양한 정보들을 Topic으로 생성해서 발행한다. 간단한 int 타입의 텍스트부터 무려 영상 정보까지 발행할 수 있다. 네트워크를 통해 데이터를 전달하므로, 작은 데이터를 발행하는 것과 큰 데이터를 발행할 때 전송 속도에는 차이가 분명히 발생할 것이다.
데이터를 1Mbyte부터 50Mbyte까지 발행해 각 데이터의 전송 속도를 확인하는 과정을 수행했다. 문자 하나는 1byte이므로, 각 MB만큼 문자열을 채워서 발행하는 형식으로 Publisher의 코드를 작성했다.
...
hello_str = String()
rate = rospy.Rate(1)
pub_size = 1000000 # 1MByte
my_string = ""
for i in range(pub_size):
my_string += "#"
while not rospy.is_shutdown():
hello_str.data = my_string + ":" + str(rospy.get_time())
pub.publish(hello_str)
rate.sleep()
...
Subscriber는 토픽이 도착한 시간과, 현재 시간과의 차를 통해 토픽의 전송 속도를 측정하도록 구성했다.
...
def callback(data):
current_time = str(rospy.get_time())
arrival_data = str(data.data).split(":")
time_diff = float(current_time) - float(arrival_data[1])
string_size = len(arrival_data[0])
rospy.loginfo(str(string_size) + " byte: " + str(time_diff) + " second")
rospy.loginfo("speed: " + str(float(string_size) / time_diff) + "byte/s")
...
1Mbyte의 데이터를 전송할 때에는 속도를 측정할 수 없을만큼 빠르기에 division 0 error가 발생한다. 반면, 50Mbyte의 데이터를 수신할 때에는 일부에는 diviison 0 error가 발생하지만, 아래와 같이 속도가 측정되는 것을 볼 수 있다.
![]#?(https://velog.velcdn.com/images/addps5012/post/99bdbe69-9d7d-4091-8143-e47411c5dcd3/image.png)
토픽의 전송속도는 500Mbps에 가까운 전송 속도를 보인다. 하지만, 통신 상태에 따라 이는 감소할 수도 있다. 대부분은 100Mbps 이상의 속도를 보여주기에 큰 데이터를 전송하거나 빠르게 전송하는 데 무리가 없는 속도이다.
ROS는 기본적으로 주기적으로 통신하는 것을 전제로 하고 있다. Publisher가 N초에 1번씩 데이터를 보내면, Subscriber가 N초에 1번씩 데이터를 읽을 수 있다. 즉, N초 사이에 데이터를 처리하지 못하면, 그 다음 데이터를 처리하는 데 문제가 발생할 수 있다.

queue_size를 1로 했을 때, 일부 토픽을 잃어버리는 것을 볼 수 있다. 이는 queue_size를 충분히 부여해주면 해결되는 문제이다. 하지만, queue_size는 그만큼의 데이터를 보관한다는 뜻이며, 오히려 이 때문에 과도한 메모리를 소모하거나 실시간성을 잃어버리는 문제가 발생할 수 있기 때문에 적절한 queue_size를 지정하는 것이 중요하다.
위의 질문과 연계되는 질문으로, Publisher에서 N초안에 다음에 보낼 메시지를 못만든다면 일부 데이터가 전송되지 않을 수 있다. Publisher에서는 Rate를 지정해서 1초에 몇 번을 보낼지 결정하게 된다. ROS에서 타임슬롯은 다음과 같이 구성된다.

rate를 5로 했을 때 하나의 타임슬롯은 0.2초가 된다. count만큼 for문을 실행했을 때, 1000번의 경우 1초를, 85,000번의 경우 1.35초의 시간이 소모되고, 일부 슬롯의 경우 작업을 하지 않고 넘어가는 것을 볼 수 있다.

ROS에서 노드들은 1:1, 1:N, N:M 형태로 구성할 수 있다. 하나의 노드에서 여러개의 토픽을 사용하는 경우, 토픽 간의 순서가 중요시 되는 경우가 발생할 수 있다.
이 경우에는 여러 방법으로 해결할 수 있다. 그 중 하나는 보낼 토픽을 결정할 수 있도록 토픽을 사용하는 노드에서 사용할 토픽의 정보를 발행하는 방법으로 구성할 수 있다.
...
rospy.init_node(name)
rospy.Subscriber(sub_topic, String, callback)
pub = rospy.Publisher(pub_topic, String, queue_size=1)
...
이런식으로 Subscriber와 Publisher를 모두 구성하고, 사용할 Topic 정보를 발행한 뒤, Publisher에서는 본인의 토픽을 요구하면 전송해주는 방식으로 1:N 통신을 구성할 수 있다.
ROS에 대해서는 학부 때에도 공부를 했기에 어렵지는 않았다.
아직은 궁금한 점은 없었다.
ROS의 노드 통신부분을 공부했다. 학부 때에도 공부했지만, 이렇게 자세히 공부하지는 않았다. 통신에 대한 내용을 여러 날짜에 나누어 공부했기에 TIL을 작성하기에는 약간 애매한 내용이었다. 한 번에 몰아서 내용으로 정리했지만, TIL을 약간 소홀히했다고 생각한다. 조금 더 열심히 해야겠다.
📌 프로그래머스 데브코스 6기 자율주행 인지과정(Perception) 수강 내용을 바탕으로 정리한 TIL 입니다.
📅 Today: 2023.10. 7.