[UnrealRobotics: SO-101] (8) 로봇 상태 퍼블리셔(robot_status_publisher) 셋업, Rviz 연결
rclpy 환경 충돌 문제 해결
- LeRobot은 conda env lerobot (Python 3.12, miniforge)에서 동작
- ROS2 Humble의 rclpy는 시스템 Python 3.10 (Ubuntu 22.04)에서 동작
- 두 환경은 PATH 충돌을 일으켜 같은 터미널에서 못 씀
먼저 확인할 내용
- 시스템 Python 버전 (Ubuntu 22.04 기본 = 3.10 예상)
- conda env lerobot의 Python 버전 (3.12 — PROGRESS.md 기재)
- ros-humble-rclpy가 apt로 설치되어 있는지, 어느 Python을 타겟으로 빌드되었는지
- rclpy의 .so 파일이 어느 CPython ABI를 요구하는지
- LeRobot의 주요 dependency (특히 torch)가 system Python venv에 설치 가능한 크기/복잡도인지
# ═══════════════════════════════════════════════════════════
# 1. 시스템 Python 확인 (conda 영향 제거한 상태에서)
# ═══════════════════════════════════════════════════════════
echo "===== [1] System Python ====="
# conda PATH 제거
export PATH=$(echo $PATH | tr ':' '\n' | grep -v miniforge | tr '\n' ':' | sed 's/:$//')
which python3
python3 --version
which python3.10
python3.10 --version 2>/dev/null || echo "python3.10 not found"
# ═══════════════════════════════════════════════════════════
# 2. ROS2 rclpy 설치 상태 및 Python ABI 타겟 확인
# ═══════════════════════════════════════════════════════════
echo ""
echo "===== [2] rclpy installation ====="
dpkg -l | grep ros-humble-rclpy || echo "ros-humble-rclpy not installed via apt"
echo ""
echo "--- rclpy Python package location ---"
ls -la /opt/ros/humble/lib/python3.10/site-packages/rclpy/ 2>/dev/null | head -20 || echo "not found at python3.10 path"
echo ""
echo "--- rclpy .so file (ABI check) ---"
find /opt/ros/humble -name "_rclpy_pybind11*.so" 2>/dev/null
# ═══════════════════════════════════════════════════════════
# 3. ROS2 source → rclpy import 검증 (시스템 Python)
# ═══════════════════════════════════════════════════════════
echo ""
echo "===== [3] rclpy import test (system python3) ====="
source /opt/ros/humble/setup.bash
python3 -c "import rclpy; print('rclpy OK:', rclpy.__file__)" 2>&1
# ═══════════════════════════════════════════════════════════
# 4. conda env 'lerobot' Python 버전 + LeRobot import 확인
# ═══════════════════════════════════════════════════════════
echo ""
echo "===== [4] conda env 'lerobot' ====="
# miniforge PATH 복원
source ~/miniforge3/etc/profile.d/conda.sh 2>/dev/null || source ~/miniconda3/etc/profile.d/conda.sh 2>/dev/null
conda activate lerobot
which python
python --version
python -c "import lerobot; print('lerobot version:', getattr(lerobot, '__version__', 'unknown')); print('lerobot path:', lerobot.__file__)" 2>&1
conda deactivate
# ═══════════════════════════════════════════════════════════
# 5. LeRobot 주요 dependency 크기 확인 (venv 방식 feasibility)
# ═══════════════════════════════════════════════════════════
echo ""
echo "===== [5] LeRobot deps ====="
source ~/miniforge3/etc/profile.d/conda.sh 2>/dev/null || source ~/miniconda3/etc/profile.d/conda.sh 2>/dev/null
conda activate lerobot
pip list 2>/dev/null | grep -iE "^(torch|numpy|scipy|feetech|scservo)" || echo "deps check failed"
conda deactivate
# ═══════════════════════════════════════════════════════════
# 6. 시스템 Python 3.10에서 LeRobot dependency 설치 가능성 프리뷰
# ═══════════════════════════════════════════════════════════
echo ""
echo "===== [6] system python3.10 - pip availability ====="
python3.10 -m pip --version 2>&1 || echo "pip not available for python3.10"
python3.10 -m venv --help > /dev/null 2>&1 && echo "venv module: OK" || echo "venv module: MISSING (need: sudo apt install python3.10-venv)"
위의 명령어 실행으로, 다음의 정보를 알아낼 수 있다.
| 시스템 Python | 3.10.12 |
| ros-humble-rclpy | 3.3.21 설치됨, python3.10 ABI 바이너리 (_rclpy_pybind11.cpython-310-*.so) |
| rclpy import (시스템 py3) | OK |
| conda lerobot env | Python 3.12.13, lerobot 0.5.2 |
| LeRobot deps | torch 2.10, numpy 2.2, feetech-servo-sdk |
| python3.10-venv | OK |
| python3.10 -m pip | 없음 (apt로 python3-pip 설치 필요) |
위의 정보를 바탕으로, rclpy는 python 3.10 ABI 바이너리로 lock-in 되어 있어서 conda Python 3.12에서는 사용할 수 없고, 시스템에 3.10+venv가 있으므로 venv를 쓰는 방향으로 갈 수 있다. venv는 격리되므로 ROS2 Humble apt 패키지를 건드리지 않는다. 또한, 3.10 바이너리를 그대로 쓰기 때문에 rclpy의 .so와 ABI 호환이 된다.
ROS2 bridge 용으로 별도의 Python 3.10 venv를 만들어서 LeRobot을 한 번 더 설치한다.
1단계: 실행 계획 (에러 발생)
# ═══════════════════════════════════════════════════════════
# Step 1.1: python3.10-pip 설치 (apt)
# ═══════════════════════════════════════════════════════════
sudo apt update
sudo apt install -y python3-pip python3.10-venv
# ═══════════════════════════════════════════════════════════
# Step 1.2: conda 영향 배제하고 순수 시스템 환경 확보
# ═══════════════════════════════════════════════════════════
export PATH=$(echo $PATH | tr ':' '\n' | grep -v miniforge | tr '\n' ':' | sed 's/:$//')
# conda auto-activate 방지
conda config --set auto_activate_base false 2>/dev/null || true
# ═══════════════════════════════════════════════════════════
# Step 1.3: venv 생성 (프로젝트 workspace 안에 배치)
# ═══════════════════════════════════════════════════════════
cd ~/UnrealRobotics
python3.10 -m venv .venv-ros-bridge
source .venv-ros-bridge/bin/activate
# 확인
which python # 반드시 ~/UnrealRobotics/.venv-ros-bridge/bin/python
python --version # 3.10.12
# ═══════════════════════════════════════════════════════════
# Step 1.4: pip 업그레이드 + LeRobot editable install
# ═══════════════════════════════════════════════════════════
pip install --upgrade pip setuptools wheel
# LeRobot은 이미 ~/lerobot에 클론되어 있음 (conda env와 공유)
# editable 설치이므로 같은 소스를 양쪽 env에서 사용 가능
cd ~/lerobot
pip install -e ".[feetech]"
# ═══════════════════════════════════════════════════════════
# Step 1.5: rclpy + lerobot 동시 import 검증 (핵심!)
# ═══════════════════════════════════════════════════════════
cd ~/UnrealRobotics
source /opt/ros/humble/setup.bash
# venv는 이미 activate 상태여야 함 (which python으로 재확인)
which python
python <<'EOF'
import sys
print("Python:", sys.version)
print("Prefix:", sys.prefix)
import rclpy
print("rclpy OK:", rclpy.__file__)
from lerobot.robots.so_follower import SO101Follower, SO101FollowerConfig
print("SO101Follower OK")
from lerobot.teleoperators.so_leader import SOLeader, SO101LeaderConfig
print("SOLeader OK")
# Bridge 노드에서 쓸 메시지 타입도 미리 검증
from sensor_msgs.msg import JointState
print("JointState OK:", JointState)
print("\n=== All imports successful. Ready for Phase 3.3 bridge node. ===")
EOF
Step 1.4 진행 중 " ERROR: Package 'lerobot' requires a different Python: 3.10.12 not in '>=3.12'" 에러가 발생하여 다른 방법을 사용해야 한다. rclpy는 3.10, lerobot은 3.12 이상 버전을 필수로 한다...
1단계 대안: 두 개의 프로세스 + ZeroMQ 방안
기존 계획을 변경한다. single-process rclpy+LeRobot 래핑 → two-process + ZeroMQ IPC
┌─────────────────────────┐ ┌──────────────────────────┐
│ lerobot_worker.py │ │ ros_bridge_node.py │
│ (conda env, Python 3.12) │<─IPC─>│ (system py3.10 + rclpy) │
│ - LeRobot API 호출 │ │ - rclpy publish/subscribe │
│ - /dev/ttyACM* 점유 │ │ - ROS2 <-> IPC 번역 │
└─────────────────────────┘ └──────────────────────────┘
위에서 만든 .venv-ros-bridge venv는 그대로 살려둔다.
pyzmq는 두 곳에 설치한다.
- conda env lerobot에 pip install pyzmq
- venv .venv-ros-bridge에 pip install pyzmq
1-A. venv (.venv-ros-bridge)에 pyzmq + rclpy 공존 검증
# 깨끗한 환경 확보 — conda PATH 제거
export PATH=$(echo $PATH | tr ':' '\n' | grep -v miniforge | tr '\n' ':' | sed 's/:$//')
# venv 활성화
cd ~/UnrealRobotics
source .venv-ros-bridge/bin/activate
# 확인
which python # ~/UnrealRobotics/.venv-ros-bridge/bin/python
python --version # 3.10.12
# pyzmq 설치
pip install pyzmq
# ROS2 source + rclpy + pyzmq 동시 import 검증
source /opt/ros/humble/setup.bash
pip install numpy #python 실행 전 numpy 설치
python <<'EOF'
import sys
print("Python:", sys.version.split()[0])
print("Prefix:", sys.prefix)
import rclpy
print("rclpy OK:", rclpy.__file__)
import zmq
print("pyzmq OK:", zmq.__version__)
from sensor_msgs.msg import JointState
print("JointState OK")
# ZMQ 기본 socket 생성 smoke test
ctx = zmq.Context()
pub = ctx.socket(zmq.PUB)
pub.bind("tcp://127.0.0.1:5555")
pub.close()
ctx.term()
print("ZMQ socket smoke test OK")
print("\n=== venv side: rclpy + pyzmq coexistence verified ===")
EOF
deactivate

