ROS 2 통합
PLEM 로봇은 표준 ROS 2 인터페이스(토픽·서비스·액션)로 제어하고 관측하실 수 있습니다. 이 페이지는 외부 ROS 2 환경을 PLEM 기기에 연결하는 절차와 인터페이스 카탈로그, 클라이언트가 지켜야 할 규약을 다룹니다.
전제
| 기기 Ubuntu | ROS 2 배포판 |
|---|---|
| 22.04 (jammy) | Humble |
| 24.04 (noble) | Jazzy |
로봇 기기의 OS 에 대응하는 같은 ROS 2 배포판의 클라이언트가 지원 구성입니다.
모든 클라이언트는 로봇과 같은 ROS_DOMAIN_ID 와 같은 RMW 를 써야 합니다. 도메인은 기기의 plem Settings(~/.plem/settings.yaml 의 ros_domain_id)가 정하고, RMW 는 CycloneDDS(rmw_cyclonedds_cpp)로 고정입니다. 셸 환경 변수는 plem 이 읽는 값이 아니라 plem 이 셸에 넣어 주는 값입니다.
로봇 기기 안에서 접속하기 (SSH 포함)
가장 간단한 통합 지점은 로봇 기기 자체입니다. 대화형 셸은 PLEM 환경(/opt/plem — rclpy + plem_msgs·plem_drive_msgs)이 자동으로 잡혀 있고, 비대화형 SSH 나 CI 는 bash -lc '...' 로 실행합니다(DDS 세 값까지 들어옵니다). source /opt/plem/setup.bash 만으로는 rclpy·plem_msgs·plem_drive_msgs 는 잡히지만 DDS 값은 들어오지 않으므로 그때도 bash -lc '...' 로 실행합니다.
기기의 DDS 는 기본적으로 루프백(lo) 전용입니다. 같은 기기 안의 대용량 전송(카메라 프레임)을 안정화하기 위한 설정입니다(QA 실측: 카메라 RGB 10.9 → 30.0 Hz). 새 터미널은 열릴 때 plem 이 정한 DDS 환경(도메인·RMW·설정 파일)을 그대로 받으므로 ros2 topic list 를 바로 쓸 수 있습니다. 셸 초기화 파일(~/.bashrc 등)에 ROS_DOMAIN_ID·CYCLONEDDS_URI·RMW_IMPLEMENTATION 을 export 하지 마세요 — 그 셸의 ros2 만 로봇과 갈라집니다. 설정에서 도메인을 바꿨다면 plem 을 다시 시작한 뒤 새 터미널을 여세요. 이미 열린 터미널은 옛 값을 그대로 씁니다.
ros2 topic list # 새 터미널에서 그냥 실행
이 버전으로 처음 업데이트한 뒤 새 터미널에서도 로봇이 보이지 않으면 ros2 daemon stop 을 한 번 실행하세요. 이전 설정을 기억하는 ros2 데몬이 남아 있을 수 있습니다.
다른 호스트에서 접속하기
lo 전용 기본값 때문에 다른 PC 의 ROS 2 클라이언트는 이 기기의 토픽을 에러 없이 못 봅니다. 빈 목록이 나올 뿐 에러가 없으므로, 원격 접속 실패의 1차 확인 대상입니다. 이 프로파일(동봉 파일 /usr/share/plem/cyclonedds-localhost.xml)은 <Domain Id="any"> 로 적용되므로 도메인 번호를 바꾸셔도 lo 전용은 그대로입니다.
크로스-호스트 접속이 필요하면 로봇과 클라이언트 양쪽이 공용 NIC 를 쓰는 CycloneDDS 프로파일을 사용하세요.
- 기기에서 동봉 파일을 복사해
<NetworkInterface name="lo" ...>의name만 실제 NIC 이름(예:eth0)으로 바꾸세요 — 기기 전역이면/etc/plem/cyclonedds.xml, 계정별이면~/.ros/cyclonedds.xml이고, 우선순위는~/.ros/cyclonedds.xml>/etc/plem/cyclonedds.xml> 동봉 파일입니다. 빈 문서로 새로 쓰지 마세요 — 버퍼·최대 메시지 크기 튜닝을 함께 잃습니다. 클라이언트 쪽도 같은 방식으로 맞추세요.sudo install -d -m 0755 /etc/plem && sudo install -m 0644 /usr/share/plem/cyclonedds-localhost.xml /etc/plem/cyclonedds.xml - 파일을 만든 뒤
plem을 다시 실행하고 새 터미널을 여세요 — 로봇 스택과 셸이 같은 파일을 받습니다. 어느 파일이 적용됐는지는plem기동 로그에 한 줄로 남습니다. - 프로파일의
<SocketReceiveBufferSize min="10MB"/>때문에 클라이언트 커널 수신 버퍼가 작으면 노드 생성이 실패할 수 있습니다.sysctl -w net.core.rmem_max=10485760(영구 적용은/etc/sysctl.d/) 로 맞추세요.
lo 전용 해제는 same-host 성능 이점과 네트워크 노출을 맞바꾸는 결정입니다. 필요한 기간과 네트워크에서만 적용하시기를 권장합니다.
plem_msgs·plem_drive_msgs 설치
PLEM 커스텀 인터페이스를 쓰시려면 클라이언트 환경에 plem_msgs·plem_drive_msgs 패키지가 필요합니다(각각 plem-msgs·plem-drive 설치 패키지에 포함). 로봇 기기에는 이미 설치돼 있습니다. 별도 PC 에는 WIM 패키지 저장소(기기 온보딩 시 등록하는 것과 동일)를 등록하신 뒤 두 패키지를 설치하세요. plem-drive 는 메시지만 필요해도 전체가 설치됩니다 — 드라이브 런타임, realtime 그룹·RT 권한 파일, 셸 설정(/etc/profile.d/plem.sh·/etc/bash.bashrc 포인터)을 함께 깝니다(/opt/plem/setup.bash 도 plem-drive 가 싣습니다).
sudo apt install plem-msgs plem-drive # /opt/plem 에 설치 — source /opt/plem/setup.bash 로 오버레이
패키지 이름은 plem-msgs 입니다 (ros-humble-plem-msgs 형태가 아닙니다). 표준 인터페이스만으로는 관절 상태 조회와 그리퍼 사용까지 가능합니다. 다만 팔 구동은 모드 진입(아래 규약)이 필요하고 그 인터페이스가 plem_msgs 이므로, 모션 통합에는 설치가 사실상 필수입니다.
인터페이스 카탈로그
로봇 1대당 자기 네임스페이스 /{robot_id}/ 아래로 노출됩니다. robot_id 는 plem 에서 선택하신 로봇 프로필 이름입니다. PLEM 커스텀 인터페이스는 /{robot_id}/plem/ 아래, ros2_control 표준 인터페이스는 /{robot_id}/ 직하에 있습니다. F/T 센서의 ft_sensor/* 도 /{robot_id}/ 직하에 있습니다.
제어
이름 (/{robot_id}/ 생략) | 타입 | 용도 |
|---|---|---|
plem/set_mode | plem_msgs/action/SetMode | 로봇 모드 전환 (모션 전 필수 — 아래 규약) |
plem/control_command | plem_drive_msgs/srv/SetControlCommand | 복구 프리미티브 — reset_error · protective_stop(브레이크 체결 = BRAKED 전환) · release_brake · free_drive. 뒤의 셋은 하위 모드·브레이크만 전환합니다(컨트롤러 전환·점유 검사 없음). protective_stop 을 release_brake·free_drive 와 한 요청에 보내면 정지만 실행하고 나머지(reset_error 포함)는 버린 뒤 실패로 응답합니다 — 정지·안전 참조 |
plem/plan_trajectory | plem_msgs/action/PlanTrajectory | 궤적 계획 — 기본(DETERMINISTIC)은 예측 가능한 산업 모션. 충돌 회피는 motion_intent=COLLISION_AWARE 로 요청하며 지원 모델에서 자기 충돌과 정적 장면까지 회피합니다. 실제 환경(놓인 물체) 회피에는 지각 스택이 더 필요합니다. ROS 2 Humble(JetPack 6) 트레인 한정: Jazzy(JetPack 7) 기기에는 cuMotion 이 아직 프로비저닝되지 않았습니다 — 지원 모델에서 스택을 이 옵션과 함께 기동하면 cuMotion 플러그인을 찾지 못해 기동이 실패합니다(사전 경고 없음) |
joint_trajectory_controller/follow_joint_trajectory | control_msgs/action/FollowJointTrajectory | 궤적 실행 (표준) — 완료·안전 규약은 아래 참조 |
gripper_action_server/gripper_command | control_msgs/action/GripperCommand | 그리퍼 (전 벤더 공통 이름 — 표준) |
ft_sensor/zero | std_srvs/srv/Trigger | F/T 센서 영점 — 센서에 저장됩니다. 아래 F/T 센서 참조 |
상태·관측
이름 (/{robot_id}/ 생략) | 타입 | 비고 |
|---|---|---|
joint_states | sensor_msgs/msg/JointState | 100 Hz |
joint_states_throttled | sensor_msgs/msg/JointState | 30 Hz 스로틀 — 기기에 topic_tools 가 설치된 경우에만 뜹니다(없으면 joint_states 를 직접 구독) |
joint_trajectory_controller/state | control_msgs/msg/JointTrajectoryControllerState | 궤적 추적 상태 (desired/actual/error) — 실행 중 모니터링 |
status_broadcaster/robot_mode | plem_msgs/msg/RobotMode | 로봇 모드 (50 Hz) |
status_broadcaster/safety_mode | plem_msgs/msg/SafetyMode | 안전 상태 (50 Hz) |
gripper_status | plem_msgs/msg/GripperStatus | 그리퍼 상태. connected 가 false 면 그리퍼가 응답하지 않는 것이고(케이블·공구 전원 확인) 나머지 필드는 마지막으로 읽은 값입니다. effort 는 채우지 않습니다(0.0). 드라이버가 그리퍼의 첫 응답을 받기 전에는 발행하지 않으며, 그 사이에 보낸 그리퍼 명령은 실패합니다 |
ft_sensor/wrench | geometry_msgs/msg/WrenchStamped | F/T 센서 힘·토크, 100 Hz |
ft_sensor/status | diagnostic_msgs/msg/DiagnosticStatus | F/T 센서 상태, 5 Hz |
rt_events | plem_drive_msgs/msg/RtEvent | RT 이산 이벤트 (fault·상태 전이 — 정지 원인 판독) |
rt_diagnostics | plem_msgs/msg/RtDiagnostics | 제어 사이클 진단, ~1 Hz 창 집계 |
rt_raw | plem_drive_msgs/msg/RtRawBatch | 사이클 전수 원시 데이터 (BestEffort 배치) — 분석용. 관절 값은 샘플마다 axis_count 개씩 실립니다. 이전 버전에서 녹화한 /rt_raw·/rt_events bag 은 이 버전에서 풀리지 않으니 녹화한 버전에서 분석하세요. 실시간 그래프는 rt_raw_relay + PlotJuggler 로 봅니다(내 서보 드라이브 붙이기) |
F/T 센서
ft_sensor/* 세 이름은 로봇 프로필에 F/T 센서를 지정한 로봇에만 뜹니다.
- wrench: frame 은
<robot_id>_ft_sensor_frame(센서의 측정 원점 — AFT200-D80 은 벤더 CAD 기준 센서 조립체 꼭대기보다 14.8 mm 아래), 단위는 힘 N·토크 N·m 입니다. 센서 좌표 그대로이며 중력 보상·좌표 변환은 없고, 마지막 영점을 기준으로 한 값입니다. 값이 유효하지 않으면 6축이 모두 NaN 입니다 — 기동 절차가 끝나기 전과 실패한 뒤, 센서가 응답하지 않을 때, 6축 원시값이 20 ms 넘게 그대로일 때(센서 케이블이 빠져도 툴 보드가 마지막 값을 계속 보내므로 이를 무응답으로 봅니다 — 값이 다시 바뀌면 바로 풀립니다), 정격을 넘었을 때(복합 부하 105% 초과, 100% 아래로 내려오면 해제), 측정 범위를 넘어 값이 뒤집혔을 때(6축 모두 감시 — 토크 약 16.4 N·m, 힘 약 655 N)입니다. 그럴듯한 값이나 직전 값을 대신 내지 않습니다. 다만 측정 범위 초과 판정은 샘플 사이의 급변으로 하므로, 한 샘플 만에 아주 크게 걸린 충격이나 스택을 켤 때 이미 범위를 넘어 있던 하중은 유효한 값으로 통과할 수 있습니다. - status: 어댑터가 뜬 순간부터 5 Hz 로 발행합니다(기동 절차 중에는
STARTING). 상태가 오지 않으면 어댑터가 없거나 연결이 끊긴 것입니다.message는 운영자에게 그대로 보여 줄 문장이고, 프로그램은values의state로 분기합니다 —OK·STARTING·ZEROING·NOT_RESPONDING·FROZEN·NO_RELAY·OUT_OF_RANGE·OVER_RATED(뜻과 조치는 F/T 센서).OK가 아니면 wrench 는 NaN 입니다.level은 OK · WARN(STARTING·ZEROING·OVER_RATED·OUT_OF_RANGE) · ERROR(나머지)입니다.values의 나머지 키는utilization(정격 대비 부하, 1.0 = 정격) ·error_flag·overload_byte·wrapped_axes·sent_cycles·zero_seq이고 값은 모두 문자열입니다. 키는 이름으로 찾으세요(순서에 기대지 마세요). - zero_seq: 영점 명령이 센서에 닿았을 수 있을 때마다(전달됨·완료 미확인) 1 씩 오르는 번호이며, 스택이 기동할 때 0 에서 시작합니다. 영점 명령이 도는 동안(해제·영점 두 명령과 적용 확인 — 보통 1 초 이내, 길면 몇 초)은 wrench 가 NaN 이고 명령이 끝나는 순간 번호가 오릅니다. ROS 2 에서는 wrench(100 Hz)에 번호가 없고 번호는 5 Hz status 로 오므로, 값이 돌아온 뒤 다음 status(최대 0.2 초 뒤)에 새 번호가 보이고 짧은 영점은
ZEROINGstatus 없이 지나갈 수도 있습니다 — wrench 의 NaN 구간과 그 뒤 번호 변화로 영점을 알아보고 힘 기준값·필터를 다시 잡으세요. (토크 플러그인은 값·상태·번호를 한 주기에 함께 받으므로 같은 번호의 유효한 값이 같은 기준 위에 있습니다.) 명령을 보내지 못한 경우에도 잠깐ZEROING이 보일 수 있으나 번호는 그대로입니다. - zero: 지금 걸린 힘을 0 으로 잡아 센서에 저장합니다(전원을 꺼도 유지). 기동 절차 중·센서 미인식·정격 초과·로봇 이동 중·이전 요청 처리 중에는 사유와 함께 거부합니다. Job 실행·조그 중 잠금은 펜던트 화면만 하므로, 프로그램에서 부를 때는 호출하는 쪽이 시점을 고르세요. 측정 범위 초과(
OUT_OF_RANGE) 상태는 영점을 막지 않습니다 — 하중을 모두 뺀 뒤의 영점이 그 상태를 푸는 방법이고, 하중이 걸린 채 잡으면 틀린 영점이 저장됩니다.success는 영점 직후 값을 다시 읽어 0 근처(축마다 힘 1 N·토크 0.05 N·m 이내)임을 확인했다는 뜻입니다. 확인하지 못하면success: false와 그때 값을 담은message를 돌려줍니다 — 그 사이 하중이 바뀌면(툴이 움직이거나 닿으면) 실패로 나옵니다. 중력 보상이 없으므로 영점은 잡은 자세·하중에서만 맞습니다.
ros2 topic echo /robot_1/ft_sensor/wrench
ros2 service call /robot_1/ft_sensor/zero std_srvs/srv/Trigger
웹 클라이언트(rosbridge): NaN 은 JSON 으로 표현할 수 없어 null 로 옵니다. Number(null) 은 0 이므로 그대로 쓰면 무효 값이 0 N 으로 보입니다 — CBOR 로 구독하거나(compression: 'cbor') null 을 무효로 처리하세요.
다른 좌표계로 보기: ROS 2 표준 wrench_transformer_node(설치된 force_torque_sensor_broadcaster 패키지에 들어 있습니다)가 TF 로 변환해 줍니다. 아래는 robot_1_tcp 기준 값을 /robot_1/ft_sensor/robot_1_tcp/wrench 로 냅니다. 입력 토픽은 절대 이름으로 remap 합니다. 축 방향과 토크의 기준점(r × F)까지 옮겨 주지만 중력 보상은 하지 않습니다. 변환은 TF 가 있어야 하므로 로봇 스택이 떠 있는 상태에서 실행하세요.
ros2 run force_torque_sensor_broadcaster wrench_transformer_node --ros-args \
-r __ns:=/robot_1/ft_sensor \
-r /robot_1/ft_sensor/fts_wrench_transformer/wrench:=/robot_1/ft_sensor/wrench \
-p target_frames:="['robot_1_tcp']"
모드 상수
RobotMode: BRAKED=0 STOPPING=1 TRAJECTORY=2 FREEDRIVE=3 ERROR=-1
SafetyMode: NORMAL=0 PROTECTIVE_STOP=1
구독 클라이언트는 다섯 값 모두를 처리해야 합니다.
클라이언트 규약
표준 ROS 2 관례 위에서 PLEM 이 요구하는 규약입니다.
모드 먼저
궤적 실행은 로봇이 TRAJECTORY 모드일 때만 수락됩니다. 그 외 모드에서 보낸 FollowJointTrajectory goal 은 거부됩니다(accepted == False, 조용한 무시가 아니라 명시적 거부).
궤적 컨트롤러는 비활성 상태로 로드되고 모드 진입이 활성화하므로, ros2 action list 에 실행 액션이 보인다고 goal 이 수락되는 것은 아닙니다. TRAJECTORY 진입은 plem/set_mode(TRAJECTORY) 로 하세요. plem/control_command 의 release_brake 는 같은 모드 전환을 일으키지만 궤적 컨트롤러를 활성화하지 않아 실행 준비가 끝나지 않습니다.
권장 시퀀스는 plem/set_mode(TRAJECTORY) → plem/plan_trajectory → 결과의 trajectory 를 follow_joint_trajectory 로 실행입니다.
직접 작성한 궤적은 충돌 검사를 거치지 않는다
plem/plan_trajectory 를 거치지 않고 직접 작성해 follow_joint_trajectory 로 보낸 궤적은 어떤 충돌·경로 검사도 거치지 않습니다. 사람이 작업영역 밖에 있고 비상정지에 도달 가능한 상태에서, 검증된 동작에만 사용하세요.
실행 성공은 목표 도달이 아니다
FollowJointTrajectory 의 SUCCEEDED 는 목표 도달을 보장하지 않습니다. 도달 tolerance 가 설정돼 있지 않아 time_from_start 경과 시점에 성공으로 보고합니다. 종료 후 joint_states 로 최종 위치를 독립 검증하세요. 서보 한계를 넘는 궤적(과도하게 짧은 time_from_start)은 보내지 마세요.
마지막 포인트의 velocity 가 0 이 아니어도 goal 은 수락됩니다(allow_nonzero_velocity_at_trajectory_end: true). 안전을 위해 마지막 포인트의 velocity 는 0 으로 두는 것을 권장합니다.
정지·복구
제어된 정지는 실행 goal 의 cancel, 즉시 정지는 plem/control_command 의 protective_stop(브레이크 체결) 또는 plem/set_mode(BRAKED) 입니다. P-STOP 과 ERROR 상태는 자동 복구되지 않습니다. safety_mode 와 robot_mode 를 감시하시고, 복구는 reset_error(P-STOP/ERROR 전용) 후 모드 재진입으로 하세요.
joint 순서
joint_states 의 name 배열 순서는 보장되지 않습니다 (실측 예: joint0,1,2,5,3,4). 인덱스 순서를 하드코딩하지 마시고 항상 name 으로 매핑하세요. 명령(joints_rad, JointTrajectory.joint_names)도 같은 원칙입니다.
단위
각도는 radian, 거리는 meter, 회전 표현은 쿼터니언입니다 (SI 일관). 모션 목표 필드명이 단위를 드러냅니다: MotionTarget.joints_rad. degree 값은 rad = deg × π / 180 으로 변환해 보내세요.
스키마 확인
필드 구조는 문서로 옮겨 적지 않습니다. 인터페이스 정의의 단일 진실은 plem_msgs·plem_drive_msgs 두 패키지이며(타입마다 한 곳에만 정의), 환경에서 직접 조회하세요.
ros2 interface show plem_msgs/action/SetMode
ros2 interface show plem_msgs/action/PlanTrajectory # Goal = MotionTarget target
ros2 interface show plem_msgs/msg/MotionTarget # 모션 목표 필드 전체 (move_type·speed_scale 등)
ros2 interface show plem_drive_msgs/srv/SetControlCommand
인터페이스 가용 시점
로봇 스택 기동 시 로봇별 1초 간격으로 ros2_control 과 그리퍼 드라이버가 병렬로 뜨고, PLEM 상태·명령 인터페이스(plem/set_mode·plem/control_command·status_broadcaster·rt_*)는 하드웨어 활성화 때 함께 생성됩니다. plem/plan_trajectory 는 계획 스택(MoveIt)이 수 초 늦게 기동하므로 마지막에 뜹니다. 클라이언트는 wait_for_server 로 대기하세요.
plem/set_mode 전환이 거부되면 두 경우를 구분하세요.
goal 이 아예 수락되지 않으면(accepted == false) 결과 메시지가 없습니다. 이미 그 모드이거나, 안전 상태가 그 전환을 막거나, 다른 모드 goal 이 처리 중인 경우입니다(모드 goal 은 직렬로 보내세요).
수락된 뒤 실패하면 결과의 message 에 사유가 담깁니다. EtherCAT 링크 비정상(로봇 전원·케이블), 다른 제어 주체(teleop)의 점유, 컨트롤러 전환 실패 등이 같은 증상으로 나타나며, robot_mode·safety_mode·rt_events 가 원인을 가릅니다.