IT/Network & OS

ROS - 3. React 웹앱

루벤초이 2021. 5. 27. 23:35

오늘은 ROS 컨트롤러와 영상을 보여주는 React 웹앱을 만들고 CLOi 시뮬레이터에 연결해 보겠습니다.

ROS 컨트롤러 React 웹앱 결과 화면

 

React-Nipple

먼저 조이스틱을 직접 만들기 어려우니, 널리 알려진 nipple.js를 사용합니다. React에서 사용할 수 있는 버전이 npm에서 제공되므로 그걸 사용합니다.

 

react-nipple

A react wrapper for the [nipplejs](https://www.npmjs.com/package/nipplejs) on-screen-joystick.

www.npmjs.com

ReactNipple 설치 후 import해서 move, end 이벤트만 처리하면 되는데요, 조이스틱을 움직이기 시작하면 start(무시해도 됩니다) 이벤트 이후 지속적으로 move 이벤트가 올라오고 조이스틱을 놓으면 도로 원위치되면서 잠시 후 end 이벤트가 올라옵니다. 이 end 이벤트를 받았을 때 로봇도 함께 멈춰주면 되겠죠. move 이벤트에는 data에 angle, distance 값이 올라오는데, 이를 처리해주는 계산은 아래 Reference에 소개한 블로그에서 그대로 차용했습니다.

const handleEvent = (evt, data) => {
  switch (evt.type) {
  case 'end':
    setTwist({ linear: 0, angular: 0 });
    break;
  case 'move':
    const linear_speed = Math.sin(data.angle.radian) * MAX_LINEAR * data.distance / MAX_DISTANCE;
    const angular_speed = -Math.cos(data.angle.radian) * MAX_ANGULAR * data.distance / MAX_DISTANCE;
    setTwist({ linear: linear_speed, angular: angular_speed });
    break;
  default:
  	break;
  }
};

const [twist, setTwist] = React.useState({ linear: 0, angular: 0 });

...중략...

<ReactNipple
  options={{ mode: 'static', position: { top: '50%', left: '50%' } }}
  style={{
    outline: '1px dashed #555555',
    color: 'blue',
    width: '100%',
    height: '100%',
    position: 'relative'
  }}
  onStart={handleEvent}
  onEnd={handleEvent}
  onMove={handleEvent}
/>

 

 

Twist

계산된 linear, angular 값을 twist 값에 반영하면, 어떻게 되는지 살펴 봅시다.

import ROSLIB from 'roslib';

const rosHandler = new ROSLIB.Ros();

const topicCloiTwist = new ROSLIB.Topic({
  ros: rosHandler,
  name: '/cloi1/cmd_vel',
  messageType: 'geometry_msgs/Twist'
});

const getTwistMessage = ({ linear, angular }) => {
  return new ROSLIB.Message({
    linear: {
      x: linear,
      y: 0,
      z: 0
    },
    angular: {
      x: 0,
      y: 0,
      z: angular
    }
  });
}

export default function RosComponent(props) {

...중략...

	React.useEffect(() => topicCloiTwist.publish(getTwistMessage(twist)), [twist]);
    

이제 ROS 부분입니다. npm에서 roslib을 설치하고 ROSLIB.Ros() 객체를 생성합니다. 사용 방법이나 원리는 MQTT와 거의 동일합니다. ROSLIB.Topic()으로 토픽 메시지를 만드는데, 이때 CLOi 시뮬레이터에서 받는 토픽 이름은 /cloi1/cmd_vel을 사용합니다. Twist는 로봇의 위치를 조종할 때 사용하는 메시지 타입인데 보통 토픽 이름으로 /cmd_vel 을 사용합니다. 그 앞에 붙은 /cloi1은 namespace라고 해서 로봇의 이름을 의미합니다. (같은 이름으로 여러 대 로봇을 동시에 컨트롤할 경우도 있겠죠.)

 

메시지 타입 geometry_msgs/Twist는 ROS 표준으로 정의된 메시지 형식인데요, getTwistMessage() 함수에서 보듯 linear 3차원 좌표와 angular 3차원 좌표로 구성되어 있습니다. 이 메시지 타입과 데이터가 일치하지 않으면 메시지는 무시됩니다. 

 

이렇게 ROS 프로토콜에 맞춰 데이터를 구성해서 publish해주면 됩니다. 앞에서 setTwist()를 호출하면 React.useEffect에 의해 .publish() 구절로 들어오겠죠.

 

Key Event

조이스틱 뿐만 아니라 버튼이나 키보드도 추가할 때도 setTwist() 이하는 공통부로 그대로 사용합니다.

  React.useEffect(() => {
    //WARNING : removeEventListener does NOT work!
    const keyEventListener = (event) => {
      switch (event.code) {
        case 'ArrowUp':
        case 'KeyW':
          setTwist({ linear: TWIST_INTENSITY, angular: 0 });
          setKeyPressed("up");
          break;
        case 'ArrowLeft':
        case 'KeyA':
          setTwist({ linear: 0, angular: TWIST_INTENSITY * 2 });
          setKeyPressed("left");
          break;
        case 'ArrowDown':
        case 'KeyS':
          setTwist({ linear: -TWIST_INTENSITY, angular: 0 });
          setKeyPressed("down");
          break;
        case 'ArrowRight':
        case 'KeyD':
          setTwist({ linear: 0, angular: -TWIST_INTENSITY * 2 });
          setKeyPressed("right");
          break;
        case 'KeyQ':
        case 'KeyE':
          setTwist({ linear: 0, angular: 0 });
          setKeyPressed("stop");
          break;
        default:
          break;
      }
    }

    if (enableKey) {
      document.addEventListener('keydown', keyEventListener);
    }
  }, [enableKey]);

키보드 이벤트 리스너를 만들고 document.addEventListener('keydown' 으로 리스너를 등록하면 끝. 키가 눌릴 때마다 setTwist()를 사용하고 있죠. 즉, Twist 입장에서는 조이스틱으로 움직였는지 키보드로 움직였는지 알 필요 없이 그저 받은 대로 움직일 뿐이죠.

 

이제 클로이를 움직일 수 있게 됐습니다. 생각보다 간단하죠?

여기서 끝내기엔 아쉬우니, 클로이 영상도 다뤄봅시다.


ROS2 Web Video

ROS를 통해 보낼 수 있는 정보에는 카메라 영상도 있는데요, CLOi 시뮬레이터에서도 실제 로봇처럼 카메라 영상을 보내줍니다. 물론 시뮬레이터 영상 안의 모습이라 보여지는 것은 3D 영상이지만, 동작이나 프로토콜은 실제 카메라 영상과 같습니다.

먼저 ROS 패키지 중에 rqt(QT로 개발된 ROS GUI 툴 모음)를 이용해서 이미지를 확인해 봅시다.

ros2 run rqt_image_view rqt_image_view

rqt_image_view

실행 후 /cloi/camera/color/image_raw 토픽을 선택하면 실시간 영상이 보여집니다.

이제 이 영상을 우리 웹앱으로 가져와 봅시다. 가장 간단한 방법으로는 오픈 소스 중에 RobotWebTools/web_video_server를 사용하면 되는데요, apt install은 dashing 버전만 지원하기 때문에 터미널을 열고 dashing 환경을 적용합니다.

$source /opt/ros/dashing/setup.bash
$sudo apt install ros-dashing-web-video-server
$ros2 run web_video_server web_video_server

이렇게 명령을 입력하면, 로컬서버 8080 포트에 web_video_server가 뜹니다. 이후 브라우저를 열어 http://localhost:8080/stream?topic=/cloi1/camera/color/image_raw 를 열면, 바로 영상이 보이게 되는데요, 사실 이건 영상이 아니라 이미지 시퀀스(이미지를 연속으로 갱신하는) 방식이라서 HTML <img> 태그로 사용해야 합니다. React 웹앱에서도 마찬가지에요.

<img src='http://localhost:8080/stream?topic=/cloi1/camera/color/image_raw' style={{ width: '100%', border: '1px dashed #555555' }} alt={'ROS'} />

만일 HTML <video> 태그로 보기 위해서는 url 끝에 &type=vp8을 추가해주어 web_video_server에서 이미지 시퀀스를 웹 비디오 영상 포맷으로 전송해주도록 하면 됩니다. 이때 vp8은 웹 비디오 코덱입니다. 그런데 하다 보니까, 저처럼 eloquent랑 dashing을 섞어 사용하는 경우에 패스가 꼬이거나 혹은 코덱 라이브러리 문제로 vp8 지원이 안 되는 경우가 있네요. 


Summary

지금까지 했던 내용을 정리해봅시다.

전체 시스템 구조

다 좋은데 ROS2와 React Web App 사이에 두 줄이 마음에 걸리는군요. web_video_server는 사용이 간단하지만 dashing만 지원되고 이후 유지보수가 잘 안되는 듯 해요. 그렇다고 소스를 가져와 빌드하는 것도 복잡하고 오류가 많습니다. 더욱 중요한 것은 로봇과 웹앱 사이에 이 web_video_server가 항상 동작하고 있어야 한다는 점이죠. 

 

다음편에서는 web_video_server가 없다고 전제했을 때, 로봇에서 보낸 이미지를 웹앱에서 바로 받아 그리는 작업을 해봅시다.

 

References

 

ROS web tutorial part 1 - rosbridge server and roslibjs

The first part of a series of beginner tutorials touching upon using ROS with web broweser, web video servers and cameras.

msadowski.github.io

 

728x90
반응형

'IT > Network & OS' 카테고리의 다른 글

[AWS] RDS MySQL Prisma  (0) 2022.02.06
[AWS] EC2  (0) 2022.02.05
ROS - 2. ROS2 Web Server & 웹앱  (0) 2021.05.17
ROS - 1. ROS2 설치 및 CLOi 시뮬레이터  (0) 2021.05.17
네트워크 - 2. MQTT Client  (0) 2021.04.08