ok로 잘 나오면 성공.
1-B. conda env (lerobot)에 pyzmq 추가 + LeRobot 공존 검증
# conda 활성화 (새 터미널 열었다면 source 필요할 수 있음)
source ~/miniforge3/etc/profile.d/conda.sh
conda activate lerobot
which python # ~/miniforge3/envs/lerobot/bin/python
python --version # 3.12.13
# pyzmq 설치
pip install pyzmq
# LeRobot + pyzmq 공존 import 검증
python <<'EOF'
import sys
print("Python:", sys.version.split()[0])
print("Prefix:", sys.prefix)
import zmq
print("pyzmq OK:", zmq.__version__)
from lerobot.robots.so_follower import SO101Follower, SO101FollowerConfig
print("SO101Follower OK")
from lerobot.teleoperators.so_leader import SOLeader, SO101LeaderConfig
print("SOLeader OK")
# ZMQ 기본 socket smoke test (bridge 쪽과 port 겹치지 않게 5557 사용)
ctx = zmq.Context()
pub = ctx.socket(zmq.PUB)
pub.bind("tcp://127.0.0.1:5557")
pub.close()
ctx.term()
print("ZMQ socket smoke test OK")
print("\n=== worker side: lerobot + pyzmq coexistence verified ===")
EOF
conda deactivate

