콘텐츠로 이동

PhysiCar ROS

로봇을 구동하는 ROS 2 Jazzy 스택(physicar-ros)입니다. 시뮬레이터와 실물 키트에서 같은 스택이 돌아가므로, 이 인터페이스로 작성한 코드는 양쪽에서 그대로 동작합니다.

이 페이지의 모든 인터페이스는 워크스페이스 노트북 examples/physicar-ros.ipynb (커널 Python 3 (PhysiCar AI))에서 직접 실행해 볼 수 있습니다.

ROS 2 토픽

센서

토픽 타입 비고
/camera/image_raw/compressed sensor_msgs/CompressedImage 카메라 이미지(JPEG)
/battery_state sensor_msgs/BatteryState 1 Hz, percentage는 0–1 비율
/imu sensor_msgs/Imu 50 Hz
/odom nav_msgs/Odometry 융합(라이다 + IMU)
/scan sensor_msgs/LaserScan 원시
/scan_filtered sensor_msgs/LaserScan 필터링

wait_for_message는 spin 없이 메시지 하나를 받아옵니다 — 노트북에 알맞습니다:

import rclpy
from rclpy.wait_for_message import wait_for_message
from sensor_msgs.msg import CompressedImage

rclpy.init()
node = rclpy.create_node("tutorial")
ok, image = wait_for_message(CompressedImage, node,
                             "/camera/image_raw/compressed", time_to_wait=2.0)

라이다 QoS

라이다는 best-effort 센서 QoS로 발행합니다 — /scan·/scan_filtered 구독에는 qos_profile_sensor_data를 넘겨야 매칭됩니다.

제어

토픽 타입 비고
/cmd_vel geometry_msgs/Twist linear.x = 속도(m/s), angular.z = 회전 속도(rad/s), Ackermann 변환
/speed std_msgs/Float64 속도(m/s)
/steering std_msgs/Float64 조향각(rad), + = 좌회전, 최대 ±20°(±0.35 rad)
/camera/pan std_msgs/Float64 카메라 팬(rad), + = 왼쪽, ±30°(±0.52 rad)
/camera/tilt std_msgs/Float64 카메라 틸트(rad), + = 위, ±30°(±0.52 rad)
  • 안전 워치독 — 속도 명령은 갱신 없이 드라이버의 cmd_timeout(~1초)이 지나면 만료됩니다: 계속 달리려면 주기적으로 발행하세요. 속도만 멈추고 바퀴 각도는 유지됩니다. /cmd_vel에 0으로 채운 Twist()를 보내면 명시적 정지가 되며 조향도 중앙으로 돌아옵니다.
  • 퍼블리셔 디스커버리 — 드라이버가 퍼블리셔를 발견하기 전에 발행한 메시지는 유실됩니다. 먼저 구독자를 기다리세요:
import time

from std_msgs.msg import Float64

speed_pub = node.create_publisher(Float64, "/speed", 10)
while speed_pub.get_subscription_count() == 0:
    time.sleep(0.1)

speed_pub.publish(Float64(data=0.5))

Web API

같은 인터페이스를 HTTP로 제공합니다(physicar_webserver). 워크스페이스에서 실행하는 코드 기준 베이스 URL은 http://localhost, 인터랙티브 문서는 /docs(OpenAPI)에 있습니다. 조회 엔드포인트는 ?stream=true로 실시간 스트리밍을 지원합니다 — 카메라는 MJPEG, 나머지는 SSE.

import requests

requests.post("http://localhost/speed", json={"value": 0.5, "duration": 2.0}, timeout=10)
jpg = requests.get("http://localhost/camera", params={"width": 480}).content

센서 조회 (GET)

경로 비고
/states 전체 상태 스냅샷, ?include=odom,battery,imu로 선택
/speed · /steering m/s · rad
/odom · /battery · /imu 센서 읽기
/lidar ranges, range_min/range_max, count를 담은 스캔; ?step=으로 각도 간격(도 단위, 기본 1) 지정. 0° = 정면, +90° = 왼쪽
/camera JPEG, ?width/?height로 리사이즈
/camera/pan · /camera/tilt 각도(rad)

제어 (POST)

