Tutorial/Control UR Robot with Docker and ROS

4. URSim with Docker Compose / Docker with VS Code

페트론 2022. 1. 15. 23:00

Goal of Lecture


  • Docker Compose에 대한 이론 및 실습.
  • VS Code를 사용한 개발환경 구축에 대한 실습 수행.
  • Cartesian Controller를 이용한 URSim 제어 프로그래밍.

01. Docker Compose

Introduction

Docker Compose는 다중 컨테이너 애플리케이션을 정의할 수 있도록 개발된 도구.

서비스를 정의하는 YAML 파일을 만들고, 단일 명령을 사용하여 모두 실행하거나 모두 종료할 수 있음.

Docker Compose의 '중요한' 이점은 YAML 파일에서 애플리케이션 스택을 정의하고 프로젝트 리포지토리 루트에 파일을 저장하여 다른 사용자가 프로젝트에 참여하기 쉽게 만들 수 있다는 것.

사용자는 리포지토리를 복제하고 Docker Compose 앱을 시작하기만 하면 됨.

Install Docker Compose

다음의 명령어를 이용하여 Docker Compose를 설치.

sudo apt install docker-compose

이 후, 다음 명령어를 이용하여 Docker Compose가 제대로 설치되었는지 확인함.

docker-compose --version

Create Docker Compose File

다음 명령어를 통해 폴더를 생성.

mkdir -p ~/eh_lecture/week_04/ros-ursim

이 후, 다음 명령어를 통해 docker-compose.yml 파일을 생성.

gedit ~/eh_lecture/week_04/ros-ursim/docker-compose.yml

docker-compose.yml 파일에 다음의 내용 입력.

# version of docker-compose
version: '2.2'

# external network
networks:
  bridge-network:
    external: true

# external volume
volumes:
    dockursim:
      driver: local

# services (container)
services:
  ros-master:
    image: ur-ros-driver:latest
    container_name: ros-master
    networks: 
      bridge-network: 
        ipv4_address: 192.168.56.2
    environment:
      - "ROS_MASTER_URI=http://192.168.56.2:11311"
      - "ROS_HOSTNAME=192.168.56.2"
    command: stdbuf -o L roscore
    restart: always

  dockursim:
    image: dockursim:latest
    container_name: dockursim
    networks: 
      bridge-network: 
        ipv4_address: 192.168.56.3
    environment: 
      - "ROBOT_MODEL=UR10"
    ports:
      - "8080:8080"
      - "29999:29999"
      - "30001-30004:30001-30004"
    volumes:
      - ~/eh_lecture/DockURSim/ursim/programs:/ursim/programs
      - dockursim:/ursim
    cpus: 1.0
    privileged: true
    restart: always

  ur-ros-driver:
    image: ur-ros-driver:latest
    container_name: ur-ros-driver
    depends_on:
      - ros-master
      - dockursim
    networks: 
      bridge-network: 
        ipv4_address: 192.168.56.4
    environment:
      - "ROS_MASTER_URI=http://192.168.56.2:11311"
      - "ROS_HOSTNAME=192.168.56.4"
      - "DISPLAY=unix$DISPLAY"
    volumes: 
      - /tmp/.X11-unix:/tmp/.X11-unix:rw
    command: >      
      bash -c "source ~/catkin_ws/devel/setup.bash &&
      roslaunch ur_robot_driver ur10_bringup.launch robot_ip:=192.168.56.3"
    cpus: 1.0
    privileged: true
    restart: always

depends_on : 해당 컨테이너가 실행되기 전 먼저 실행되어야할 컨테이너를 명시. restart : always를 줄 경우, 컨테이너가 종료되었을 때, 다시 실행됨. depends_on을 사용하는 경우, 의존하는 컨테이너가 실행된 것만을 인식할 뿐, 실행이 '완료' 되었는지는 인식할 수 없기 때문에 의존하는 컨테이너가 실행이 완료되지 않아 해당 컨테이너가 종료된 경우, 항상 해당 컨테이너를 재시작함.

Run Docker Compose

먼저 기존에 존재하는 컨테이너들을 모두 제거. (포트 혹은 네트워크 등의 설정에 대해 충돌을 방지하기 위함.)

sudo docker rm -f $(sudo docker ps -a -q)

이 후, 다음의 명령어를 통해 docker-compose를 실행함.

cd ~/eh_lecture/week_04/ros-ursim/

sudo docker-compose up

Setting ursim

이 후, 저번주와 같이 ursim을 설정.

dockursim은 HTTP 포트 포워딩을 통해 컨테이너 내부에서 작동하는 ursim을 확인할 수 있도록 구성되어있음.

ursim을 확인하기 위해서는 웹페이지를 오픈하여 아래의 주소를 입력.

<http://localhost:8080>

아래와 같이 ursim이 켜지는 것을 확인할 수 있음.

이 후, Confirm Safety Configuration 버튼을 눌러 창을 닫아줌.

오른쪽 상단의 메뉴버튼을 클릭.