OK가 잘 나오면 성공.
1-C. Cross-process ZMQ loopback 검증 (두 env 간 실제 메시지 전달)
두 env의 pyzmq 버전이 호환되는지, 그리고 localhost TCP로 데이터가 오가는지 실제로 확인한다. 두 터미널이 필요하다.
터미널 1 (worker 역할, conda env):
source ~/miniforge3/etc/profile.d/conda.sh
conda activate lerobot
python <<'EOF'
import zmq, time, json
ctx = zmq.Context()
pub = ctx.socket(zmq.PUB)
pub.bind("tcp://127.0.0.1:5555")
print("PUB bound on 5555. Sending 20 messages at 10Hz...")
time.sleep(0.5) # SUB이 연결할 시간
for i in range(20):
msg = {"seq": i, "ts": time.time(), "dummy_joint_pos": -5.89 + i * 0.1}
pub.send_string(json.dumps(msg))
print(f" sent: {msg}")
time.sleep(0.1)
pub.close()
ctx.term()
print("PUB done.")
EOF
터미널 2 (bridge 역할, venv + ROS2 source) — 터미널 1 실행 직전에 먼저 SUB을 띄워놓아야 초기 메시지를 놓치지 않는다.
export PATH=$(echo $PATH | tr ':' '\n' | grep -v miniforge | tr '\n' ':' | sed 's/:$//')
cd ~/UnrealRobotics
source .venv-ros-bridge/bin/activate
source /opt/ros/humble/setup.bash
python <<'EOF'
import zmq, json
ctx = zmq.Context()
sub = ctx.socket(zmq.SUB)
sub.connect("tcp://127.0.0.1:5555")
sub.setsockopt_string(zmq.SUBSCRIBE, "") # 모든 메시지 구독
sub.setsockopt(zmq.RCVTIMEO, 5000) # 5초 idle이면 종료
print("SUB connected to 5555. Waiting for messages...")
count = 0
try:
while True:
raw = sub.recv_string()
msg = json.loads(raw)
print(f" recv: {msg}")
count += 1
except zmq.Again:
pass
print(f"\nTotal received: {count}")
sub.close()
ctx.term()
EOF
총 17개의 수신으로 성공적으로 동작하는 것을 확인할 수 있었다. ZMQ PUB/SUB 정상 동작 확인.

