Skip to content

웹으로 MiniPi 원격 제어하기

카메라 영상 스트리밍, 배터리 잔량 표시, 실시간 로봇 제어가 모두 가능합니다.

NiceGUI를 활용한 ROS 배터리 전압 모니터링

1. 학습 목표

  • ROS Subscriber의 기본 구조와 콜백(Callback) 함수의 동작 원리를 이해한다.
  • NiceGUI 프레임워크를 사용하여 간단한 웹 인터페이스를 구성한다.
  • 데이터 바인딩(Data Binding) 개념을 익혀, 백엔드(ROS) 데이터 변화를 프론트엔드(UI)에 실시간으로 반영하는 방법을 배웁니다.

2. 전체 코드 확인

먼저 작성된 전체 코드를 살펴봅시다.

python
import rospy
from std_msgs.msg import Float32
from nicegui import ui

# 1. 데이터 저장소 클래스 정의
class Data:
    current_voltage = 0.0  # 초기값 설정 (숫자형 권장)

# 2. 인스턴스 생성
data = Data()

# 3. ROS 콜백 함수 정의
def battery_callback(msg):
    # 터미널 출력용 (디버깅)
    print(f'Battery voltage = {msg.data}V')
    # GUI에 바인딩된 변수 업데이트
    data.current_voltage = msg.data

# 4. ROS 노드 초기화
rospy.init_node('web_gui_control_node', anonymous=True, disable_signals=True)
sub_bat = rospy.Subscriber('/battery_voltage', Float32, battery_callback)

# 5. GUI 구성 (데이터 바인딩)
# 라벨을 생성하고 data 객체의 'current_voltage' 속성과 연결합니다.
ui.label('Battery Status: ').classes('text-xl')
ui.label().bind_text(data, 'current_voltage').classes('text-2xl text-blue-500')

# 6. GUI 실행
ui.run(title='ROS Battery Monitor')
로봇 on

3. 상세 코드 분석

Step 1: 상태 관리 클래스 (Data Class)

python
class Data:
    current_voltage = 0.0
data = Data()
  • 역할: ROS에서 받아온 데이터를 저장하는 '그릇' 역할을 합니다.
  • 해설: 함수 간에 변수를 공유하기 위해 클래스나 전역 변수를 사용합니다. 여기서는 Data 클래스의 인스턴스인 data를 만들어, ROS 콜백 함수와 GUI 시스템이 이 객체를 함께 바라보게 합니다.

Step 2: ROS Subscriber 설정

python
def battery_callback(msg):
    print(f'Battery voltage = {data.current_voltage}V')
    data.current_voltage = msg.data

rospy.init_node('web_gui_control_node', anonymous=True, disable_signals=True)
sub_bat = rospy.Subscriber('/battery_voltage', Float32, battery_callback)
  • rospy.init_node(..., disable_signals=True):
    • 핵심: disable_signals=True 옵션이 매우 중요합니다. ROS와 NiceGUI 모두 Ctrl+C(종료 신호)를 처리하려고 하기 때문에 충돌이 발생할 수 있습니다. ROS의 신호 처리를 비활성화하고 GUI(NiceGUI)에게 종료 권한을 넘겨주는 설정입니다.
  • Subscriber: /battery_voltage라는 토픽으로 들어오는 Float32 메시지를 구독합니다. 데이터가 들어올 때마다 battery_callback 함수가 실행됩니다.

Step 3: GUI 데이터 바인딩 (Data Binding)

python
ui.label().bind_text(data, 'current_voltage')
  • ui.label(): 웹 화면에 글자를 띄우는 요소입니다.
  • .bind_text(data, 'current_voltage'): (이 실습의 핵심)
    • 단순히 텍스트를 한 번 출력하는 것이 아니라, data 객체의 current_voltage 변수와 화면을 연결(Bind)합니다.
    • data.current_voltage의 값이 바뀌면(ROS 콜백에 의해), 웹 화면의 숫자도 자동으로 바뀝니다. 개발자가 직접 "화면을 갱신해라"라고 코드를 짤 필요가 없습니다.