메뉴에서 Settings 클릭.

왼쪽 메뉴에서 [System] → [URCaps] 선택, 하단의 '+' 버튼 클릭.

externalcontrol-1.0.5.urcap 선택 후, 하단의 'Open' 버튼 클릭.

여기서 보이는 창은 컨테이너의 /ursim/programs 폴더의 화면이며, 기존에 로컬의 ~/eh_lecture/ursim/programs 에 있는 externalcontrol-1.0.5.urcap 파일이 동기화되어 보이게 됨.

하단의 Restart 버튼 클릭.

ursim이 재시작된 후, 기존의 화면으로 들어가면, ExternalControl URCap이 활성화되어 있는 것을 확인할 수 있음.

ExternalControl 이 제대로 설치된 것.

[Update] URSim에서는 Remote Control 부분을 Local로 설정. (실제 로봇에서는 Remote Control로 진행해야함.)

이 후, 왼쪽의 [System] → [Remote Control] 메뉴로 들어가 'Enable' 버튼 클릭.

Remote Control : UR Robot을 원격제어하기 위해서 활성화시켜야함.

실제 로봇에서는 [System]의 [Network]도 수정해주어야 하지만, ursim에서는 설정하지 않아도 문제없음.

이 후, 'Exit' 버튼을 클릭하여 Setting 창을 닫은 뒤, 좌측의 [Systems] → [External Control] 메뉴를 선택하고,

Host IP와 Host Name을 192.168.56.4로 바꾸어줌.

192.168.56.4 : ur-ros-driver container의 ip address.

이 후, 좌상단의 'Program' 을 클릭하고,

좌측의 [URCaps] → [External Control] 을 클릭하면,

중앙의 Robot Program에 External Control 프로그램이 추가되는 것을 확인할 수 있음.

이 후, 상단의 [Save] → [Save All] 선택하여,

Filename : ros

입력 후, 저장.

이 후, 우 상단의 'Local' 버튼을 클릭하여 'Remote Control' 모드로 선택.

이제 URSim의 UR 로봇은 구동할 준비가 된 상태.

rqt-joint-trajectory-controller

저번주에 진행한 내용과 같이 rqt-joint-trajectory-controller를 실행.

이제 ROS를 이용하여 UR Robot을 구동할 준비가 완료되었음.

이제 rqt-joint-trajectory-controller를 이용하여 URSim의 UR Robot을 구동.

이를 위해서 먼저 xhost를 실행하여 도커의 x host 접속을 허용함.

# terminal_4
xhost +local:docker

이 후, 다음의 명령어를 통해 ur-ros-driver container에 접속.

# terminal_4
sudo docker exec -it ur-ros-driver bash

이 후, 컨테이너의 bash에 다음의 명령어를 입력하여 RQT를 실행.

# terminal_4
source ~/catkin_ws/devel/setup.bash
rqt

UR Robot을 가시화하기 위하여 URSim에서 좌하단의 Paused 버튼 클릭.

이 후, RQT에서 [Plugins] → [Service Caller] 실행.

이 후, Service 스크롤 메뉴에서 각각 다음의 순서로 Call 수행.

 

/ur_hardware_interface/dashboard/connect

/ur_hardware_interface/dashboard/power_on : UR Robot의 전원을 ON.

/ur_hardware_interface/dashboard/brake_release : UR Robot의 Break를 풀어줌. (토크 발생)

/ur_hardware_interface/dashboard/play : 설정된 프로그램 실행. (External Control 실행.)

! Reponse는 모두 True 값으로 반환되어야 함.

이 후, 아래의 Sevice를 Call 하여 Response 확인.

/controller_manager/list_controllers

이 때, 'scaled_pos_joint_traj_controller'의 상태가 running 인 것을 확인.

scaled_pos_joint_traj_controller : 우리가 rqt-joint-trajectory-controller 에서 사용해야할 컨트롤러.

이 후, [Plugins] → [Robot Tools] → [joint trajectory controller] 를 선택하여, 스크롤 메뉴에서 [/controller_manager], [/scaled_pos_joint_trajectory_controller] 선택.

이 후, 전원버튼을 누르고 각 조인트 값을 변경하면 URSim의 로봇이 제어되는 것을 확인할 수 있음.

해당 부분에서 오류 발생시 시스템 리부팅 후, 재수행

Close Containers

다음 과정을 수행하기 전에 현재 작동하고 있는 컨테이너를 모두 종료 및 제거해 줄 것임.

docker compose를 이용하면 컨테이너들을 한번에 종료할 수 있음.

명령어는 다음과 같음.

cd ~/eh_lecture/week_04/ros-ursim/

sudo docker-compose down

02. Docker with VS Code

다음 단계는 프로그래밍을 이용해 URSim의 제어.

일반적으로 프로그래밍을 위해서 PyCharm, VS Code 등의 통합개발환경(IDE,Integrated Development Environment)를 사용하게 됨.

도커는 특수한 환경성으로 인해 일반적인 개발환경 구축과는 다른 접근이 필요함.

