LeRobot 文件
使用自備硬體 (Bring Your Own Hardware)
並獲得增強的文件體驗
開始使用
自備硬體
本教學將說明如何將您自己的機器人設計整合到 LeRobot 生態系統中,並使其能夠使用我們所有的工具(資料收集、控制管道、策略訓練與推論)。
為此,我們在 LeRobot 中提供了 Robot 基礎類別,它為實體機器人整合指定了標準介面。讓我們看看如何實作它。
先修條件
- 您自己的機器人,且該機器人公開了通訊介面(例如:串列埠、CAN、TCP)
- 一種以程式化方式讀取感測器資料並發送馬達指令的方法,例如製造商的 SDK 或 API,或是您自行實作的通訊協定。
- 在您的環境中安裝 LeRobot。請參閱我們的安裝指南。
選擇您的馬達
如果您使用 Feetech 或 Dynamixel 馬達,LeRobot 提供了內建的匯流排介面
FeetechMotorsBus– 用於控制 Feetech 伺服馬達DynamixelMotorsBus– 用於控制 Dynamixel 伺服馬達
請參考 MotorsBus 抽象類別以了解其 API。如需如何使用它的良好範例,您可以查看我們自己的 SO101 跟隨者實作
如果相容,請使用這些介面。否則,您需要找到或編寫一個 Python 介面(本教學不包含此內容)
- 找到現有的 Python SDK(或使用 C/C++ 的繫結)
- 或者實作一個基礎通訊封裝(例如透過 pyserial、socket 或 CANopen)
您並不孤單——許多社群貢獻都使用了自訂板卡或韌體!
對於 Feetech 和 Dynamixel,我們目前支援這些伺服馬達: - Feetech: - STS 與 SMS 系列(協定 0):sts3215, sts3250, sm8512bl - SCS 系列(協定 1):scs0009 - Dynamixel(僅限協定 2.0):xl330-m077, xl330-m288, xl430-w250, xm430-w350, xm540-w270, xc430-w150
如果您使用的 Feetech 或 Dynamixel 伺服馬達不在列表中,您可以將其添加到 Feetech 表格 或 Dynamixel 表格 中。根據型號的不同,這需要您添加特定型號的資訊。不過在大多數情況下,應該不需要太多額外工作。
在接下來的章節中,我們將使用 FeetechMotorsBus 作為範例的馬達介面。如有必要,請根據您的馬達進行替換與調整。
步驟 1:繼承 Robot 介面
您首先需要為您的機器人指定設定類別與字串識別碼(name)。如果您的機器人有需要輕鬆變更的特殊需求,都應該放在這裡(例如:連接埠/位址、包率)。
這裡我們將預設添加連接埠名稱和一台攝影機到我們的機器人中
from dataclasses import dataclass, field
from lerobot.cameras import CameraConfig
from lerobot.cameras.opencv import OpenCVCameraConfig
from lerobot.robots import RobotConfig
@RobotConfig.register_subclass("my_cool_robot")
@dataclass
class MyCoolRobotConfig(RobotConfig):
port: str
cameras: dict[str, CameraConfig] = field(
default_factory={
"cam_1": OpenCVCameraConfig(
index_or_path=2,
fps=30,
width=480,
height=640,
),
}
)攝影機教學,以了解如何偵測並添加您的攝影機。
接下來,我們將建立繼承自 Robot 的實際機器人類別。這個抽象類別定義了一個合約,您必須遵守該合約,機器人才能與 LeRobot 的其他工具搭配使用。
這裡我們將建立一個簡單的 5 自由度 (DoF) 機器人,並配備一台攝影機。它可以是一個簡單的機械臂,但請注意,Robot 抽象類別對您機器人的外形因素沒有任何假設。設計新機器人時,您可以發揮無限想像力!
from lerobot.cameras import make_cameras_from_configs
from lerobot.motors import Motor, MotorNormMode
from lerobot.motors.feetech import FeetechMotorsBus
from lerobot.robots import Robot
class MyCoolRobot(Robot):
config_class = MyCoolRobotConfig
name = "my_cool_robot"
def __init__(self, config: MyCoolRobotConfig):
super().__init__(config)
self.bus = FeetechMotorsBus(
port=self.config.port,
motors={
"joint_1": Motor(1, "sts3250", MotorNormMode.RANGE_M100_100),
"joint_2": Motor(2, "sts3215", MotorNormMode.RANGE_M100_100),
"joint_3": Motor(3, "sts3215", MotorNormMode.RANGE_M100_100),
"joint_4": Motor(4, "sts3215", MotorNormMode.RANGE_M100_100),
"joint_5": Motor(5, "sts3215", MotorNormMode.RANGE_M100_100),
},
calibration=self.calibration,
)
self.cameras = make_cameras_from_configs(config.cameras)步驟 2:定義觀測值與動作特徵
這兩個屬性定義了機器人與使用它的工具(例如資料收集或學習管道)之間的介面合約。
請注意,即使機器人尚未連線,這些屬性也必須是可呼叫的,因此請避免依賴執行時的硬體狀態來定義它們。
observation_features
此屬性應返回一個字典,描述機器人感測器輸出的結構。鍵(key)需與 get_observation() 的回傳值匹配,值(value)則描述形狀(針對陣列/影像)或類型(針對簡單值)。
以我們的 5 自由度機械臂(含一台攝影機)為例
@property
def _motors_ft(self) -> dict[str, type]:
return {
"joint_1.pos": float,
"joint_2.pos": float,
"joint_3.pos": float,
"joint_4.pos": float,
"joint_5.pos": float,
}
@property
def _cameras_ft(self) -> dict[str, tuple]:
return {
cam: (self.cameras[cam].height, self.cameras[cam].width, 3) for cam in self.cameras
}
@property
def observation_features(self) -> dict:
return {**self._motors_ft, **self._cameras_ft}在此情況下,觀測值包含一個簡單的字典,儲存每個馬達的位置與一張攝影機影像。
action_features
此屬性描述了您的機器人透過 send_action() 所期望的指令。同樣地,鍵必須與預期的輸入格式相符,而值定義了每個指令的形狀/類型。
這裡,我們簡單地使用與 observation_features 相同的關節本體感覺特徵(self._motors_ft):發送的動作將單純是每個馬達的目標位置。
def action_features(self) -> dict:
return self._motors_ft步驟 3:處理連線與中斷連線
這些方法應處理與您的硬體(例如串列埠、CAN 介面、USB 裝置、攝影機)之間通訊的開啟與關閉。
is_connected
此屬性應單純反映與機器人硬體的通訊是否已建立。當此屬性為 True 時,應可使用 get_observation() 和 send_action() 對硬體進行讀寫。
@property
def is_connected(self) -> bool:
return self.bus.is_connected and all(cam.is_connected for cam in self.cameras.values())connect()
此方法應建立與硬體的通訊。此外,如果您的機器人需要校準且尚未校準,它應預設啟動校準程序。若您的機器人需要特定設定,也應在此處呼叫。
def connect(self, calibrate: bool = True) -> None:
self.bus.connect()
if not self.is_calibrated and calibrate:
self.calibrate()
for cam in self.cameras.values():
cam.connect()
self.configure()disconnect()
此方法應優雅地終止與硬體的通訊:釋放任何相關資源(執行緒或處理序)、關閉連接埠等。
在此,我們已在 MotorsBus 和 Camera 類別中處理了此作業,因此只需呼叫它們各自的 disconnect() 方法即可。
def disconnect(self) -> None:
self.bus.disconnect()
for cam in self.cameras.values():
cam.disconnect()步驟 4:支援校準與設定
LeRobot 支援自動儲存與載入校準資料。這對於關節偏移量、零位或感測器對齊非常有用。
請注意,根據您的硬體,這可能不適用。如果是這種情況,您可以直接將這些方法留空即可。
@property
def is_calibrated(self) -> bool:
return True
def calibrate(self) -> None:
passis_calibrated
此屬性應反映您的機器人是否已載入所需的校準資料。
@property
def is_calibrated(self) -> bool:
return self.bus.is_calibratedcalibrate()
校準的目標有二:
- 了解每個馬達的物理運動範圍,以便僅在該範圍內發送指令。
- 將原始馬達位置正規化為合理的連續值(例如百分比、度數),而不是依賴特定馬達的任意離散值,這些值在其他地方無法複製。
它應實作校準邏輯(如果適用)並更新 self.calibration 字典。如果您使用 Feetech 或 Dynamixel 馬達,我們的匯流排介面已經包含了有助於此的方法。
def calibrate(self) -> None:
self.bus.disable_torque()
for motor in self.bus.motors:
self.bus.write("Operating_Mode", motor, OperatingMode.POSITION.value)
input(f"Move {self} to the middle of its range of motion and press ENTER....")
homing_offsets = self.bus.set_half_turn_homings()
print(
"Move all joints sequentially through their entire ranges "
"of motion.\nRecording positions. Press ENTER to stop..."
)
range_mins, range_maxes = self.bus.record_ranges_of_motion()
self.calibration = {}
for motor, m in self.bus.motors.items():
self.calibration[motor] = MotorCalibration(
id=m.id,
drive_mode=0,
homing_offset=homing_offsets[motor],
range_min=range_mins[motor],
range_max=range_maxes[motor],
)
self.bus.write_calibration(self.calibration)
self._save_calibration()
print("Calibration saved to", self.calibration_fpath)configure()
使用此方法為您的硬體設定任何組態(伺服控制模式、控制器增益等)。此步驟通常應在連線時執行,且應具備冪等性。
def configure(self) -> None:
with self.bus.torque_disabled():
self.bus.configure_motors()
for motor in self.bus.motors:
self.bus.write("Operating_Mode", motor, OperatingMode.POSITION.value)
self.bus.write("P_Coefficient", motor, 16)
self.bus.write("I_Coefficient", motor, 0)
self.bus.write("D_Coefficient", motor, 32)步驟 5:實作感測器讀取與動作發送
這些是最重要的執行時函式:核心 I/O 迴圈。
get_observation()
從機器人返回一個感測器值字典。這些通常包括馬達狀態、攝影機影格、各種感測器等。在 LeRobot 框架中,這些觀測值將被餵入策略中,以預測要採取的動作。字典的鍵與結構必須與 observation_features 相符。
def get_observation(self) -> dict[str, Any]:
if not self.is_connected:
raise ConnectionError(f"{self} is not connected.")
# Read arm position
obs_dict = self.bus.sync_read("Present_Position")
obs_dict = {f"{motor}.pos": val for motor, val in obs_dict.items()}
# Capture images from cameras
for cam_key, cam in self.cameras.items():
obs_dict[cam_key] = cam.async_read()
return obs_dictsend_action()
接收一個與 action_features 相符的字典,並將其發送至您的硬體。您可以新增安全限制(截斷、平滑處理)並返回實際發送的內容。
為了簡單起見,我們在此範例中不會對動作進行任何修改。
def send_action(self, action: dict[str, Any]) -> dict[str, Any]:
goal_pos = {key.removesuffix(".pos"): val for key, val in action.items()}
# Send goal position to the arm
self.bus.sync_write("Goal_Position", goal_pos)
return action添加遠端操作裝置 (Teleoperator)
為了實作遠端操作裝置,我們也提供了 Teleoperator 基礎類別。這個類別與 Robot 基礎類別非常相似,同樣對外形因素沒有任何假設。
主要差異在於 I/O 函式:遠端操作裝置允許您透過 get_action 產生動作,並可透過 send_feedback 接收回饋動作。回饋可以是遠端操作裝置上任何可控制的內容,能幫助操作人員理解所發送動作的結果。例如領導機械臂上的運動/力回饋、遊戲手把上的振動等。要實作遠端操作裝置,您可以按照本教學進行操作,並針對這兩個方法進行調整。
使用您自己的 LeRobot 裝置 🔌
您可以透過建立一個獨立、可安裝的 Python 套件,輕鬆地使用自訂硬體(無論是攝影機、機器人還是遠端操作裝置)來擴充 lerobot。如果您遵循幾個簡單的慣例,lerobot 命令列工具(例如 lerobot-teleop 和 lerobot-record)將會自動發現並整合您的創作,而無需對 lerobot 原始碼進行任何更改。
本指南概述了您的外掛程式必須遵守的慣例。
4 大核心慣例
為確保您的自訂裝置可被發現,您必須遵守以下四項規則。
1. 建立具特定字首的可安裝套件
您的專案必須是一個標準、可安裝的 Python 套件。至關重要的是,您的套件名稱(定義於 pyproject.toml 或 setup.py 中)必須以以下其中一個字首開頭:
lerobot_robot_(適用於機器人)lerobot_camera_(適用於攝影機)lerobot_teleoperator_(適用於遠端操作裝置)
此字首系統是 lerobot 自動在 Python 環境中尋找您外掛程式的方式。
2. 遵循 SomethingConfig / Something 命名模式
您裝置的實作類別命名必須參考其設定類別,只需移除 Config 字尾即可。
- 設定類別 (Config Class):
MyAwesomeTeleopConfig - 裝置類別 (Device Class):
MyAwesomeTeleop
3. 將您的檔案放置於可預測的結構中
裝置類別 (MyAwesomeTeleop) 必須位於相對於其設定類別 (MyAwesomeTeleopConfig) 的可預測模組中。lerobot 將自動搜尋以下位置:
- 在與設定類別相同的模組中。
- 在以裝置命名的子模組中(例如:
my_awesome_teleop.py)。
建議且最簡單的結構是將它們放在同一個目錄下,分別命名清晰的檔案中。
4. 在 __init__.py 中公開類別
您套件的 __init__.py 檔案應該匯入並公開設定類別與裝置類別,使其易於存取。
綜合範例:一個完整的例子
讓我們建立一個名為 my_awesome_teleop 的新遠端操作裝置。
目錄結構
以下是專案資料夾結構的樣子。套件名稱 lerobot_teleoperator_my_awesome_teleop 遵循慣例 #1。
lerobot_teleoperator_my_awesome_teleop/
├── pyproject.toml # (or setup.py) lists lerobot as a dependency
└── lerobot_teleoperator_my_awesome_teleop/
├── __init__.py
├── config_my_awesome_teleop.py
└── my_awesome_teleop.py檔案內容
config_my_awesome_teleop.py:定義設定類別。請注意Config字尾(慣例 #2)。from dataclasses import dataclass from lerobot.teleoperators.config import TeleoperatorConfig @TeleoperatorConfig.register_subclass("my_awesome_teleop") @dataclass class MyAwesomeTeleopConfig(TeleoperatorConfig): # Your configuration fields go here port: str = "192.168.1.1"my_awesome_teleop.py:實作裝置。類別名稱MyAwesomeTeleop與其設定類別名稱相符(慣例 #2)。此檔案結構遵循慣例 #3。from lerobot.teleoperators.teleoperator import Teleoperator from .config_my_awesome_teleop import MyAwesomeTeleopConfig class MyAwesomeTeleop(Teleoperator): config_class = MyAwesomeTeleopConfig name = "my_awesome_teleop" def __init__(self, config: MyAwesomeTeleopConfig): super().__init__(config) self.config = config # Your device logic (e.g., connect) goes here__init__.py:公開關鍵類別(慣例 #4)。from .config_my_awesome_teleop import MyAwesomeTeleopConfig from .my_awesome_teleop import MyAwesomeTeleop
安裝與使用
在您的 Python 環境中安裝您的新外掛程式。 您可以使用
pip的可編輯模式或從 PyPi 安裝您的本地外掛套件。# Locally # Navigate to your plugin's root directory and install it cd lerobot_teleoperator_my_awesome_teleop pip install -e . # From PyPi pip install lerobot_teleoperator_my_awesome_teleop直接從命令列使用。 現在,您可以透過引用其類型來使用您的自訂裝置。
lerobot-teleoperate --teleop.type=my_awesome_teleop \ # other arguments
就是這樣!您的自訂裝置現在已完全整合。
尋找範例?
請查看這些來自社群的兩個套件:
- https://github.com/SpesRobotics/lerobot-robot-xarm
- https://github.com/SpesRobotics/lerobot-teleoperator-teleop
總結
一旦您的機器人類別完成,您就可以充分利用 LeRobot 生態系統:
- 使用現有的遠端操作裝置控制您的機器人,或直接整合您自己的遠端操作裝置
- 記錄訓練資料並將其視覺化
- 將其整合到強化學習 (RL) 或模仿學習管道中
請隨時在我們的 Discord 上向社群尋求協助 🤗
在 GitHub 上更新