학교에서 음료를 마시고 싶을 때마다 멀리 있는 매점까지 가야 하고, 막상 가도 종류가 많아 뭘 골라야 할지 막막한 경험을 한 적이 있습니다.
심리학 연구(Frontiers in Psychology, 2023)에 따르면 감정 상태는 음료 선택에 직접적인 영향 을 미칩니다. 사람들은 단맛을 행복 같은 긍정적 감정 과, 쓴맛을 슬픔 같은 부정적 감정 과 연결 짓는 경향이 있으며, 긍정적인 감정 상태일 때 음료에 대한 수용도가 유의미하게 높아집니다. 단순히 취향이 아니라 그날의 감정에 맞는 음료를 추천해준다면 어떨까 하는 아이디어에서 출발하였습니다.
Mood Delivery는 카메라로 사용자의 감정을 분석하여 그에 맞는 음료를 추천하고, 자율주행 로봇이 직접 배달하는 무인 카페 서비스입니다.
시스템 아키텍처 + 차량 제어 [설계]
배달 로봇인 만큼 넓은 공간을 안정적으로 주행해야 했습니다. 차체는 알루미늄과 아크릴로 구성하여 실내 배달 환경에서의 내구성을 확보하였고, 구동부는 DC 모터 기반 이륜 구동에 캐스터를 추가하여 방향 전환이 원활하도록 하였습니다.
차량 원격 제어를 위해 MQTT 기반 아키텍처를 설계하였습니다. WebSocket은 클라이언트와 서버 간 지속적인 연결이 필요한 반면, MQTT는 브로커와의 연결만으로 어디로 가라는 명령을 간단하게 전달 할 수 있습니다. 넓은 공간을 대상으로 하는 배달 환경에서 Jetson의 리소스 부담을 줄이면서도 안정적인 원격 제어 가 가능하도록 MQTT를 채택하였습니다.
팀원마다 사용하는 OS가 달랐습니다. Linux 배포판을 쓰는 팀원, macOS를 쓰는 팀원, Windows를 쓰는 팀원이 섞여 있어 개발 환경 충돌이 잦았습니다. 이를 해결하기 위해 서버와 ROS2 컨테이너를 분리 구성하여, 팀원 전원이 OS에 관계없이 동일한 환경에서 개발 할 수 있도록 Docker 환경을 구축하였습니다.
[얻은 점]
OS 환경에 관계없이 팀원 전원이 동일한 개발 환경 사용 가능
MQTT 브로커 기반 구조로 Jetson 리소스 부담 최소화
서버/ROS2 컨테이너 분리로 각 컴포넌트 독립적 업데이트 가능
UART 통신 - 시리얼 포트 점유 [기능]
STM32와 Jetson을 USB to Serial로 연결하여 하드웨어를 제어하였습니다. ROS2에서 STM32를 사용하는 여러 노드가 시리얼 포트를 통해 MCU와 통신하는 구조였습니다.
[문제]
시리얼 포트를 어떤 노드가 점유 중 이면 다른 노드가 해당 포트를 열지 못하고 종료되는 문제 가 발생하였습니다.
[원인]
처음에는 시리얼 통신이 필요한 노드마다 각각 연결 로직을 구현하였습니다. 시리얼 포트는 동시에 하나의 프로세스만 점유할 수 있기 때문에, 먼저 포트를 연 노드가 있으면 다른 노드는 포트를 열지 못하고 죽어버리는 현상 이 나타났습니다.
[해결]
MCU의 기능을 추상화하여 ROS2 토픽으로 제공하는 serial_node를 별도로 구현 하였습니다. serial_node가 시리얼 포트를 단독으로 점유하고, 다른 노드들은 /mcu_rx, /mcu_tx 토픽을 통해서만 MCU와 통신하도록 구조를 변경하였습니다. serial_node 내부에서는 요청 큐를 통해 순차적으로 처리하여 충돌 없이 안정적인 통신 이 가능하도록 하였습니다.
/cmd_node_v2 → /mcu_tx → /serial_node → /mcu_rx → /odom_node → /odom → /tf_node [배운 점]
ROS2의 노드와 토픽 구조에 대해 이해하였습니다.
하드웨어 추상화에 대해 알게 되었습니다.
SLAM 맵 미생성 - odom QoS 불일치 [기능]
slam_toolbox를 활용하여 실내 공간의 맵을 생성하고, 이를 기반으로 자율 주행 Navigation을 구축하였습니다.
[문제]
slam_toolbox를 구동하였으나 맵이 정상적으로 생성되지 않는 문제가 발생하였습니다.
[원인]
ROS2는 QoS를 통해 노드 간 통신 조건을 규약 하며, 조건을 만족하지 못하는 메시지는 버림 처리 됩니다. slam_toolbox는 /odom, /scan 토픽의 QoS 조건을 요구하는데, odom_node의 발행 주기가 MCU에서 엔코더 값이 수신될 때마다 발행하는 방식이었습니다. 이로 인해 MCU 발행 속도, /mcu_rx 토픽 발행 속도 등 여러 요인에 따라 odom 발행 주기가 불규칙 해졌고, slam_toolbox의 QoS 조건을 만족하지 못해 메시지가 버림 처리 되었습니다.
# 수정 전: 수신되는 엔코더 값을 즉시 Odom에 반영
self .x = 0.0
self .y = 0.0
self .theta = 0.0
# 퍼블리셔
self .odom_pub = self .create_publisher(Odometry, '/odom' , 10 )
# 구독: MCU에서 CSV 형식 엔코더 값 수신
self .create_subscription(String, '/mcu_rx' , self .mcu_callback, 10 ) [해결]
slam_toolbox의 코드를 분석한 후 요구사항에 맞춰 /mcu_rx 수신 버퍼를 두고 10Hz로 주기적으로 발행하도록 odom_node를 수정하였습니다. 불규칙한 수신 타이밍에 관계없이 일정한 주기로 odom이 발행되어 QoS 조건을 안정적으로 만족할 수 있게 되었습니다.
# 수정 후: Timer와 Queue로 10Hz 주기 보장
self .odom_pub = self .create_publisher(Odometry, '/odom' , 10 )
self .tf_broadcaster = TransformBroadcaster( self )
# MCU 엔코더 구독 (큐 50)
self .create_subscription(String, '/mcu_rx' , self .mcu_callback, 20 )
# Timer: 10Hz → 0.1초 주기
self .timer = self .create_timer( 0.1 , self .publish_odom)
self .get_logger().info( "OdomNode initialized (10Hz, queue_size=20)" ) [배운 점]
ROS2의 통신 QoS 조건에 대해 알게 되었습니다.
slam_toolbox와 같은 오픈소스의 코드를 분석하는 방법을 알게 되었습니다.