이를 VS Code를 이용하여 진행할 예정.

Install VS Code

Ubuntu OS에서 좌상단의 Activitises를 누른 후, 검색창에 "Ubuntu Software"를 입력.

이 후, 검색된 Ubuntu Software 아이콘을 선택.

Ubuntu Software 창이 켜지면, 우상단의 돋보기 버튼을 누르고 검색창에 "code"를 입력.

이 후, code 아이콘 클릭.

이 후, Install 버튼을 클릭해 설치 진행.

설치가 완료되면, Launch 버튼을 클릭하여 VS Code 실행.

(이제 Activities 메뉴에서 code를 검색하면 VS Code 실행 가능.)

VS Code가 켜지면 Extension 메뉴를 선택.

(혹은 ctrl + shift + x)

검색창에 "Remote - Containers" 입력.

Install 버튼 클릭하여 설치 진행.

검색창에 "Docker" 입력.

Install 버튼 클릭하여 설치 진행.

Add User to Docker Group

Ubuntu에서 Docker는 기본적으로 Root 권한을 이용하여 실행됨.

따라서 권한 설정이 제대로 이뤄지지 않으면, VS Code에서 Docker는 인식될 수 없음.

이를 위해서 사용자를 Docker Group의 User로 추가하여 권한 설정을 진행함.

먼저 다음의 명령어를 이용해 사용자를 Docker Group의 User로 추가.

sudo usermod -a -G docker $USER

이 후, 다음 명령어를 통해 사용자가 추가되었는지 확인.

id

User가 제대로 추가되었다면, 시스템 재부팅을 진행.

Run Docker Compose in VS Code

상단의 메뉴바에서 [File] → [Open Folder] 클릭.

이 후, ~/eh_lecture/week_04/경로로 들어가 우상단의 'OK' 버튼 클릭.

왼쪽 폴더에서 docker-compose.yml 파일을 우클릭해 'Compose Up' 메뉴를 선택하면, 컨테이너가 구동되는 것을 확인할 수 있음.

rqt-joint-trajectory-controller

저번주에 진행한 내용과 같이 rqt-joint-trajectory-controller를 실행.

다만 VS Code의 Terminal을 이용해서 진행.

먼저 VS Code에서 터미널을 켜줌.

  • New Terminal : Ctrl + Shift + '
  • Split Terminal : Ctrl + Shift + 5

이 후, xhost를 실행하여 도커의 x host 접속을 허용함.

xhost +local:docker

이 후, 다음의 명령어를 통해 ur-ros-driver container에 접속.

sudo docker exec -it ur-ros-driver bash

이 후, 컨테이너의 bash에 다음의 명령어를 입력하여 RQT를 실행.

source ~/catkin_ws/devel/setup.bash
rqt

UR Robot을 가시화하기 위하여 URSim에서 좌하단의 Paused 버튼 클릭.

이 후, RQT에서 [Plugins] → [Service Caller] 실행.

이 후, Service 스크롤 메뉴에서 각각 다음의 순서로 Call 수행.

 

/ur_hardware_interface/dashboard/connect

/ur_hardware_interface/dashboard/power_on : UR Robot의 전원을 ON.

/ur_hardware_interface/dashboard/brake_release : UR Robot의 Break를 풀어줌. (토크 발생)

/ur_hardware_interface/dashboard/play : 설정된 프로그램 실행. (External Control 실행.)

! Reponse는 모두 True 값으로 반환되어야 함.

이 후, 아래의 Sevice를 Call 하여 Response 확인.

/controller_manager/list_controllers

이 때, 'scaled_pos_joint_traj_controller'의 상태가 running 인 것을 확인.

scaled_pos_joint_traj_controller : 우리가 rqt-joint-trajectory-controller 에서 사용해야할 컨트롤러.

이 후, [Plugins] → [Robot Tools] → [joint trajectory controller] 를 선택하여, 스크롤 메뉴에서 [/controller_manager], [/scaled_pos_joint_trajectory_controller] 선택.

이 후, 전원버튼을 누르고 각 조인트 값을 변경하면 URSim의 로봇이 제어되는 것을 확인할 수 있음.

해당 부분에서 오류 발생시 시스템 리부팅 후, 재수행

Close Containers

다음 과정을 수행하기 전에 현재 작동하고 있는 컨테이너를 모두 종료 및 제거해 줄 것임.

docker compose를 이용하면 컨테이너들을 한번에 종료할 수 있음.

명령어는 다음과 같음.

cd ~/eh_lecture/week_04/ros-ursim/

sudo docker-compose down

03. [Practice] Programming with VS Code

지금까지는 RQT를 이용하여 URSim을 제어하는 것만을 진행하였음.

다음 단계는 프로그래밍을 통해 URSim을 제어해봄.

System Interface

기존의 System Interface와의 차이점은 ur-ros-control container의 추가.

이 때, 이미지는 ur-ros-driver container와 ur-ros-control container가 공유하게 됨.