메서드 경로 비고
POST /speed {"value": m/s, "duration": 초?}duration 없이는 갱신하지 않으면 cmd_timeout(~1초) 후 만료. duration을 주면 서버가 명령을 유지하고 끝나면 0을 발행하며, 주행이 끝난 뒤 응답이 돌아옵니다
POST /steering {"value": rad}, 바꿀 때까지 유지
POST /camera/pan · /camera/tilt {"value": rad}
WS /speed/stream · /steering/stream 각 프레임은 POST와 같은 {"value": x}. 데드맨 스위치: 연결이 끊기면 값이 0으로 — 죽은 클라이언트가 로봇을 계속 달리게 둘 수 없습니다

오디오

로봇 스피커의 명령 기반 재생입니다(SIM에서는 브라우저 뷰어에서 재생).

메서드 경로 비고
POST /audio/play url / path / data(base64 오디오 파일) 중 하나; 옵션 volume(0–1), loop, replace
POST /audio/stop id로 지정, 또는 {"all": true}로 전체 정지
POST /audio/volume 재생 중 항목의 볼륨 변경(id, volume 0–1)
POST /audio/duration 재생 중 항목의 길이
GET /audio 현재 재생 중 목록
WS /audio/stream 실시간 PCM16 재생(?sample_rate=24000&channels=1&volume=1.0) — 바이너리 프레임 = raw PCM16, 연결 종료 = 정지

툴 서버 (Tool Server)

AI 채팅의 파이썬 도구를 서빙하는 로컬 FastAPI 서비스입니다 (루프백 전용, nginx 경유 /physicar-ext/). 번들 도구 스크립트 — robot.py(Web API 미러), sim.py(시뮬레이터 API), utils.py(타이밍·음악 검색·예제 러너) — 에 더해 직접 작성하는 /opt/physicar/userdata/custom_tools.py 를 함께 로드합니다. 커스텀 스크립트가 깨져도 서버는 죽지 않습니다 — 마지막으로 정상이던 모듈이 계속 서빙되고 임포트 에러는 그대로 보고됩니다.

메서드 경로 설명
GET /physicar-ext/tools 도구 목록 + 파라미터 스키마 (+ 스크립트별 임포트 에러)
POST /physicar-ext/tools/<name> 도구 실행 — {"args": {...}, "session": "<채팅 세션>"}
POST /physicar-ext/wake 1회용 웨이크 티켓 상환 — 티켓을 예약한 채팅에서 자동 턴 시작
POST /physicar-ext/wake/status 티켓 상태({"wake_id"}) 또는 세션의 미상환 티켓({"session"})
GET /physicar-ext/health 로드된 모듈, 임포트 에러, 프로세스 RSS
POST /physicar-ext/reload 인터프리터 재시작 — 새로 설치한 라이브러리·교체한 모델 가중치 반영

웨이크 티켓은 도구 코드가 나중에 AI 를 깨울 수 있게 합니다: 티켓을 예약하고 (커스텀 도구에서 from pcwake import reserve, redeem, 또는 채팅 도구 utils_wake_reserve), 백그라운드 스레드에 id 를 넘겨뒀다가 이벤트가 발생하면 redeem(wake_id) — 예: "라이다에 0.5 m 이내 장애물이 잡히면 깨워줘". 티켓은 1회용(재시도가 채팅을 도배할 수 없음)이며 메모리에만 존재합니다.

MyApp

포트 5000에 자신의 웹 앱을 띄우면 /myapp/(App 페이지의 MYAPP 탭)에서 접근할 수 있습니다.

  • nginx가 /myapp을 떼고 전달하므로 앱은 자기 루트 기준으로만 작성하면 됩니다: HTML 링크·정적 리소스·리다이렉트·fetch상대 경로로 — 절대 경로(/...)는 /myapp/ 바깥을 가리켜 깨집니다.
  • 자동 시작: /opt/physicar/userdata/myapp.sh가 부팅 시 실행됩니다(앱을 띄우는 명령). 로그는 /opt/physicar/userdata/myapp.log.
  • MyApp 페이지에서 피지카 에이아이 서비스 호출하기 — PhysiCar API 참고.

더 알아보기

AI