본문 바로가기

ROS2

Turtlebot3 자율주행 서빙로봇 프로젝트 [2]

1. 코드 분석

(1) 테이블 좌표 및 네비게이션 코드

위 코드에서 get_table_coordinates()는 테이블 번호와 해당 테이블의 (x, y) 좌표를 매핑합니다. 예를 들어, 테이블 1의 좌표는 (1.1, 0.8)로 설정되어 있습니다.

 

 

(2) 주요 노드 설명

각 노드별 주요 기능을 간단히 정리합니다:

  • kitchen_display:
    • 테이블에서 받은 주문을 처리하며, 로봇의 네비게이션 명령을 실행합니다.
    • navigate_to_pose_send_goal 함수를 통해 TurtleBot3가 지정된 좌표로 이동합니다.
    • 로봇 움직임 알고리즘은 A* 를 기반으로 작성하였습니다.
  • order_database_server:
    • 주문 정보를 저장 및 관리하며, 데이터베이스와의 연동을 처리합니다.
  • system_logging:
    • 각 노드에서 발생하는 이벤트 및 시스템 로그 데이터를 저장합니다.
  • table_order_manager:
    • 사용자가 테이블 번호를 선택하고 주문을 입력할 수 있는 인터페이스를 제공합니다.

(3) Docker를 활용한 환경 설정

이 프로젝트는 Docker를 통해 모든 의존성과 환경을 컨테이너화하여 플랫폼 독립적으로 실행할 수 있도록 설계되었습니다. 주요 Docker 설정은 아래와 같습니다.

 

FROM ros:humble

# 환경 변수 설정
ENV DEBIAN_FRONTEND=noninteractive
ENV TURTLEBOT3_MODEL=waffle_pi
ENV ROS_LOCALHOST_ONLY=0