실질적으로 프로그래밍을 통해 ur-ros-control container가 제어 및 통신을 하게 되는 컨테이너는 ur-ros-driver container이기 때문에 같은 메시지 형태를 공유하기 위해서 이미지도 함께 공유하는 것이 효율적.

이 후, Dockerfile 생성.

Docker-Compose 생성.

Container로 들어가 프로그래밍.

Create Folder

VS Code에서 new folder 버튼을 클릭해 "ros-ursim-control" 폴더를 생성.

Create Dockerfile

'ros-ursim-control' 폴더를 우클릭하여 'New File' 메뉴를 선택한 후,

Dockerfile을 입력하여 파일을 생성.

저번주에 작성한 Dockerfile을 기반으로 새로운 Dockerfile 작성.

'ctrl + s'로 저장.

# syntax=docker/dockerfile:1
FROM osrf/ros:melodic-desktop-full

# Create catkin workspace
# Install UR Robot Driver
RUN /bin/bash -c "apt-get update &&\\
    source /opt/ros/melodic/setup.bash &&\\
    mkdir -p ~/catkin_ws/src && cd ~/catkin_ws &&\\
    git clone <https://github.com/UniversalRobots/Universal_Robots_ROS_Driver.git> src/Universal_Robots_ROS_Driver &&\\
    git clone -b calibration_devel <https://github.com/fmauch/universal_robot.git> src/fmauch_universal_robot &&\\
    sudo apt update -qq &&\\
    rosdep update &&\\
    rosdep install --from-paths src --ignore-src -y &&\\
    cd ~/catkin_ws &&\\
    catkin_make &&\\
    source devel/setup.bash"

# Install Packages for Installing Python Packages
RUN /bin/bash -c "apt-get update -y &&\\
    apt-get install -y python-dev &&\\
    apt-get install -y build-essential &&\\
    apt-get install -y libssl-dev &&\\
    apt-get install -y libffi-dev &&\\
    apt-get install -y libxml2-dev &&\\
    apt-get install -y libxslt1-dev &&\\
    apt-get install -y zlib1g-dev &&\\
    apt-get install -y python-pip"

# Install Pynput Package for Keyboard Input Control
RUN python -m pip install pynput

Build Dockerfile

생성한 Dockerfile을 Build하여 이미지를 생성.

Build 작업은 연산량이 높기 때문에, 가동중인 컨테이너들은 모두 정지하고 진행하는 것을 권장함.

VS Code에서 Dockerfile을 우클릭하여 "Build Image" 선택.

"ur-ros-driver:lecture-week-04"를 입력한 후 'Enter'.

이 후, 왼쪽 메뉴의 Docker를 클릭하고 Images의 ur-ros-driver를 선택하면, lecture-week-04 tag의 이미지가 생성된 것을 확인할 수 있음.

Bind Mount

앞으로 우리는 'ur-ros-driver:lecture-week-04' 이미지 기반으로 생성된 컨테이너 내부에서 프로그래밍 작업을 수행할 것임.

하지만, 컨테이너는 이미지를 기반으로 생성됨. 그렇기 때문에 만약 컨테이너 내부에서 프로그래밍 후 컨테이너가 삭제된다면 프로그래밍된 내용이 모두 사라져버림.

이런 상황을 방지하기 위하여, 우리는 Local PC에 가상의 Docker Volume을 생성하여 컨테이너와 바인딩 함으로써, 컨테이너 내부에서 수정된 내용을 백업시키게 됨.

이 때, Docker 자체적으로 관리되는 가상 Volume이 아닌 유저가 직접 경로를 지정해 생성하는 가상 Volume을 이용한 마운트가 'Bind Mount'.

Create Docker Compose

'ros-ursim-control' 폴더를 우클릭하여 'New File' 메뉴를 선택한 후,

'docker-compose.yml'을 입력하여 파일을 생성.

기존 docker-compose 파일을 기반으로 새로운 docker-compose.yml 작성.

'ctrl + s'로 저장.

# version of docker-compose
version: '2.2'

# external network
networks:
  bridge-network:
    external: true

# external volume
volumes:
    dockursim:
      driver: local