4. 프로그램 동작 흐름 (Flow)

  1. 시작: 프로그램이 실행되면 웹 브라우저가 열리고 초기값이 표시됩니다.
  2. 대기: ui.run()이 실행되며 웹 서버가 동작하고, 백그라운드에서 ROS는 메시지를 기다립니다.
  3. 수신: 외부(로봇)에서 /battery_voltage 토픽으로 전압 데이터(예: 12.5)를 보냅니다.
  4. 갱신:
    • ROS battery_callback 함수가 호출됩니다.
    • data.current_voltage 변수의 값이 12.5로 변경됩니다.
  5. 반영: NiceGUI는 바인딩된 변수의 변화를 감지하고(혹은 갱신 주기에 맞춰) 웹 브라우저의 숫자를 12.5로 바꿉니다.

5. 심화 학습 및 주의사항

1) Threading (스레드) 이슈

ROS의 Subscriber는 별도의 스레드(Thread)에서 동작하고, NiceGUI는 메인 스레드의 이벤트 루프에서 동작합니다.

  • 이 코드에서는 단순히 변수 값을 바꾸는 것이라 큰 문제가 없지만, 복잡한 UI 업데이트를 할 때는 동기화 문제가 발생할 수 있습니다.
  • NiceGUI에서는 타이머(ui.timer)를 사용하여 주기적으로 데이터를 읽어오거나, 명시적으로 UI 업데이트를 요청하는 방식이 더 안정적일 수 있습니다.

3) 실습 과제

  1. 단위 추가하기: 전압 숫자 뒤에 "V"라는 글자가 항상 붙어 있도록 코드를 수정해보세요. (힌트: bind_text 대신 f-string 포맷팅이나 별도의 라벨 배치)
  2. 색상 경고등: 전압이 11.0V 이하로 떨어지면 글자 색이 빨간색으로 변하고, 정상이면 초록색이 되도록 기능을 추가해보세요.

6. 결론

이 예제는 Back-end(ROS 로봇 데이터)Front-end(웹 GUI)를 연결하는 가장 기초적이면서도 강력한 패턴을 보여줍니다. 이를 응용하면 로봇의 위치, 센서 값, 카메라 영상 등을 웹 브라우저에서 실시간으로 관제하는 시스템을 만들 수 있습니다.

NiceGUI를 활용한 로봇 원격 조종기 만들기 (버튼 제어)

이전 예제가 데이터를 조회(Read)하는 것이었다면, 이번 예제는 데이터를 제어(Write/Publish)하는 실습입니다. 이를 바탕으로 구성한 교재 내용입니다.

1. 학습 목표

  • ROS Publisher의 개념을 이해하고, Twist 메시지를 통해 로봇의 속도를 제어한다.
  • GUI 이벤트(Event)의 종류(mousedown, mouseup, mouseleave)를 이해한다.
  • 버튼을 누르고 있을 때만 로봇이 움직이는 모멘터리(Momentary) 스위치 기능을 구현한다.

2. 전체 코드 확인

작성된 코드는 '전진' 버튼 하나를 만들고, 마우스 동작에 따라 로봇에 명령을 내리는 구조입니다.

python
import rospy
from geometry_msgs.msg import Twist
from nicegui import ui

# 1. ROS 노드 및 퍼블리셔 설정
rospy.init_node('web_gui_control_node', anonymous=True, disable_signals=True)
# '/cmd_vel/auto' 토픽으로 속도 명령을 보냅니다.
pub = rospy.Publisher('/cmd_vel/auto', Twist, queue_size=10)

# 2. 속도 명령 발행 함수
def publish_vel(linear: float, angular: float):
    twist = Twist()
    twist.linear.x = linear   # 직선 속도 (m/s)
    twist.angular.z = angular # 회전 속도 (rad/s)
    pub.publish(twist)

# 3. 정지 함수 (안전을 위해 분리)
def stop_vel():
    publish_vel(0.0, 0.0)

# 4. GUI 버튼 생성 및 이벤트 연결
# 위쪽 화살표 아이콘을 가진 버튼 생성
btn_up = ui.button(icon='arrow_upward')

# 마우스를 누르는 순간 -> 전진 (0.5 m/s)
btn_up.on('mousedown', lambda: publish_vel(0.5, 0.0))

# 마우스를 떼는 순간 -> 정지
btn_up.on('mouseup', stop_vel)

# 마우스가 버튼 밖으로 벗어나는 순간 -> 정지 (안전 장치)
btn_up.on('mouseleave', stop_vel)