2단계: 파일 구조 생성
이제 실제 코드를 작성하며 두 파일을 만들어야 한다.
- lerobot_worker.py — conda env에서 실행, LeRobot API + ZMQ PUB/REP
- ros_bridge_node.py — venv + ROS2에서 실행, ZMQ SUB/REQ + rclpy
파일 디렉토리 계획
~/UnrealRobotics/src/lerobot_ros2_bridge/
├── package.xml
├── setup.py
├── setup.cfg
├── resource/
│ └── lerobot_ros2_bridge
├── lerobot_ros2_bridge/
│ ├── __init__.py
│ └── ros_bridge_node.py ← rclpy 노드 (venv에서 실행)
└── scripts/
└── lerobot_worker.py ← LeRobot + ZMQ (conda에서 실행)
파일 생성
# 아래 구조를 수동 생성:
mkdir -p ~/UnrealRobotics/src/lerobot_ros2_bridge/{lerobot_ros2_bridge,scripts,resource}
touch ~/UnrealRobotics/src/lerobot_ros2_bridge/resource/lerobot_ros2_bridge
touch ~/UnrealRobotics/src/lerobot_ros2_bridge/lerobot_ros2_bridge/__init__.py
# 그 다음 5개 파일을 맞게 배치
5개 파일을 디렉토리 계획에 맞게 배치한다.
# 2. ROS2 빌드 (miniforge PATH 제거 필수!)
export PATH=$(echo $PATH | tr ':' '\n' | grep -v miniforge | tr '\n' ':' | sed 's/:$//')
source /opt/ros/humble/setup.bash
cd ~/UnrealRobotics
colcon build --packages-select lerobot_ros2_bridge --symlink-install
source install/setup.bash
# 3. 빌드 성공 확인
ros2 pkg list | grep lerobot