# services (container)
services:
  ros-master:
    image: ur-ros-driver:latest
    container_name: ros-master
    networks: 
      bridge-network: 
        ipv4_address: 192.168.56.2
    environment:
      - "ROS_MASTER_URI=http://192.168.56.2:11311"
      - "ROS_HOSTNAME=192.168.56.2"
    command: stdbuf -o L roscore
    restart: always

  dockursim:
    image: dockursim:latest
    container_name: dockursim
    networks: 
      bridge-network: 
        ipv4_address: 192.168.56.3
    environment: 
      - "ROBOT_MODEL=UR10"
    ports:
      - "8080:8080"
      - "29999:29999"
      - "30001-30004:30001-30004"
    volumes:
      - ~/eh_lecture/DockURSim/ursim/programs:/ursim/programs
      - dockursim:/ursim
    cpus: 1.0
    privileged: true
    restart: always

  ur-ros-driver:
    image: ur-ros-driver:latest
    container_name: ur-ros-driver
    depends_on:
      - ros-master
      - dockursim
    networks: 
      bridge-network: 
        ipv4_address: 192.168.56.4
    environment:
      - "ROS_MASTER_URI=http://192.168.56.2:11311"
      - "ROS_HOSTNAME=192.168.56.4"
      - "DISPLAY=unix$DISPLAY"
    volumes: 
      - /tmp/.X11-unix:/tmp/.X11-unix:rw
    command: >      
      bash -c "source ~/catkin_ws/devel/setup.bash &&
      roslaunch ur_robot_driver ur10_bringup.launch robot_ip:=192.168.56.3"
    cpus: 1.0
    privileged: true
    restart: always

  ur-ros-control:
    image: ur-ros-driver:lecture-week-04
    container_name: ur-ros-control
    depends_on:
      - ros-master
      - dockursim
      - ur-ros-driver
    networks: 
      bridge-network: 
        ipv4_address: 192.168.56.5
    environment:
      - "ROS_MASTER_URI=http://192.168.56.2:11311"
      - "ROS_HOSTNAME=192.168.56.5"
      - "DISPLAY=unix$DISPLAY" 
    volumes:
      - ./ur-ros-contol:/root/catkin_ws/src/ur_ros_joint_control
      - /tmp/.X11-unix:/tmp/.X11-unix:rw 
    command: tail -F anything
    cpus: 1.0
    privileged: true
    restart: always

여기서 우리는 ur-ros-control 컨테이너가 추가된 것을 확인할 수 있음.

ur-ros-control에서 유의하여 볼 부분은 다음과 같음.

command: tail -F anything

command: tail -F anything

tail 명령어는 anything 이란 파일을 찾을 때 까지 대기한다는 의미.

일반적으로 컨테이너는 수행된 프로세스가 종료되면 꺼지게 됨.

그렇게 되면 컨테이너에서의 작업 또한 불가능하기 때문에, 컨테이너를 종료시키지 않고 유지함으로써, 프로그래밍을 가능토록 함.

volumes:

  - ./ur-ros-control:/root/catkin_ws/
volumes:
      - ./ur-ros-control:/root/catkin_ws/

Bind Mount를 실행하는 명령어로써, 위 명령어를 통해 'docker-compose.yml' 파일이 존재하는 경로에 'ur-ros-control' 디렉토리를 생성함과 동시에 컨테이너에서 작업이 이루어지는 폴더인 '/root/catkin_ws'에 바인딩함.

Run Docker Compose

먼저 기존에 존재하는 컨테이너들을 모두 제거. (포트 혹은 네트워크 등의 설정에 대해 충돌을 방지하기 위함.)

sudo docker rm -f $(sudo docker ps -a -q)

이 후, docker-compose.yml을 우클릭하여 Compose Up을 클릭.

'docker-compose.yml' 파일 경로에 'ur-ros-control' 디렉토리가 자동으로 생성되는 것을 확인할 수 있음.

이 후, 왼쪽 메뉴에서 Docker 버튼을 눌러 확인해보면, 컨테이너들이 실행된 것을 확인할 수 있음.

Attach to VS Code of Container

VS Code를 사용하면 각 컨테이너에 접근하여 VS Code를 이용한 개발이 가능함.

이를 위해서 ur-ros-control container를 우클릭하여 'Attach Visual Studio Code'를 선택.

좌하단에 표시된 바와 같이, container에 연결된 VS Code가 생성됨.

Create Package in Container

상단 메뉴에서 [Terminal] → [New Terminal] 선택.

Shell에서 다음과 같이 입력.

cd ~/catkin_ws/src

catkin_create_pkg ur_ros_joint_control rospy

cd ~/catkin_ws

source /opt/ros/melodic/setup.bash

catkin_make

source devel/setup.bash

Open Folder

상단의 메뉴바에서 [File] → [Open Folder] 클릭.

이 후, '/root/catkin_ws/src/ur_ros_joint_control/' 경로로 들어가 우상단의 'OK' 버튼 클릭.

작업이 이루어질 폴더 경로를 불러올 수 있음.

Create ROS(python) Node

다음으로, Python을 이용하여 ROS Node를 생성.

'src' 폴더를 우클릭한 후, 'New File' 클릭하여 "ur_node.py" 파일 생성.

파일에 다음의 내용 복사.

'ctrl + s'를 눌러 저장.

#!/usr/bin/env python2

import rospy
import threading
import time

from pynput import keyboard

import actionlib
from ur_dashboard_msgs.msg import SetModeAction, \\
                                    SetModeGoal, \\
                                    RobotMode
from ur_dashboard_msgs.srv import GetRobotMode, \\
                                    GetProgramState, \\
                                    GetLoadedProgram, \\
                                    GetSafetyMode, \\
                                    Load
from controller_manager_msgs.srv import SwitchControllerRequest, \\
                                        SwitchController
from std_srvs.srv import Trigger
import std_msgs.msg
from std_msgs.msg import Bool

