LeRobot 文件
自帶硬體
並獲得增強的文件體驗
開始使用
自帶硬體
本教程將解釋如何將您自己的機器人設計整合到 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 自由度機器人。它可以是一個簡單的機械臂,但請注意,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
此屬性應返回一個描述機器人感測器輸出結構的字典。鍵與 get_observation()
返回的鍵相匹配,值則描述形狀(對於陣列/影像)或型別(對於簡單值)。
我們帶一個攝像頭的 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:
> pass
> ```
### `is_calibrated`
This should reflect whether your robot has the required calibration loaded.
@property def is_calibrated(self) -> bool: return self.bus.is_calibrated
### `calibrate()`
The goal of the calibration is twofold:
- Know the physical range of motion of each motors in order to only send commands within this range.
- Normalize raw motors positions to sensible continuous values (e.g. percentages, degrees) instead of arbitrary discrete value dependant on the specific motor used that will not replicate elsewhere.
It should implement the logic for calibration (if relevant) and update the `self.calibration` dictionary. If you are using Feetech or Dynamixel motors, our bus interfaces already include methods to help with this.
<!-- prettier-ignore-start -->
```python
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_dict
send_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
基類。這個類與 Robot
基類非常相似,也不對形態做任何假設。
主要區別在於 I/O 函式:遙控操作器允許您透過 get_action
產生動作,並可以透過 send_feedback
接收反饋動作。反饋可以是遙控操作裝置上任何可控的東西,可以幫助控制者理解所傳送動作的後果。例如,主控臂上的運動/力反饋,遊戲手柄控制器上的振動。要實現一個遙控操作器,您可以遵循本教程併為這兩個方法進行調整。
總結
一旦您的機器人課程完成,您就可以利用 LeRobot 生態系統
- 使用可用的遙操作器控制您的機器人,或直接整合您的遙操作裝置
- 記錄訓練資料並進行視覺化
- 將其整合到強化學習或模仿學習管道中
請隨時在我們的 Discord 上向社群尋求幫助 🤗
< > 在 GitHub 上更新