Arsitektur CI untuk Validasi Robotika
Perubahan deskripsi fisik robot (URDF/Xacro) tanpa validasi otomatis kerap merusak kontroler kinematika, simulasi dinamika, dan algoritma path planning. Penerapan Continuous Integration (CI) pada repositori robotika memastikan setiap modifikasi deskripsi link, joint limit, tensor inersia, dan visual/collision mesh terverifikasi sebelum masuk ke branch utama.
Pipeline validasi terbagi menjadi dua tier: linting statis berbasis CPU untuk struktur dan kinematika, serta dynamic regression test berbasis physics engine headless (PyBullet atau Isaac Sim) untuk mengukur performa task benchmark.
Validasi Statis: Linting URDF dan Mesh Checking
Langkah pertama memverifikasi dokumen XML dari Xacro atau URDF mentah. Gunakan check_urdf dari paket liburdfdom-tools untuk memvalidasi tree struktur link dan joint, serta script Python untuk memverifikasi file STL/DAE/OBJ yang direferensikan.
Eksekusi CLI Linting
# Konversi Xacro ke URDF jika menggunakan parameter dinamis
xacro robot.urdf.xacro -o robot.urdf
# Validasi struktur tree dan parse XML
check_urdf robot.urdfSkrip Verifikasi Mesh dan Tensor Inersia
Berikut skrip Python lint_robot.py untuk mendeteksi missing mesh file dan mengecek validitas matriks inersia (massa positif dan kondisi non-zero inertia tensor):
import os
import sys
import xml.etree.ElementTree as ET
def validate_urdf(urdf_path):
tree = ET.parse(urdf_path)
root = tree.getroot()
errors = []
for link in root.findall("link"):
link_name = link.attrib.get("name", "unknown")
# Validasi inersia jika link memiliki massa
inertial = link.find("inertial")
if inertial is not None:
mass = inertial.find("mass")
if mass is not None and float(mass.attrib.get("value", 0)) <= 0:
errors.append(f"Link {link_name}: Massa harus > 0")
inertia = inertial.find("inertia")
if inertia is not None:
ixx = float(inertia.attrib.get("ixx", 0))
iyy = float(inertia.attrib.get("iyy", 0))
izz = float(inertia.attrib.get("izz", 0))
if ixx <= 0 or iyy <= 0 or izz <= 0:
errors.append(f"Link {link_name}: Diagonal inersia harus positif")
# Validasi path visual/collision mesh
for mesh in link.iter("mesh"):
uri = mesh.attrib.get("filename", "")
clean_path = uri.replace("package://", "").replace("file://", "")
# Resolve relative path sesuai konteks workspace
if not os.path.exists(clean_path):
errors.append(f"Link {link_name}: Mesh file tidak ditemukan: {uri}")
if errors:
print("\n".join(errors))
sys.exit(1)
print("URDF & Mesh Check Passed.")
if __name__ == "__main__":
validate_urdf(sys.argv[1])Headless Regression Benchmark
Linting statis tidak dapat memverifikasi efek dynamika, slip, atau joint limits saat robot diberi kontrol torsi atau posisi. Regression testing dijalankan secara headless menggunakan PyBullet untuk menguji eksekusi trajectory dan mendeteksi deviasi posisi end-effector dari target ground-truth.
Skrip Benchmark Trajectory
Skrip evaluate_trajectory.py memuat URDF, menjalankan serangkaian command trajectory, dan menghitung Mean Squared Error (MSE) antara posisi aktual dengan expected waypoint:
import sys
import numpy as np
import pybullet as p
import pybullet_data
def run_benchmark(urdf_path, threshold_mse=0.015):
physics_client = p.connect(p.DIRECT) # Mode headless tanpa GUI
p.setAdditionalSearchPath(pybullet_data.getDataPath())
p.setGravity(0, 0, -9.81)
plane_id = p.loadURDF("plane.urdf")
robot_id = p.loadURDF(urdf_path, [0, 0, 0], useFixedBase=True)
num_joints = p.getNumJoints(robot_id)
active_joints = [j for j in range(num_joints) if p.getJointInfo(robot_id, j)[2] != p.JOINT_FIXED]
# Generate trajectory target sintetis (step input per joint)
target_positions = [0.5] * len(active_joints)
actual_positions = []
for step in range(240): # Simulasi 1 detik (240 Hz)
p.setJointMotorControlArray(
robot_id,
active_joints,
controlMode=p.POSITION_CONTROL,
targetPositions=target_positions,
forces=[50.0] * len(active_joints)
)
p.stepSimulation()
states = p.getJointStates(robot_id, active_joints)
final_positions = [state[0] for state in states]
p.disconnect()
mse = np.mean((np.array(target_positions) - np.array(final_positions)) ** 2)
print(f"Trajectory MSE: {mse:.6f} (Threshold: {threshold_mse})")
if mse > threshold_mse:
print("Deviasi melebihi batas toleransi!")
sys.exit(1)
print("Benchmark Passed.")
if __name__ == "__main__":
run_benchmark(sys.argv[1])Konfigurasi GitHub Actions Workflow
Konfigurasi workflow berikut menggabungkan stage linting dan execution benchmark. Untuk pipeline yang menggunakan Isaac Sim atau renderer berat, self-hosted runner berbasis GPU diperlukan. Skrip menggunakan Docker layer caching dan cache dependency untuk meminimalkan durasi eksekusi.
name: Robot Model CI
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
lint-urdf:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Lint Tools
run: |
sudo apt-get update
sudo apt-get install -y liburdfdom-tools xacro
- name: Generate and Check URDF
run: |
xacro models/robot.urdf.xacro -o /tmp/robot.urdf
check_urdf /tmp/robot.urdf
- name: Run Python Mesh and Inertia Linter
run: |
python3 scripts/lint_robot.py /tmp/robot.urdf
trajectory-benchmark:
needs: lint-urdf
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
cache: 'pip'
- name: Install Physics Dependencies
run: |
python -m pip install --upgrade pip
pip install numpy pybullet
- name: Run Headless Benchmark
run: |
python scripts/evaluate_trajectory.py models/robot.urdf
Mitigasi Runner Timeout pada Job Simulasi Berat
Simulasi fisika resolusi tinggi rentan terkena timeout jika dioperasikan pada GitHub-hosted runners standard (2 CPU cores, tanpa GPU). Terapkan strategi berikut untuk menjaga runtime CI tetap rendah:
- Batasi Step Simulasi: Hanya simulasikan durasi minimal yang dibutuhkan kontroler untuk mencapai steady state (misalnya 1-3 detik pada timestep 240 Hz).
- Gunakan Direct Mode (Headless): Hindari inisialisasi context OpenGL visualisasi kecuali sedang menguji visual perception pipeline. Gunakan opsi headless bawaan seperti
p.DIRECTdi PyBullet atau flag--headlessdi Isaac Sim. - Subsampling Collision Meshes: Untuk model simulasi CI, gunakan simplified convex hull collision mesh (.obj/.stl ter-reduksi) alih-alih high-poly visual mesh asli.
- Self-Hosted GPU Runner dengan Caching: Jika beralih ke Omniverse Isaac Sim, jalankan job pada self-hosted runner dengan driver NVIDIA terpasang dan pasang cache Docker container image agar layer simulasi (~15GB+) tidak diunduh ulang di setiap commit.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!