from control_msgs.msg import FollowJointTrajectoryAction, FollowJointTrajectoryGoal
from control_msgs.msg import JointTrajectoryControllerState
from trajectory_msgs.msg import JointTrajectoryPoint
from sensor_msgs.msg import JointState

# If your robot description is created with a tf_prefix, those would have to be adapted
JOINT_NAMES = [
    "shoulder_pan_joint",
    "shoulder_lift_joint",
    "elbow_joint",
    "wrist_1_joint",
    "wrist_2_joint",
    "wrist_3_joint",
]

ALL_CONTROLLERS = [
        "scaled_pos_joint_traj_controller",
        "pos_joint_traj_controller",
        "scaled_vel_joint_traj_controller",
        "vel_joint_traj_controller",
        "joint_group_vel_controller",
        "forward_joint_traj_controller",
        "forward_cartesian_traj_controller",
        "twist_controller",
        "pose_based_cartesian_traj_controller",
        "joint_based_cartesian_traj_controller",
        ]

class UR():
    def __init__(self):
        print("UR Class is Initializing")

        self.position = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
        self.cnt = 0
        # ROS Service Initialization
        self.s_getRobotMode = rospy.ServiceProxy('/ur_hardware_interface/dashboard/get_robot_mode', GetRobotMode)
        self.s_getProgramState = rospy.ServiceProxy('/ur_hardware_interface/dashboard/program_state', GetProgramState)
        self.s_getLoadedProgram = rospy.ServiceProxy('/ur_hardware_interface/dashboard/get_loaded_program', GetLoadedProgram)
        self.s_getSafetyMode = rospy.ServiceProxy('/ur_hardware_interface/dashboard/get_safety_mode', GetSafetyMode)

        self.s_playProgram = rospy.ServiceProxy('/ur_hardware_interface/dashboard/play', Trigger)
        self.s_stopProgram = rospy.ServiceProxy('/ur_hardware_interface/dashboard/stop', Trigger)
        self.s_connectToDashboardServer = rospy.ServiceProxy('/ur_hardware_interface/dashboard/connect', Trigger)
        self.s_quitFromDashboardServer = rospy.ServiceProxy('/ur_hardware_interface/dashboard/quit', Trigger)
        self.s_loadProgram = rospy.ServiceProxy('/ur_hardware_interface/dashboard/load_program', Load)

        timeout = rospy.Duration(30)

        self.set_mode_client = actionlib.SimpleActionClient(
            '/ur_hardware_interface/set_mode', SetModeAction)
        try:
            self.set_mode_client.wait_for_server(timeout)
            print("set mode action client is on")
        except rospy.exceptions.ROSException as err:
            print(
                "Could not reach set_mode action. Make sure that the driver is actually running."
                " Msg: {}".format(err))

        self.switch_controllers_client = rospy.ServiceProxy('/controller_manager/switch_controller',
                SwitchController)
        try:
            self.switch_controllers_client.wait_for_service(timeout)
            print("controller switch service is on")
        except rospy.exceptions.ROSException as err:
            print(
                "Could not reach controller switch service. Make sure that the driver is actually running."
                " Msg: {}".format(err))

        self.trajectory_client = actionlib.SimpleActionClient(
            '/scaled_pos_joint_traj_controller/follow_joint_trajectory', FollowJointTrajectoryAction)
        try:
            self.trajectory_client.wait_for_server(timeout)
            print("follow trajectory action client is on")
        except rospy.exceptions.ROSException as err:
            print(
                "Could not reach controller action. Make sure that the driver is actually running."
                " Msg: {}".format(err))

        self.script_publisher = rospy.Publisher("/ur_hardware_interface/script_command", std_msgs.msg.String, queue_size=1)

        # Running up the Manipulator
        resp = self.s_connectToDashboardServer()

        self.set_robot_to_mode(RobotMode.POWER_OFF)
        rospy.sleep(0.5)
        self.set_robot_to_mode(RobotMode.RUNNING)
        rospy.sleep(10)

        self.s_loadProgram("/programs/ros.urp")
        rospy.wait_for_service('/ur_hardware_interface/dashboard/play')
        resp = self.s_playProgram()
        rospy.sleep(0.5)

        self.switch_on_controller("scaled_pos_joint_traj_controller")
        rospy.sleep(0.5)

        rospy.Subscriber('/scaled_pos_joint_traj_controller/state',JointTrajectoryControllerState, self.callback)

        with keyboard.Listener(
            on_press=self.on_press,
            on_release=self.on_release) as listener:
            listener.join()

    def move(self, key):
        if key == keyboard.KeyCode(char='q'):
            #print("joint_1 + ")
            self.position[0] = self.position[0]+0.1
            self.joint_traj(self.position[0],
                            self.position[1],
                            self.position[2],
                            self.position[3],
                            self.position[4],
                            self.position[5])
            
        elif key == keyboard.KeyCode(char='a'):
            #print("joint_1 - ")
            self.position[0] = self.position[0]-0.1
            self.joint_traj(self.position[0],
                            self.position[1],
                            self.position[2],
                            self.position[3],
                            self.position[4],
                            self.position[5])

        elif key == keyboard.KeyCode(char='w'):
            #print("joint_2 + ")
            self.position[1] = self.position[1]+0.1
            self.joint_traj(self.position[0],
                            self.position[1],
                            self.position[2],
                            self.position[3],
                            self.position[4],
                            self.position[5])

        elif key == keyboard.KeyCode(char='s'):
            #print("joint_2 - ")
            self.position[1] = self.position[1]-0.1
            self.joint_traj(self.position[0],
                            self.position[1],
                            self.position[2],
                            self.position[3],
                            self.position[4],
                            self.position[5])

        elif key == keyboard.KeyCode(char='e'):
            #print("joint_3 + ")
            self.position[2] = self.position[2]+0.1
            self.joint_traj(self.position[0],
                            self.position[1],
                            self.position[2],
                            self.position[3],
                            self.position[4],
                            self.position[5])

        elif key == keyboard.KeyCode(char='d'):
            #print("joint_3 - ")    
            self.position[2] = self.position[2]-0.1
            self.joint_traj(self.position[0],
                            self.position[1],
                            self.position[2],
                            self.position[3],
                            self.position[4],
                            self.position[5])

        elif key == keyboard.KeyCode(char='r'):
            #print("joint_4 + ")
            self.position[3] = self.position[3]+0.1
            self.joint_traj(self.position[0],
                            self.position[1],
                            self.position[2],
                            self.position[3],
                            self.position[4],
                            self.position[5])

        elif key == keyboard.KeyCode(char='f'):
            #print("joint_4 - ") 
            self.position[3] = self.position[3]+0.1
            self.joint_traj(self.position[0],
                            self.position[1],
                            self.position[2],
                            self.position[3],
                            self.position[4],
                            self.position[5])

        elif key == keyboard.KeyCode(char='t'):
            #print("joint_5 + ")
            self.position[4] = self.position[4]+0.1
            self.joint_traj(self.position[0],
                            self.position[1],
                            self.position[2],
                            self.position[3],
                            self.position[4],
                            self.position[5])

        elif key == keyboard.KeyCode(char='g'):
            #print("joint_5 - ")
            self.position[4] = self.position[4]-0.1
            self.joint_traj(self.position[0],
                            self.position[1],
                            self.position[2],
                            self.position[3],
                            self.position[4],
                            self.position[5])

        elif key == keyboard.KeyCode(char='y'):
            #print("joint_6 + ")
            self.position[5] = self.position[5]+0.1
            self.joint_traj(self.position[0],
                            self.position[1],
                            self.position[2],
                            self.position[3],
                            self.position[4],
                            self.position[5])

        elif key == keyboard.KeyCode(char='h'):
            #print("joint_6 - ")     
            self.position[5] = self.position[5]-0.1
            self.joint_traj(self.position[0],
                            self.position[1],
                            self.position[2],
                            self.position[3],
                            self.position[4],
                            self.position[5])  

    def on_press(self, key):
        #print('Key %s pressed' % key)
        threading.Thread(target=self.move, args=(key,)).start()  # <- note extra ','
        
         

    def on_release(self, key):
        #print('Key %s released' %key)
        if key == keyboard.Key.esc:
            self.finalize()
            return False

    def set_robot_to_mode(self, target_mode):
        goal = SetModeGoal()
        goal.target_robot_mode = target_mode
        goal.play_program = True # we use headless mode during tests
        # This might be a bug to hunt down. We have to reset the program before calling `resend_robot_program`
        goal.stop_program = False

        self.set_mode_client.send_goal(goal)
        self.set_mode_client.wait_for_result()
        return self.set_mode_client.get_result().success

    def switch_on_controller(self, controller_name):
        """Switches on the given controller stopping all other known controllers with best_effort
        strategy."""
        srv = SwitchControllerRequest()
        srv.stop_controllers = ALL_CONTROLLERS
        srv.start_controllers = [controller_name]
        srv.strictness = SwitchControllerRequest.BEST_EFFORT
        result = self.switch_controllers_client(srv)
        print(result)

    def joint_traj(self, _1, _2, _3, _4, _5, _6):
        # Create and fill trajectory goal
        goal = FollowJointTrajectoryGoal()
        goal.trajectory.joint_names = JOINT_NAMES
        point = JointTrajectoryPoint()
        position = [_1, _2, _3, _4, _5, _6]
        point.positions = position
        point.time_from_start = rospy.Duration(1)
        goal.trajectory.points.append(point)

        self.trajectory_client.send_goal(goal)
        self.trajectory_client.wait_for_result()

        result = self.trajectory_client.get_result()
        #rospy.loginfo("Trajectory execution finished in state {}".format(result.error_code))

    def callback(self, data):
        self.position = list(data.actual.positions)

    def finalize(self):
        self.set_robot_to_mode(RobotMode.POWER_OFF)
        rospy.sleep(0.5)