# 필요한 시스템 패키지 및 의존성 설치
RUN apt-get update && apt-get install -y \
    ros-humble-gazebo-ros-pkgs \
    ros-humble-gazebo-plugins \
    ros-humble-nav2-map-server \
    ros-humble-nav2-bringup \
    ros-humble-rviz2 \
    ros-humble-tf2-tools \
    ros-humble-dynamixel-sdk \
    ros-humble-turtlebot3-gazebo \
    ros-humble-turtlebot3-navigation2 \
    ros-humble-rclpy \
    python3-colcon-common-extensions \
    python3-pip \
    git \
    wget \
    nano \
    libpulse-mainloop-glib0 \
    pulseaudio \
    libasound2-dev \
    libgl1-mesa-glx \
    libgl1-mesa-dri \
    x11-apps \
    libosmesa6-dev \
    mesa-utils \
    imagemagick \
    fonts-nanum \
    && rm -rf /var/lib/apt/lists/*

# Python 의존성 설치
RUN pip3 install --no-cache-dir \
    PyQt5 \
    mysql-connector-python \
    && rm -rf /root/.cache/pip

# 작업 디렉토리 설정 
WORKDIR /project_ws

# 소스 코드 및 스크립트 복사 
COPY src src
COPY launch_all.sh launch_all.sh

# 실행 스크립트에 실행 권한 부여
RUN chmod +x launch_all.sh

# ROS 종속성 설치
RUN rosdep update && \
    rosdep install --from-paths src --ignore-src -r -y || true

# 워크스페이스 빌드
RUN /bin/bash -c "source /opt/ros/humble/setup.bash && colcon build --symlink-install"

# 환경 설정을 위한 설정 파일 추가 
RUN echo "source /project_ws/install/local_setup.bash" >> ~/.bashrc
RUN echo "source /usr/share/gazebo/setup.sh" >> ~/.bashrc
RUN echo "export TURTLEBOT3_MODEL=waffle_pi" >> ~/.bashrc
RUN echo "export ROS_LOCALHOST_ONLY=0" >> ~/.bashrc

# PulseAudio 설정
ENV PULSE_SERVER=unix:/run/pulse/native
RUN mkdir -p /run/pulse && \
    chmod 777 /run/pulse

# 실행 스크립트를 엔트리포인트로 설정 
ENTRYPOINT ["./launch_all.sh"]

 

해당 dockerfile로 모든 의존성을 설치 후 docker hub에 push 하였습니다. 

 

docker pull seonghoham/foodies:3.5 해당 명령어로 docker file을 받을 수 있습니다.

 

실행 코드

import os
import subprocess
import sys

def run_command(command, cwd=None):
    """
    주어진 명령어를 실행하고, 출력과 오류를 실시간으로 표시합니다.
    """
    try:
        print(f"실행 중: {' '.join(command)}")
        result = subprocess.run(command, cwd=cwd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        if result.stdout:
            print(result.stdout)
        if result.stderr:
            print(result.stderr, file=sys.stderr)
    except subprocess.CalledProcessError as e:
        print(f"명령어 실행 중 오류 발생: {' '.join(command)}", file=sys.stderr)
        if e.stdout:
            print(e.stdout)
        if e.stderr:
            print(e.stderr, file=sys.stderr)
        sys.exit(e.returncode)

def container_exists(container_name):
    """
    주어진 이름의 컨테이너가 존재하는지 확인합니다.
    """
    try:
        result = subprocess.run(['docker', 'ps', '-a', '-q', '-f', f'name={container_name}'], check=True, stdout=subprocess.PIPE, text=True)
        return bool(result.stdout.strip())
    except subprocess.CalledProcessError as e:
        print(f"컨테이너 확인 중 오류 발생: {container_name}", file=sys.stderr)
        sys.exit(e.returncode)

def main():
    # 현재 스크립트의 절대 경로를 기준으로 설정
    current_dir = os.path.abspath(os.getcwd())
    workdir = os.path.join(current_dir, 'project_ws')

    # 작업 디렉토리 확인 및 생성
    if not os.path.exists(workdir):
        try:
            os.makedirs(workdir)
            print(f"디렉토리 생성: {workdir}")
        except Exception as e:
            print(f"디렉토리 생성 실패: {workdir}", file=sys.stderr)
            print(e, file=sys.stderr)
            sys.exit(1)
    else:
        print(f"디렉토리 존재: {workdir}")

    # 작업 디렉토리로 이동
    try:
        os.chdir(workdir)
        print(f"작업 디렉토리로 이동: {workdir}")
    except Exception as e:
        print(f"디렉토리 이동 실패: {workdir}", file=sys.stderr)
        print(e, file=sys.stderr)
        sys.exit(1)

    # 호스트의 현재 디렉토리 기준 상대 경로 사용
    map_yaml_host = os.path.join(workdir, 'map.yaml')
    map_pgm_host = os.path.join(workdir, 'map.pgm')

    # foodies_ros2 컨테이너가 존재하는지 확인
    commands = [
        ['docker', 'pull', 'seonghoham/foodies:3.5']  # 이미지 다운로드
    ]

    if container_exists('foodies_ros2'):
        # 컨테이너가 존재하면 중지 및 삭제 명령 추가
        commands.extend([
            ['docker', 'stop', 'foodies_ros2'],
            ['docker', 'rm', 'foodies_ros2']
        ])
    else:
        print("기존에 실행된 'foodies_ros2' 컨테이너가 없습니다. 중지 및 삭제 단계 생략.")

    # Docker 실행 명령어 추가
    commands.append([
        'docker', 'run', '-it',
        '--name', 'foodies_ros2',
        '--env', 'DISPLAY',
        '--env', 'QT_X11_NO_MITSHM=1',
        '--env', 'PULSE_SERVER=unix:/run/pulse/native',
        '--volume', '/tmp/.X11-unix:/tmp/.X11-unix:rw',
        '--volume', f"{map_yaml_host}:/project_ws/map.yaml:ro",
        '--volume', f"{map_pgm_host}:/project_ws/map.pgm:ro",
        '--volume', f"{os.getenv('XDG_RUNTIME_DIR')}/pulse/native:/run/pulse/native" if os.getenv('XDG_RUNTIME_DIR') else '/run/pulse/native:/run/pulse/native',
        '--volume', '/etc/machine-id:/etc/machine-id',
        '--network', 'host',
        'seonghoham/foodies:3.5'
    ])

    # 각 명령어 순차적으로 실행
    for cmd in commands:
        run_command(cmd, cwd=workdir)

    print("\n모든 명령어가 성공적으로 실행되었습니다.")

if __name__ == "__main__":
    main()

 

해당 파이썬 파일로 docker hub 에서 pull 하고 ROS2 humble

 

프로젝트 파일을 실행 할 수 있습니다. 

 

짧은 시간이였지만 완성도 있는 산출물을 위해 노력하였습니다.