빌드 성공과 패키지 등록이 확인되면 성공.
3단계: 로봇 없이 end-to-end 파이프 검증
실물 로봇 연결 전에 dummy 데이터로 전체 파이프라인을 테스트한다. worker를 가짜 joint 값으로 돌려서 ZMQ → ROS2 → ros2 topic echo까지 데이터가 흐르는지 확인.
터미널이 3개 필요하다.
터미널 1 — dummy worker (conda env):
source ~/miniforge3/etc/profile.d/conda.sh
conda activate lerobot
python3 <<'WORKER_EOF'
import zmq, json, time, math, signal, sys
ctx = zmq.Context()
pub = ctx.socket(zmq.PUB)
pub.bind("tcp://127.0.0.1:5555")
rep = ctx.socket(zmq.REP)
rep.bind("tcp://127.0.0.1:5556")
poller = zmq.Poller()
poller.register(rep, zmq.POLLIN)
print("[dummy-worker] PUB on 5555, REP on 5556. Sending fake joints at 30Hz...")
joints = ["shoulder_pan","shoulder_lift","elbow_flex","wrist_flex","wrist_roll","gripper"]
seq = 0
running = True
signal.signal(signal.SIGINT, lambda *a: sys.exit(0))
while running:
t = time.time()
# Sine wave in degrees (±30°) so values look realistic
pos = {j: 30.0 * math.sin(t + i * 0.5) for i, j in enumerate(joints)}
msg = {"seq": seq, "ts": t, "follower": pos, "leader": pos}
pub.send_string(json.dumps(msg))
seq += 1
# Check commands (non-blocking)
evts = dict(poller.poll(timeout=0))
if rep in evts:
raw = rep.recv_string()
print(f" cmd recv: {raw[:80]}")
rep.send_string(json.dumps({"status": "ok"}))
time.sleep(1/30)
WORKER_EOF
터미널 2 — bridge node (venv + ROS2): 에러 터미널3 에러로 아래 다시 작성
export PATH=$(echo $PATH | tr ':' '\n' | grep -v miniforge | tr '\n' ':' | sed 's/:$//')
cd ~/UnrealRobotics
source .venv-ros-bridge/bin/activate
source /opt/ros/humble/setup.bash
source install/setup.bash
ros2 run lerobot_ros2_bridge bridge_node
터미널 3 — 검증 (ROS2):
export PATH=$(echo $PATH | tr ':' '\n' | grep -v miniforge | tr '\n' ':' | sed 's/:$//')
source /opt/ros/humble/setup.bash
# joint_states 토픽이 보이는지 확인
ros2 topic list | grep joint
# 데이터 흐름 확인 (5개만 보고 종료)
ros2 topic echo /joint_states --once
*터미널2에서 오류 발생 "ModuleNotFoundError: No module named 'zmq'"
venv의 pyzmq가 ros2 run에서 보이지 않는다고 한다. ros2 run은 venv의 site-packages를 자동으로 참조하지 않기 때문.
export PATH=$(echo $PATH | tr ':' '\n' | grep -v miniforge | tr '\n' ':' | sed 's/:$//')
sudo pip3 install pyzmq numpy --break-system-packages 2>/dev/null || sudo pip3 install pyzmq numpy
-> 수정 후 터미널2에서 메시지 개수가 늘어나는 것 확인.
*터미널3에서 아무것도 출력되지 않는 오류 발생. FastDDS discovery가 WSL2 mirrored mode에서 완전히 막혀있는 상황. ros2 topic list 자체가 hang하는 건 daemon과의 통신도 안 되는 것.
# 터미널 3에서 먼저 테스트
export PATH=$(echo $PATH | tr ':' '\n' | grep -v miniforge | tr '\n' ':' | sed 's/:$//')
source /opt/ros/humble/setup.bash
# CycloneDDS 설치 (이미 있을 수도 있음)
sudo apt install -y ros-humble-rmw-cyclonedds-cpp
# RMW 전환
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
export ROS_LOCALHOST_ONLY=1
# daemon 재시작 (중요!)
ros2 daemon stop
ros2 daemon start
# 테스트
ros2 topic list
/rosout 등을 출력하면 성공.
터미널2 - (bridge node)
export PATH=$(echo $PATH | tr ':' '\n' | grep -v miniforge | tr '\n' ':' | sed 's/:$//')
cd ~/UnrealRobotics
source .venv-ros-bridge/bin/activate
source /opt/ros/humble/setup.bash
source install/setup.bash
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
export ROS_LOCALHOST_ONLY=1
ros2 run lerobot_ros2_bridge bridge_node
터미널3 - 출력
ros2 topic list
ros2 topic echo /joint_states --once