if __name__ == '__main__':
    rospy.init_node('ur_ros_joint_control_node') 

    try:
        ur = UR()
    except rospy.ROSInterruptException: pass

아래 명령어를 통해 Python File에 실행 권한 부여.

chmod +x /root/catkin_ws/src/ur_ros_joint_control/src/ur_node.py

Edit CMakeLists.txt

왼쪽메뉴에서 'CMakeLists.txt' 파일을 선택하여 다음의 내용 입력.

'ctrl + s'를 눌러 저장.

cmake_minimum_required(VERSION 3.0.2)
project(ur_ros_joint_control)

find_package(catkin REQUIRED COMPONENTS
  rospy
)

catkin_package(
)

include_directories(
  ${catkin_INCLUDE_DIRS}
)

catkin_install_python(PROGRAMS src/ur_node.py
                      DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})

Build Package

다음의 명령어를 이용하여 패키지 빌드.

cd ~/catkin_ws/

source /opt/ros/melodic/setup.bash

catkin_make

source devel/setup.bash

Run ROS UR Node

다음 명령어를 통해 ROS 프로그램을 구동하면 Keyboard를 이용하여 UR 로봇을 제어하는 프로그램이 실행됨.

이 때, dockursim에서 ros.urp로 세팅이 되어있어야 함.

