LeRobot 文件

使用自備硬體 (Bring Your Own Hardware)

Hugging Face's logo
加入 Hugging Face 社群

並獲得增強的文件體驗

開始使用

自備硬體

本教學將說明如何將您自己的機器人設計整合到 LeRobot 生態系統中,並使其能夠使用我們所有的工具(資料收集、控制管道、策略訓練與推論)。

為此,我們在 LeRobot 中提供了 Robot 基礎類別,它為實體機器人整合指定了標準介面。讓我們看看如何實作它。

先修條件

  • 您自己的機器人,且該機器人公開了通訊介面(例如:串列埠、CAN、TCP)
  • 一種以程式化方式讀取感測器資料並發送馬達指令的方法,例如製造商的 SDK 或 API,或是您自行實作的通訊協定。
  • 在您的環境中安裝 LeRobot。請參閱我們的安裝指南

選擇您的馬達

如果您使用 Feetech 或 Dynamixel 馬達,LeRobot 提供了內建的匯流排介面

請參考 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()

此方法應優雅地終止與硬體的通訊:釋放任何相關資源(執行緒或處理序)、關閉連接埠等。

在此,我們已在 MotorsBusCamera 類別中處理了此作業,因此只需呼叫它們各自的 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

此屬性應反映您的機器人是否已載入所需的校準資料。

@property
def is_calibrated(self) -> bool:
    return self.bus.is_calibrated

calibrate()

校準的目標有二:

  • 了解每個馬達的物理運動範圍,以便僅在該範圍內發送指令。
  • 將原始馬達位置正規化為合理的連續值(例如百分比、度數),而不是依賴特定馬達的任意離散值,這些值在其他地方無法複製。

它應實作校準邏輯(如果適用)並更新 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_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)

為了實作遠端操作裝置,我們也提供了 Teleoperator 基礎類別。這個類別與 Robot 基礎類別非常相似,同樣對外形因素沒有任何假設。

主要差異在於 I/O 函式:遠端操作裝置允許您透過 get_action 產生動作,並可透過 send_feedback 接收回饋動作。回饋可以是遠端操作裝置上任何可控制的內容,能幫助操作人員理解所發送動作的結果。例如領導機械臂上的運動/力回饋、遊戲手把上的振動等。要實作遠端操作裝置,您可以按照本教學進行操作,並針對這兩個方法進行調整。

使用您自己的 LeRobot 裝置 🔌

您可以透過建立一個獨立、可安裝的 Python 套件,輕鬆地使用自訂硬體(無論是攝影機、機器人還是遠端操作裝置)來擴充 lerobot。如果您遵循幾個簡單的慣例,lerobot 命令列工具(例如 lerobot-teleoplerobot-record)將會自動發現並整合您的創作,而無需對 lerobot 原始碼進行任何更改。

本指南概述了您的外掛程式必須遵守的慣例。

4 大核心慣例

為確保您的自訂裝置可被發現,您必須遵守以下四項規則。

1. 建立具特定字首的可安裝套件

您的專案必須是一個標準、可安裝的 Python 套件。至關重要的是,您的套件名稱(定義於 pyproject.tomlsetup.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

安裝與使用

  1. 在您的 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
  2. 直接從命令列使用。 現在,您可以透過引用其類型來使用您的自訂裝置。

    lerobot-teleoperate --teleop.type=my_awesome_teleop \
    # other arguments

就是這樣!您的自訂裝置現在已完全整合。

尋找範例?

請查看這些來自社群的兩個套件:

總結

一旦您的機器人類別完成,您就可以充分利用 LeRobot 生態系統:

  • 使用現有的遠端操作裝置控制您的機器人,或直接整合您自己的遠端操作裝置
  • 記錄訓練資料並將其視覺化
  • 將其整合到強化學習 (RL) 或模仿學習管道中

請隨時在我們的 Discord 上向社群尋求協助 🤗

在 GitHub 上更新

© . This site is unofficial and not affiliated with Hugging Face, Inc.