맨 오른쪽 터미널3에서 6개의 joint 이름, position 값이 잘 표시되는 것을 확인했다!
결론적으로 ROS2 노드 간 DDS discovery는 CycloneDDS가 필요하다고 결정되었다.
전체 데이터 흐름은
dummy worker (ZMQ PUB) → bridge node (ZMQ SUB → rclpy publish) → /joint_states (ROS2 topic)
4단계: robot_state_publisher 연동
bridge node가 퍼블리시하는 /joint_states를 robot_state_publisher가 받아서 TF 트리를 생성하면, RViz에서 실시간으로 로봇 모델이 움직이는 걸 볼 수 있다. 이미 URDF + display.launch.py가 검증됐으니 바로 연결 가능하다.
터미널1, 2가 계속 돌아가는 상황에서
터미널4 - robot_state_publisher
export PATH=$(echo $PATH | tr ':' '\n' | grep -v miniforge | tr '\n' ':' | sed 's/:$//')
source /opt/ros/humble/setup.bash
source ~/UnrealRobotics/install/setup.bash
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
export ROS_LOCALHOST_ONLY=1
# xacro → URDF 변환 후 robot_state_publisher 실행
xacro ~/UnrealRobotics/src/so101_description/urdf/so101_arm.urdf.xacro variant:=follower > /tmp/so101_follower.urdf
ros2 run robot_state_publisher robot_state_publisher --ros-args -p robot_description:="$(cat /tmp/so101_follower.urdf)"
*URDF 내용이 너무 길어서 커맨드라인 인자로 전달이 안되는 오류 발생
# 파일로 파라미터 전달
cat > /tmp/rsp_params.yaml << 'YAML'
robot_state_publisher:
ros__parameters:
robot_description: ""
YAML
# URDF 내용을 yaml에 삽입
python3 -c "
import yaml
with open('/tmp/so101_follower.urdf') as f:
urdf = f.read()
params = {'robot_state_publisher': {'ros__parameters': {'robot_description': urdf}}}
with open('/tmp/rsp_params.yaml', 'w') as f:
yaml.dump(params, f, default_flow_style=False)
print('Done')
"
export PATH=$(echo $PATH | tr ':' '\n' | grep -v miniforge | tr '\n' ':' | sed 's/:$//')
source /opt/ros/humble/setup.bash
source ~/UnrealRobotics/install/setup.bash
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
export ROS_LOCALHOST_ONLY=1
ros2 run robot_state_publisher robot_state_publisher --ros-args --params-file /tmp/rsp_params.yaml

링크 출력 확인.
터미널5 - rviz2
export PATH=$(echo $PATH | tr ':' '\n' | grep -v miniforge | tr '\n' ':' | sed 's/:$//')
source /opt/ros/humble/setup.bash
source ~/UnrealRobotics/install/setup.bash
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
export ROS_LOCALHOST_ONLY=1
rviz2
rviz2 화면에서 [Add] - RobotModel로 로봇을 추가하고,
[Global Options] - [Fixed Frame] - base_link로 설정하고,
[RobotModel] - [Description Topic] 오른쪽이 비어있다면 /robot_description을 직접 지정해야 한다.