만일 작동하지 않을 시, 재통신을 위해 4개의 컨테이너를 재시작.

rosrun ur_ros_joint_control ur_node.py

이 후, 다음의 입력을 통해 UR 로봇 제어가 가능.

  • q : shoulder_pan_joint +
  • a : shoulder_pan_joint -
  • w : shoulder_lift_joint +
  • s : shoulder_lift_joint -
  • e : elbow_joint +
  • d : elbow_joint -
  • r : wrist_1_joint +
  • f : wrist_1_joint -
  • t : wrist_2_joint +
  • g : wrist_2_joint -
  • y : wrist_3_joint +
  • h : wrist_3_joint -
  • esc : close program

Create Image with Custom Package

지금까지 컨테이너 내부에서 프로그래밍을 통해 Custom Package(ur_ros_joint_control)를 생성함.

해당 패키지가 포함된 이미지를 생성하기 위해서는 다음의 과정을 수행. (해당 방법뿐만 아니라 다양한 방법들이 있지만, GUI를 이용한 접근성이 높은 방법부터 수행.)

Create Repository

(Github 회원가입 관련해서는 해당 강의에서 다루지 않음.)

먼저 Github 페이지에서 Repository를 생성해야함.

이를 위해 우상단의 본인 프로필을 선택한 후, 'Your repositories'를 클릭.

이 후, 우상단의 'New' 버튼을 클릭.

Repository name에 'ur_ros_joint_control' 입력.

'Public' 선택.

'Create repository' 클릭.

중간의 'uploading an existing file' 클릭.

'~/eh_lecture/week_04/ros_ursim_control/ur_ros_control' 경로에 존재하는 다음의 파일을 드래그하여 Github에 첨부.

이 후, 'Commit changes' 버튼을 클릭.

이 후, 'Code'를 누르면, 본인 Repository의 HTTPS를 확인할 수 있음.

Edit Dockerfile

이제 Dockerfile을 다음과 같이 수정.

추가된 Code는 다음과 같음.

# Install Custom Package(ur_ros_joint_control)
RUN /bin/bash -c "cd ~/catkin_ws &&\\
    git clone <https://github.com/suho0515/ur_ros_joint_control.git> src/ur_ros_joint_control &&\\
    source /opt/ros/melodic/setup.bash &&\\
    catkin_make &&\\
    source devel/setup.bash"

위 코드를 이용하여 Github에 업로드한 'ur_ros_joint_control' 패키지를 다운로드하고 빌드를 수행함.

Build Dockerfile

이제 Dockerfile을 우클릭하여 'Build Image' 선택.

이미지명은 "ur-ros-driver:joint-control"로 지정.

이 후, 왼쪽의 Docker 메뉴에서 'ur-ros-driver:joint-control' 이미지가 생성된 것을 확인할 수 있음.

해당 이미지를 우클릭하여 'Run Interactive'를 선택하면 Containers 메뉴에 생성된 'ur-ros-driver:ur_ros_joint_control' 컨테이너처럼, 컨테이너가 종료되지않고 동작하도록 할 수 있음.

'ur-ros-driver:ur_ros_joint_control' 컨테이너를 우클릭하여 "Attach Visual Studio Code"를 클릭.

이 후, 'Open Folder'를 선택하여 '/root/catkin_ws/src' 폴더를 오픈하면 Local의 Volume과 바인딩 되지 않고도 우리가 필요한 코드들이 존재하는 것을 확인할 수 있음.

반응형

'Tutorial > Control UR Robot with Docker and ROS' 카테고리의 다른 글

3. URSim with Dockerfile  (0) 2022.01.15
2. ROS with Docker  (1) 2022.01.15
1. Theory of Linux / ROS / Docker  (0) 2022.01.15