# 5. GUI 실행
ui.run()
로봇 on

3. 핵심 개념 분석

1) ROS Publisher 설정

python
pub = rospy.Publisher('/cmd_vel/auto', Twist, queue_size=10)
  • Topic: /cmd_vel/auto라는 이름의 토픽으로 메시지를 보냅니다. (일반적으로 로봇 제어에는 /cmd_vel을 쓰지만, 자율주행이나 다른 제어기와 구분하기 위해 이름을 다르게 짓기도 합니다.)
  • Message Type: Twist는 로봇의 선속도(Linear Velocity)와 각속도(Angular Velocity)를 담는 표준 메시지 타입입니다.

2) 람다(Lambda) 함수의 활용

python
btn_up.on('mousedown', lambda: publish_vel(0.5, 0.0))
  • publish_vel 함수는 인자(linear, angular)가 필요합니다.
  • on 메서드 안에 publish_vel(0.5, 0.0)라고 직접 쓰면, 프로그램 시작과 동시에 함수가 실행되어 버립니다.
  • Lambda: "이벤트가 발생했을 때만 이 함수를 실행해라"라고 감싸주는 역할을 합니다. 즉, 매개변수를 넘겨주기 위해 사용된 익명 함수입니다.

4. 상세 동작 원리 (이벤트 핸들링)

로봇 조종 인터페이스에서 가장 중요한 것은 안전입니다. 사용자가 버튼을 눌렀다가 실수로 놓치거나 마우스가 미끄러졌을 때 로봇은 즉시 멈춰야 합니다. 이를 위해 3가지 이벤트를 조합합니다.

이벤트(Event)동작 설명로봇 상태
mousedown사용자가 마우스 버튼을 누르는 순간 발생전진 (속도 0.5)
mouseup사용자가 마우스 버튼을 떼는 순간 발생정지 (속도 0.0)
mouseleave마우스 커서가 버튼 영역 밖으로 나갈 때 발생정지 (속도 0.0)

click 이벤트를 쓰지 않나요?

click 이벤트는 "눌렀다 떼는 행위"가 완료되어야 발생합니다. 로봇 조종에서는 "누르고 있는 동안" 계속 움직여야 하므로 click 대신 mousedownmouseup을 분리해서 사용해야 직관적인 운전이 가능합니다.

5. 실습 과제 (Challenge)

현재 코드는 "전진" 버튼 하나만 있습니다. 이를 확장하여 완전한 조종기를 만들어 봅시다.

과제 1: 4방향 조종기 만들기

  • 후진(Down), 좌회전(Left), 우회전(Right) 버튼을 추가하세요.
  • ui.row()ui.column()을 사용하여 십자키(D-pad) 모양으로 버튼을 배치해보세요.
    • 힌트: btn_down, btn_left, btn_right 변수를 만들고 각각 적절한 속도 값을 publish_vel에 넣어보세요.
    • 후진: publish_vel(-0.5, 0.0)
    • 좌회전: publish_vel(0.0, 0.5)

과제 2: 속도 조절 슬라이더 추가

  • 현재 속도는 0.5로 고정되어 있습니다. ui.slider를 추가하여 사용자가 속도를 조절할 수 있게 해보세요.
    python
    speed_slider = ui.slider(min=0.1, max=1.0, value=0.5)
    # 힌트: lambda: publish_vel(speed_slider.value, 0.0)

6. 주의 사항

  1. 실제 로봇 테스트: 실제 로봇에서 테스트할 때는 로봇이 갑자기 움직일 수 있으므로, 바퀴를 지면에서 띄우거나 넓은 공간에서 테스트해야 합니다.
  2. 네트워크 지연: Wi-Fi 상태가 좋지 않으면 mouseup 신호가 늦게 전달되어, 버튼을 뗐는데도 로봇이 조금 더 움직일 수 있습니다. (이를 방지하기 위해 로봇 내부적으로 Watchdog 타이머를 설정하기도 합니다.)

7. 결론

이 예제는 웹 브라우저를 통해 하드웨어(로봇)에 물리적인 명령을 내리는 방법을 보여줍니다. 이벤트 기반 프로그래밍(Event-driven Programming)을 통해 사용자의 행동에 즉각적으로 반응하는 안전한 제어 시스템을 구축할 수 있습니다.