로봇이 원을 그리듯 움직이면 성공.
*터미널1~5 접은글로 전체 코드 정리
터미널1 (dummy worker)
source ~/miniforge3/etc/profile.d/conda.sh
conda activate lerobot
python3 <<'WORKER_EOF'
import zmq, json, time, math, signal, sys
ctx = zmq.Context()
pub = ctx.socket(zmq.PUB)
pub.bind("tcp://127.0.0.1:5555")
rep = ctx.socket(zmq.REP)
rep.bind("tcp://127.0.0.1:5556")
poller = zmq.Poller()
poller.register(rep, zmq.POLLIN)
print("[dummy-worker] PUB on 5555, REP on 5556. Sending fake joints at 30Hz...")
joints = ["shoulder_pan","shoulder_lift","elbow_flex","wrist_flex","wrist_roll","gripper"]
seq = 0
signal.signal(signal.SIGINT, lambda *a: sys.exit(0))
while True:
t = time.time()
pos = {j: 30.0 * math.sin(t + i * 0.5) for i, j in enumerate(joints)}
msg = {"seq": seq, "ts": t, "follower": pos, "leader": pos}
pub.send_string(json.dumps(msg))
seq += 1
evts = dict(poller.poll(timeout=0))
if rep in evts:
raw = rep.recv_string()
rep.send_string(json.dumps({"status": "ok"}))
time.sleep(1/30)
WORKER_EOF
터미널 2 (bridge node):
export PATH=$(echo $PATH | tr ':' '\n' | grep -v miniforge | tr '\n' ':' | sed 's/:$//')
cd ~/UnrealRobotics
source /opt/ros/humble/setup.bash
source install/setup.bash
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
export ROS_LOCALHOST_ONLY=1
ros2 run lerobot_ros2_bridge bridge_node
터미널 3 (robot_state_publisher):
export PATH=$(echo $PATH | tr ':' '\n' | grep -v miniforge | tr '\n' ':' | sed 's/:$//')
source /opt/ros/humble/setup.bash
source ~/UnrealRobotics/install/setup.bash
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
export ROS_LOCALHOST_ONLY=1
ros2 run robot_state_publisher robot_state_publisher --ros-args --params-file /tmp/rsp_params.yaml
터미널 4 (검증):
export PATH=$(echo $PATH | tr ':' '\n' | grep -v miniforge | tr '\n' ':' | sed 's/:$//')
source /opt/ros/humble/setup.bash
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
export ROS_LOCALHOST_ONLY=1
ros2 node list
ros2 topic list
ros2 topic echo /joint_states --once
터미널 5 (RViz):
export PATH=$(echo $PATH | tr ':' '\n' | grep -v miniforge | tr '\n' ':' | sed 's/:$//')
source /opt/ros/humble/setup.bash
source ~/UnrealRobotics/install/setup.bash
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
export ROS_LOCALHOST_ONLY=1
rviz2
완성된 부분
- 환경 조사 (rclpy 3.10 vs lerobot 3.12 비호환 확인)
- Two-process + ZeroMQ IPC 아키텍처 결정 ( LeRobot ≥0.5.2는 Python ≥3.12 요구 → rclpy(3.10)와 한 프로세스 공존 불가 → two-process ZMQ 구조 채택)
- pyzmq 양쪽 env 설치 + cross-process 검증
- lerobot_ros2_bridge ROS2 패키지 생성
- CycloneDDS 필수 발견 및 적용 ( ROS2 노드 간 DDS discovery에 FastDDS가 WSL2 mirrored mode에서 동작 안 함. export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp + export ROS_LOCALHOST_ONLY=1 필수. (CycloneDDS 불필요 기술은 rosbridge WebSocket 경로에만 해당)
- dummy worker → ZMQ → bridge node → /joint_states → robot_state_publisher → TF → RViz 3D 모델 실시간 움직임