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 참고.
더 알아보기
- 시뮬레이터 제어 API → PhysiCar Sim
- 클라우드 AI 서비스(chat/realtime) → PhysiCar API
- 부품이 하는 일 → 로봇 구조 & 센서