הגדרת Meta Quest 3 VR Teleop
השלם את הנתיב צעד אחר צעד מ-Quest 3 חשוף להפעלה חיה של זרועות הרובוטים. מכסה את הגדרת הרשת, תצורת Unity, שרת Python UDP, piper_controller.py ודפוס המתאם לכל זרוע.
רשת ודרישות מוקדמות
לפני כתיבת קוד כלשהו, אשר כי Meta Quest 3 ומחשב הבקרה יכולים להגיע זה לזה דרך אותה רשת מקומית. המערכת משתמשת ב-UDP - שני המכשירים חייבים לשתף את אותה רשת משנה, ויציאות UDP 8888 ו-8889 חייבות להיות פתוחות בחומת האש של המחשב.
רשימת דרישות:
- אוזניות Meta Quest 3 עם מעקב ידני מופעל (הגדרות ← מעקב אחר תנועה ← מעקב יד)
- נקודת גישה ל-Wi-Fi 6 - מומלץ בחום כדי לשמור על זמן אחזור תחבורה של UDP מתחת ל-10 אלפיות השנייה
- שליטה במחשב עם לינוקס (Ubuntu 22.04+) או macOS עם Python 3.10+
- עבור AgileX Piper: מתאם USB ל-CAN (למשל Kvaser או PEAK), כבל CAN מחובר לזרוע
- יציאות UDP 8888 ו-8889 נפתחות נכנסות בחומת האש של המחשב
בדוק קישוריות:
# On the control PC — find your IP address ip addr show # Linux ipconfig getifaddr en0 # macOS (Wi-Fi) # Quick UDP listener test (run on PC, send any packet from Quest) python3 -c "ייבואשקע; s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM); s.bind(('0.0.0.0',8888)); print('Listening...'); print(s.recvfrom(256))"
ip addr במחשב האישי וודא ששלושת האוקטטים הראשונים תואמים את ה-IP של Quest 3 (גלוי בהגדרות Quest → Wi-Fi → סמל גלגל השיניים). בעיה נפוצה היא שרשתות Wi-Fi ארגוניות מבודדות לקוחות זו מזו - השתמשו בנקודת גישה ייעודית לעבודת רובוטיקה.
תצורת App Unity
אפליקציית Unity הפועלת ב-Quest 3 בנויה עם Unity 2022.3 LTS ואילך, תוך שימוש בחבילת XR Hands למעקב ידני. שלושה סקריפטים מטפלים בצד הטלאופרציה:
- VRHandPoseSender.cs - קורא תנוחת יד מתת-המערכת XR Hands, מסדרת לחבילה בינארית של 45 בתים, שולח באמצעות UDP
- VRGripperController.cs - ממפה את עוצמת הצביטה לערך תפס מנורמל [0, 1]
- VRTeleoperationManager.cs - ניהול מחזור חיים, ממשק משתמש סטטוס חיבור, חיבור אוטומטי מחדש
אתה כן לֹא צריך להרכיב מחדש את אפליקציית Unity כדי לעבור בין זרועות הרובוט. חשפו את השדות הבאים כשדות מפקח בסידרה וכוונו אותם מעורך Unity או דרך קובץ תצורה:
| שדה המפקח | ערך התחלתי של AgileX Piper | הערות |
|---|---|---|
| IP יעד | כתובת ה-IP של המחשב האישי שלך | לָרוּץ ip addr במחשב כדי למצוא אותו |
| מיקום היסט (מ') | (0, 0, 0.3) | משנה את מקור הרובוט הווירטואלי; טווח הפייפר קצר מ-xArm6 |
| סיבוב היסט (מעלה) | (0, 90, 0) | תיקון Y של 90° עבור מסגרת CAN של Piper; התאם לפי כיוון הרכבה |
| scaleFactor | 0.75 | מקטין את טווח תנועת היד כדי להתאים לסביבת העבודה של פייפר (כ-600 מ"מ טווח) |
| סביבת עבודה X (מ"מ) | ±400 | השאר שוליים של 50 מ"מ בתוך גבולות פיזיים |
| סביבת עבודה Z (מ"מ) | 50 – 700 | שמור על Z min מעל פני השולחן כדי למנוע התנגשות |
הגדרת שרת Python UDP
שרת Python מריץ שלושה שרשורים במקביל: א חוט מקלט שקורא נתונים גולמיים של UDP, א חוט בטיחות שמאמתת מנות ומעמידה אותן בתור, וכן א חוט בקרת רובוט שמנקז את התור וקורא לבקר הרובוט. הפרדה זו מבטיחה ששיחות SDK לרובוט איטיות לעולם לא יחסמו קליטת UDP.
תלות בהתקנה:
pip install python-can piper_sdk # Activate the CAN interface (run once per boot, requires root or dialout group) bash can_activate.sh can0 1000000 # Verify the interface is UP ifconfig can0
הפעל את שרת ה-Teleoperation:
python3 teleoperation_main.py
השרת מתחבר ל 0.0.0.0:8888 (יד ימין) ואופציונלי 0.0.0.0:8889 (יד שמאל). כאשר מגיעה חבילה עם ה valid הדגל מוגדר, שרשור הבקרה קורא robot.set_pose() ו robot.set_gripper(). לִלְחוֹץ Ctrl-C כדי להפעיל עצירת חירום וכיבוי נקי.
מבנה השרת הפשוט המראה כיצד כל שלושת השרשורים מתקשרים:
# teleoperation_main.py (simplified structure) יְבוּא socket, struct, queue, threading, signal, time מִן piper_controller יְבוּא PiperController # swap this to change arms HOST = "0.0.0.0" RIGHT_PORT = 8888 LEFT_PORT = 8889 QUEUE_MAXSIZE = 3 # drop stale frames if robot is slow CONTROL_HZ = 30 # robot command rate pose_queue = queue.Queue(maxsize=QUEUE_MAXSIZE) shutdown_event = threading.Event() def udp_receiver(port: int): """שרשור 1 - קבל נתונים גולמיים של UDP.""" sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((HOST, port)) sock.settimeout(1.0) בזמן שלא shutdown_event.is_set(): לְנַסוֹת: data, _ = sock.recvfrom(256) pose = parse_packet(data) אִם pose ו pose["תָקֵף"]: לְנַסוֹת: pose_queue.put_nowait(pose) אֶלָא queue.Full: pose_queue.get_nowait() # drop oldest frame pose_queue.put_nowait(pose) אֶלָא socket.timeout: לְהַמשִׁיך sock.close() def רובוט_שליטה_לולאת(robot: PiperController): """שרשור 3 - ניקוז תור ורובוט פקודה ב-CONTROL_HZ.""" period = 1.0 / CONTROL_HZ last_pose = None בזמן שלא shutdown_event.is_set(): t0 = time.monotonic() לְנַסוֹת: pose = pose_queue.get(timeout=0.1) אִם pose["לְהַפְסִיק"]: robot.emergency_stop() לְהַמשִׁיך אִם pose["תָקֵף"]: last_pose = pose x, y, z = transform_position(pose["מַצָב"]) roll, pitch, yaw = quat_to_euler(pose["רוֹטַציָה"]) robot.set_pose(x, y, z, roll, pitch, yaw) robot.set_gripper(pose["תפסן"]) # tracking lost — hold last known position (do not send zero) אֶלָא queue.Empty: לַעֲבוֹר time.sleep(max(0, period - (time.monotonic() - t0)))
QUEUE_MAXSIZE של 1 נותן זמן אחזור מינימלי אבל יכול להרגיש קופצני. ערך של 3-5 מחליק את התנועה על פני אובדן מנות, אך מוסיף עד 100 אלפיות השנייה חביון נוסף ב-30 הרץ. מתחילים ב-3 ומכוונים לפי הטעם.
piper_controller.py הדרכה
ה piper_controller.py מודול עוטף את ה-AgileX piper_sdk ספריית פייתון. הוא מיישם את ממשק חמש השיטות המצופה על ידי teleoperation_main.py. החלטות עיצוב מרכזיות:
- CAN דרך USB: זרוע הפייפר מתקשרת דרך אוטובוס CAN. ה-SDK מקבל שם ממשק CAN (
can0) ומחייב את הפעלת הממשק לפני החיבור. - מצב עבדים: יִעוּד
MasterSlaveConfig(0xFC, 0, 0, 0)מכניס את הזרוע למצב עבד כך שהיא מקבלת פקודות מיקום סטרימינג. - הידוק סביבת עבודה: מיקומים מוצמדים לקבועים בחלק העליון של הקובץ לפני כל קריאת SDK - אלה הם השורה האחרונה של ההגנה על התוכנה לפני הקושחה.
- יחידות SDK: המיקום הוא במיקרומטרים (מספר שלם), כיוון במלי מעלות - הכפל את ערכי הצפה מ"מ/מעלות ב-1000 לפני מעבר ל
EndEffectorCtrl.
# piper_controller.py מִן piper_sdk יְבוּא C_PiperInterface יְבוּא math, logging logger = logging.getLogger(__name__) # Piper workspace limits (millimetres) — keep 50 mm inside physical limits X_MIN, X_MAX = -400, 400 Y_MIN, Y_MAX = -400, 400 Z_MIN, Z_MAX = 50, 700 # Maximum joint speed (0–100 %; keep conservative for teleop) SPEED_PERCENT = 25 מַחלָקָה PiperController: """תחליף ל-Drop-in עבור XArmController - אותו ממשק של חמש שיטות.""" def __init__(self, can_interface: str = "יכול 0"): self.can_interface = can_interface self._piper: C_PiperInterface | None = None self.connected = False def לְחַבֵּר(self) -> bool: """פתח את יציאת CAN והפעל מצב עבדים.""" לְנַסוֹת: self._piper = C_PiperInterface( can_name=self.can_interface, judge_flag=False, # allow 3rd-party CAN adapters can_auto_init=True, dh_is_offset=1, # firmware >= V1.6-3 ) self._piper.ConnectPort() self._piper.MasterSlaveConfig(0xFC, 0, 0, 0) # enter slave mode self.connected = True logger.info("פייפר מחובר ב-%s", self.can_interface) לַחֲזוֹר True אֶלָא Exception כְּמוֹ e: logger.error("חיבור הצינור נכשל: %s", e) לַחֲזוֹר False def לְנַתֵק(self): """השבת סרוו וסגור יציאה.""" אִם self._piper: לְנַסוֹת: self._piper.DisableArm(7) # disable all joints אֶלָא Exception: לַעֲבוֹר self.connected = False def set_pose(self, x: float, y: float, z: float, roll: float, pitch: float, yaw: float): """הזז את גורם הקצה ל-(x,y,z) מ"מ במעלות כיוון (גלגול, גובה, סיבוב).""" אִם לֹא self.connected: לַחֲזוֹר x = max(X_MIN, min(X_MAX, x)) y = max(Y_MIN, min(Y_MAX, y)) z = max(Z_MIN, min(Z_MAX, z)) לְנַסוֹת: self._piper.EndEffectorCtrl( int(x * 1000), int(y * 1000), int(z * 1000), int(roll * 1000), int(pitch * 1000), int(yaw * 1000), SPEED_PERCENT, ) אֶלָא Exception כְּמוֹ e: logger.warning("שגיאת set_pose: %s", e) def set_gripper(self, value: float): """הגדר את פתיחות האוחז. 0.0 = סגור לחלוטין, 1.0 = פתוח לחלוטין.""" אִם לֹא self.connected: לַחֲזוֹר GRIPPER_MAX_UM = 70_000 # 70 mm max opening in µm target_um = int(value * GRIPPER_MAX_UM) לְנַסוֹת: self._piper.GripperCtrl(target_um, SPEED_PERCENT, 0x01, 0) אֶלָא Exception כְּמוֹ e: logger.warning("שגיאת set_gripper: %s", e) def עצירת חירום(self): """השבת מיד את כל המפרקים. בטוח להתקשר מכל שרשור.""" אִם self._piper: לְנַסוֹת: self._piper.DisableArm(7) אֶלָא Exception: לַעֲבוֹר
piper.GetPiperFirmwareVersion() ואמת מול מסמכי המפתחים של AgileX לפני פקודה על תנועה כלשהי.
אימות בטיחות ופגישה ראשונה
רשימת בדיקה יבשה (כיבוי):
- הפעל את שרת Python וחבר את Quest 3. צפה ב
transform_positionפלט מודפס למסוף. - החזק את ידך במרכז סביבת העבודה המיועדת. ודא שערכי ה-XYZ המודפסים נמצאים ליד מיקום הבית של הרובוט (כ-0, 0, 300 מ"מ עבור פייפר).
- הזז את היד שלך לכל קצה סביבת עבודה. אשר שהערכים נשארים בגבולות המהודקים ולעולם לא ישתלו ב-Z.
- לחץ על לחצן התפריט ב-Quest כדי להפעיל את עצירת התוכנה האלקטרונית. לְאַשֵׁר
emergency_stop()נקרא והלולאה נעצרת.
מפגש חי ראשון:
- התחל ב
SPEED_PERCENT = 20- זה בערך 40°/s מהירות המפרק המקסימלית. - אפשר כוח סרוו. זז לאט, הישאר ליד עמדת הבית של הזרוע במשך הדקה הראשונה.
- ודא שתנועת היד ותנועת הרובוט נמצאים באותו כיוון. אם פרק כף היד מסתובב לאחור, התאם
rotationOffsetב-90° ± על Y במפקח האחדות. - הרחב בהדרגה את טווח התנועה לאחר אישור מיפוי הכיוון הנכון.
- שמור על עצירת חירום פיזית (ממסר מתח) בהישג יד בכל עת.
מיושמים שני נתיבי עצירה אלקטרונית של תוכנה:
- כפתור תפריט קווסט 3: מגדיר סיביות 1 של בייט הדגלים בכל מנות UDP עוקבות. שרת Python קורא
robot.emergency_stop()מִיָד. - Ctrl-C (SIGINT): מטפל האותות מגדיר את אירוע הכיבוי, שגורם ללולאת הבקרה להתקשר
emergency_stop()ולצאת בצורה נקייה.
valid הדגל בחבילת UDP יורד ל-0. שרת Python מחזיק במיקום האחרון הידוע במקום להצמיד את הזרוע למצב אפס.
ממשק מתאם - יציאה לכל זרוע
דפוס החלפת הבקר מתכלל לכל זרוע עם Python SDK. הדרישה היחידה היא שמחלקת הבקר שלך תטמיע את חמש השיטות האלה בדיוק עם החתימה הזו:
| שִׁיטָה | חֲתִימָה | חוֹזֶה |
|---|---|---|
| לְחַבֵּר() | () → bool | פותח ערוץ תקשורת; מחזירה True על הצלחה |
| לְנַתֵק() | () → אין | משבית את כוח הסרוו וסוגר יציאה או שקע |
| set_pose(x, y, z, roll, pitch, yaw) | (צף×6) → אין | יעד אפקטור קצה קרטזי במ"מ + מעלות; חייב להדק פנימי |
| set_gripper(value) | (צף) → אין | פתיחות מנורמלת 0.0-1.0; מפה ליחידות ספציפיות לזרוע באופן פנימי |
| emergency_stop() | () → אין | חייב להיות בטוח להתקשר מכל שרשור בכל עת |
שלבים להוספת זרוע חדשה:
- לִכתוֹב
myarm_controller.pyיישום חמש השיטות שלמעלה באמצעות SDK של הזרוע שלך. גבולות סביבת עבודה של קוד קשיח ומגבלות מהירות כקבועים ברמת המודול. - בדיקת יחידה בבידוד: התקשר
connect(), אזset_poseעם מיקום בטוח 5 ס"מ מהבית. ודא שהזרוע זזה ומחזירה את המיקום הצפוי. - החלף את הייבוא פנימה
teleoperation_main.py: להחליףfrom piper_controller import PiperControllerעם הבקר החדש שלך. אין צורך בשינויים אחרים. - כייל את הפרמטרים של Unity Inspector (
positionOffset,rotationOffset,scaleFactor) עבור סביבת העבודה של הזרוע החדשה. - אמת את התנהגות עצירה אלקטרונית, מעקב אחר אובדן, ומהדקי סביבת עבודה לפני כל הפעלת מפעיל.
geometry_msgs/PoseStamped נושא בפנים set_pose - שאר הארכיטקטורה נשארת זהה. העיצוב מבוסס התור סופג באופן טבעי את ההשהיה של מחזור פאב/משנה ROS 2.