# Traga o seu próprio hardware

Este tutorial explicará como integrar seu próprio design de robô ao ecossistema LeRobot e permitir que ele acesse todas as nossas ferramentas (coleta de dados, pipelines de controle, treinamento e inferência de políticas).

Para isso, fornecemos a classe base [`Robot`](https://github.com/huggingface/lerobot/blob/main/src/lerobot/robots/robot.py) no LeRobot, que especifica uma interface padrão para a integração de robôs físicos. Vamos ver como implementá-la.

## Pré-requisitos

- Seu próprio robô que expõe uma interface de comunicação (por exemplo, serial, CAN, TCP)
- Uma forma de ler dados de sensores e enviar comandos de motor programaticamente, por exemplo, o SDK ou a API do fabricante, ou sua própria implementação de protocolo.
- LeRobot instalado no seu ambiente. Siga nosso [Guia de Instalação](./installation).

## Escolha seus motores

Se você estiver usando motores Feetech ou Dynamixel, o LeRobot fornece uma interface de barramento integrada, [`SerialMotorsBus`](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py), que controla qualquer uma das famílias: o modelo de cada motor informa qual é. Consulte-a para conhecer sua API. Ela requer o extra `serial-motors`, que instala o rustypot: `pip install 'lerobot[serial-motors]'`, ou `pip install -e ".[serial-motors]"` a partir de um checkout de código-fonte. Para um bom exemplo de como ela pode ser usada, você pode dar uma olhada na nossa própria [implementação de seguidor SO101](https://github.com/huggingface/lerobot/blob/main/src/lerobot/robots/so_follower/so_follower.py)

Use estes se forem compatíveis. Caso contrário, você precisará encontrar ou escrever uma interface em Python (não abordado neste tutorial):

- Encontre um SDK existente em Python (ou use bindings para C/C++)
- Ou implemente um wrapper básico de comunicação (por exemplo, via pyserial, socket ou CANopen)

Você não está sozinho—muitas contribuições da comunidade usam placas ou firmwares personalizados!

Para Feetech e Dynamixel, atualmente suportamos estes servos:

- Séries STS e SMS da Feetech: `sts3215`, `sts3250`, `sm8512bl`
- Série SCS da Feetech: `scs0009`
- Dynamixel série X (protocolo 2.0): `xl330-m077`, `xl330-m288`, `xc330-t181`, `xc330-t288`, `xl430-w250`, `xl430-w250-2`, `xc430-w150`, `xm430-w350`, `xm540-w270`, `xh540-w150`

Os modelos podem compartilhar um barramento, servos STS/SMS e SCS ou XL430 e XL330: cada motor é lido e escrito por meio da definição do próprio modelo, incluindo a ordem dos bytes e a codificação do sinal. As diferenças entre as famílias derivam das mesmas definições: qual valor de modo de operação significa controle de posição (`bus.set_operating_mode("position")`), se o deslocamento de referência (homing offset) é somado à posição ou subtraído dela, e quais registros existem.

Suportar um modelo não significa que todos os auxiliares de calibração funcionem nele: o Feetech SCS0009 não possui nem um modo de operação nem um deslocamento de retorno à origem, portanto `set_operating_mode()` e `set_half_turn_homings()` o rejeitam. A receita de calibração abaixo é para servos que possuem ambos, como o STS; um servo SCS é calibrado apenas pelo seu intervalo.

Se você estiver usando servos Feetech ou Dynamixel que não estão nesta lista, adicione-os ao [rustypot](https://github.com/pollen-robotics/rustypot), que contém as tabelas de controle lidas pelos barramentos: um modelo que compartilha uma tabela de controle existente apenas precisa ter seu número de modelo registrado, enquanto um novo modelo requer uma definição de servo (veja o [guia de contribuição de servos](https://github.com/pollen-robotics/rustypot/blob/v1.13.1/src/servo/README.md)). O LeRobot lê os modelos do pacote Python rustypot instalado, portanto, ele deve incluir o novo modelo: uma liberação do rustypot que o contenha, ou o rustypot compilado a partir do código-fonte.

Nas próximas seções, os exemplos usam motores Feetech. Altere os modelos e adapte-os aos seus motores, se necessário.

## Etapa 1: Subclassifique a interface `Robot`

Primeiro, você precisa especificar a classe de configuração e um identificador de string (`name`) para o seu robô. Se o seu robô tiver necessidades especiais que você gostaria de poder alterar facilmente, elas devem ser incluídas aqui (por exemplo, porta/endereço, baudrate).

Aqui, adicionaremos o nome da porta e uma câmera por padrão para o nosso robô:

```python
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=lambda: {
            "cam_1": OpenCVCameraConfig(
                index_or_path=2,
                fps=30,
                width=480,
                height=640,
            ),
        }
    )
```

[Tutorial de câmeras](./cameras) para entender como detectar e adicionar sua câmera.

A seguir, criaremos nossa classe de robô real, que herda de `Robot`. Esta classe abstrata define um contrato que você deve seguir para que seu robô seja utilizável com as demais ferramentas do LeRobot.

Aqui, criaremos um robô simples de 5-DoF com uma câmera. Ele poderia ser um braço simples, mas observe que a classe abstrata `Robot` não faz suposições sobre o formato do seu robô. Você pode deixar sua imaginação correr solta ao projetar novos robôs!

```python
from typing import Any

from lerobot.cameras import make_cameras_from_configs
from lerobot.motors import Motor, MotorCalibration, MotorNormMode, SerialMotorsBus
from lerobot.robots import Robot

class MyCoolRobot(Robot):
    config_class = MyCoolRobotConfig
    name = "my_cool_robot"

    def __init__(self, config: MyCoolRobotConfig):
        super().__init__(config)
        self.config = config
        self.bus = SerialMotorsBus(
            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)
```

## Etapa 2: Defina os recursos de observação e ação

Essas duas propriedades definem o _contrato de interface_ entre seu robô e as ferramentas que o consomem (como pipelines de coleta de dados ou aprendizado).

> [!WARNING]
> Observe que essas propriedades devem ser chamáveis mesmo que o robô ainda não esteja conectado, portanto, evite depender do estado do hardware em tempo de execução para defini-las.

### `observation_features`

Esta propriedade deve retornar um dicionário descrevendo a estrutura das saídas dos sensores do seu robô. As chaves correspondem ao que `get_observation()` retorna, e os valores descrevem a forma (para arrays/imagens) ou o tipo (para valores simples).

Exemplo para nosso braço de 5-DoF com uma câmera:

```python
@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}
```

Neste caso, as observações consistem em um dicionário simples que armazena a posição de cada motor e uma imagem da câmera.

### `action_features`

Esta propriedade descreve os comandos que seu robô espera via `send_action()`. Novamente, as chaves devem corresponder ao formato de entrada esperado, e os valores definem a forma/tipo de cada comando.

Aqui, usamos simplesmente os mesmos recursos proprioceptivos das juntas (`self._motors_ft`) do que com `observation_features`: a ação enviada será simplesmente a posição de destino para cada motor.

```python
@property
def action_features(self) -> dict:
    return self._motors_ft
```

## Etapa 3: Gerenciar conexão e desconexão

Esses métodos devem lidar com a abertura e o fechamento da comunicação com seu hardware (por exemplo, portas seriais, interfaces CAN, dispositivos USB, câmeras).

### `is_connected`

Esta propriedade deve simplesmente refletir que a comunicação com o hardware do robô foi estabelecida. Quando esta propriedade é `True`, deve ser possível ler e gravar no hardware usando `get_observation()` e `send_action()`.

```python
@property
def is_connected(self) -> bool:
    return self.bus.is_connected and all(cam.is_connected for cam in self.cameras.values())
```

### `connect()`

Este método deve estabelecer a comunicação com o hardware. Além disso, se o seu robô precisar de calibração e não estiver calibrado, ele deve iniciar um procedimento de calibração por padrão. Se o seu robô precisar de alguma configuração específica, esta também deve ser chamada aqui.

```python
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()`

Este método deve encerrar a comunicação com o hardware de forma adequada: liberar quaisquer recursos relacionados (threads ou processos), fechar portas, etc.

Aqui, já tratamos disso em nossas classes `SerialMotorsBus` e `Camera`, então precisamos apenas chamar seus próprios métodos `disconnect()`:

```python
def disconnect(self) -> None:
    self.bus.disconnect()
    for cam in self.cameras.values():
        cam.disconnect()
```

## Etapa 4: Suporte à Calibração e Configuração

O LeRobot suporta o salvamento e o carregamento automáticos de dados de calibração. Isso é útil para deslocamentos de juntas, posições zero ou alinhamento de sensores.

> Observe que, dependendo do seu hardware, isso pode não se aplicar. Se for esse o caso, você pode simplesmente deixar esses métodos como no-ops:

```python
@property
def is_calibrated(self) -> bool:
    return True

def calibrate(self) -> None:
    pass
```

### `is_calibrated`

Isso deve refletir se o seu robô tem a calibração necessária carregada.

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

### `calibrate()`

O objetivo da calibração é duplo:

- Conheça a amplitude de movimento física de cada motor para enviar comandos apenas dentro dessa faixa.
- Normalize as posições brutas dos motores para valores contínuos sensatos (por exemplo, porcentagens, graus) em vez de valores discretos arbitrários dependentes do motor específico usado, que não serão replicados em outros contextos.

Ele deve implementar a lógica de calibração (se relevante) e atualizar o dicionário `self.calibration`. Se você estiver usando motores Feetech ou Dynamixel, o `SerialMotorsBus` já executa a calibração manualmente:

1. `_keep_calibration_file()` oferece a opção de reutilizar o arquivo de calibração deste id. Se você mantiver, ele é gravado nos motores e a calibração termina ali.
2. `record_calibration()` libera os motores e os coloca no modo de operação que você especificar.
3. Ele pede que você segure as articulações no meio de sua amplitude de movimento: essa pose se torna meia volta de cada motor.
4. Ele registra a amplitude de cada articulação conforme você a move, até que você pressione ENTER. Articulações que giram continuamente ficam em `full_turn_motors` e recebem a faixa inteira do codificador; nosso braço de 5-DoF não possui nenhuma.
5. `write_calibration()` grava o resultado nos motores e `_save_calibration()` o salva no arquivo de calibração.

```python
def calibrate(self) -> None:
    if self._keep_calibration_file(self.bus):
        return

    self.calibration = self.bus.record_calibration("position", full_turn_motors=[])
    self.bus.write_calibration(self.calibration)
    self._save_calibration()
    print("Calibration saved to", self.calibration_fpath)
```

### `configure()`

Use isso para configurar qualquer configuração para seu hardware (modos de controle dos servos, ganhos do controlador, etc.). Isso geralmente deve ser executado no momento da conexão e ser idempotente.

```python
def configure(self) -> None:
    with self.bus.torque_disabled():
        self.bus.configure_motors()
        for motor in self.bus.motors:
            self.bus.set_operating_mode("position", motor)
            self.bus.write("P_Coefficient", motor, 16)
            self.bus.write("I_Coefficient", motor, 0)
            self.bus.write("D_Coefficient", motor, 32)
```

## Etapa 5: Implementar a leitura de sensores e o envio de ações

Estas são as funções de tempo de execução mais importantes: o loop central de E/S.

### `get_observation()`

Retorna um dicionário de valores de sensores do robô. Esses valores geralmente incluem estados dos motores, quadros da câmera, vários sensores, etc. No framework LeRobot, essas observações são o que será fornecido a uma política para prever as ações a serem executadas. As chaves e a estrutura do dicionário devem corresponder a `observation_features`.

```python
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()`

Recebe um dicionário que corresponde a `action_features` e o envia para o seu hardware. Você pode adicionar limites de segurança (clipping, suavização) e retornar o que foi efetivamente enviado.

Por simplicidade, não adicionaremos nenhuma modificação das ações em nosso exemplo aqui.

```python
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
```

### Aguardando entre as previsões

O RTC local comum continua utilizável sem uma nova capacidade do robô. O rollout remoto, as pausas de linguagem assíncronas planejadas e a inanição de ação recuperável exigem um contrato de espera com alvo retido. Opte por `supports_position_hold = True` apenas quando atualizar todos os campos de ação por meio de `send_action()` for seguro para o controlador. Normalmente, esses campos são alvos de posição reais. Um controlador pode, em vez disso, optar por um comando retido validado separadamente, como o token de movimento latente do SONIC, quando repeti-lo mantém o controlador embarcado em equilíbrio. A ativação de torque ou um sufixo `.pos` sozinho não estabelece isso; comandos de velocidade/esforço e locomoção permanecem inadequados.

Um robô que pode aguardar sem um comando de reenvio, como um simulador que avança apenas em `send_action()`, define `supports_command_hold = True` e implementa `hold()`. Ele é usado no lugar da retenção de posição.

A implementação compartilhada de rollout atualiza a última ação finita completa retornada por `send_action()`, incluindo o garra e qualquer recorte do driver. Retorne o alvo realmente aplicado, e não a solicitação original, quando o seu driver o modificar. As atualizações subsequentes de observação não substituem esse alvo. Antes que qualquer comando tenha sido aplicado, um instantâneo de posição medida validado o inicializa. O robô pode se estabilizar em direção ao seu alvo retido; esse contrato interrompe o avanço da trajetória em vez de garantir uma parada física instantânea. Valide o suporte de carga, a pegada e o comportamento de atualização de comando no hardware para cada implantação.

Quando o holding está habilitado, os processadores de ações personalizados e as correções de teleoperação devem preservar um comando de posição completo, incluindo o gripper. O driver deve retornar todos os componentes da ação com valores aplicados finitos. Um retorno incompleto/inválido é uma falha de contrato após a aplicação do comando: o rollout é interrompido e o homing é suprimido em vez de reutilizar um alvo anterior. Falhas de comando também suprimem o homing no rollout síncrono.

O rollout remoto verifica automaticamente essa capacidade do driver; não é necessário nenhum flag de CLI adicional para o modo de espera. Falhas genéricas de observação usam o caminho normal de limpeza do rollout. Voltar à posição inicial ainda requer uma observação completa e pode falhar se uma câmera permanecer indisponível.

| Implementação do robô                                    | Suporte à espera de posição-alvo                                                                                                                                                                                                                                            |
| ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| SO followers (SO-100/SO-101), OMX, Koch                 | Suportado.                                                                                                                                                                                                                                                                 |
| HopeJr braço e mão                                     | Suportado.                                                                                                                                                                                                                                                                 |
| Rebot B601                                              | Suportado em todos os modos de braço/presa de posição absoluta existentes.                                                                                                                                                                                                         |
| YAM                                                     | Suportado. Se nenhum comando chegar em `command_timeout_s`, o braço mantém sua posição ou flutua, conforme definido por `idle_mode`.                                                                                                                                                     |
| OpenArm                                                 | Suportado com `use_velocity_and_torque=false`; esquemas de ação mistos são excluídos.                                                                                                                                                                                         |
| BiSo, BiOpenArm, BiRebot B601, BiYAM                    | Suportado quando ambos os braços subjacentes o suportam; seus alvos aplicados mantêm os prefixos esquerdo/direito.                                                                                                                                                                      |
| Reachy2                                                 | Suportado apenas com `with_mobile_base=false`, `use_external_commands=false` e `max_relative_target=null`. Os comandos do mobile-base são velocidades, o modo externo não transmite alvos e o caminho de clipping atualmente retorna os valores originais em vez dos aplicados. |
| LeKiwi (incluindo cliente de rede), EarthRover Mini Plus | Não suportado: as ações incluem velocidades da base móvel, que não podem ser mantidas como alvos de posição.                                                                                                                                                                         |
| Unitree G1                                              | Suportado apenas com `SonicWholeBodyController`: a espera repete o último token de movimento 64-D completo enquanto o decodificador embarcado continua o equilíbrio. A locomoção GR00T/Holosoma e o controle bruto `.q` permanecem sem suporte.                                                      |

Essas declarações refletem o contrato de comandos do driver, não a validação física de cada encarnação ou configuração de controlador. O padrão genérico de `Robot` permanece como falso. As verificações de capacidade não estabelecem compatibilidade de checkpoint, ajuste de controlador, suporte a carga ou integridade do hardware.

## Adicionando um Teleoperador

Para a implementação de dispositivos de teleoperação, também fornecemos a classe base [`Teleoperator`](https://github.com/huggingface/lerobot/blob/main/src/lerobot/teleoperators/teleoperator.py). Esta classe é muito semelhante à classe base `Robot` e também não faz suposições sobre o formato físico.

As principais diferenças estão nas funções de E/S: um teleoperador permite que você produza uma ação via `get_action` e pode receber ações de feedback via `send_feedback`. O feedback pode ser qualquer coisa controlável no dispositivo de teleoperação que possa ajudar a pessoa que o controla a entender as consequências das ações enviadas. Pense em feedback de movimento/força em um braço líder, vibrações em um controle de videogame, por exemplo. Para implementar um teleoperador, você pode seguir este mesmo tutorial e adaptá-lo para esses dois métodos.

## Usando seus próprios dispositivos `LeRobot` 🔌

Você pode estender facilmente o `lerobot` com seu próprio hardware personalizado — seja uma câmera, um robô ou um dispositivo de teleoperação — criando um pacote Python separado e instalável. Se você seguir algumas convenções simples, as ferramentas de linha de comando do `lerobot` (como `lerobot-teleoperate` e `lerobot-record`) **descobrirão e integrarão automaticamente suas criações** sem exigir nenhuma alteração no código-fonte do `lerobot`.

Este guia descreve as convenções que seu plugin deve seguir.

### As 4 Convenções Principais

Para garantir que seu dispositivo personalizado seja detectável, você deve seguir as quatro regras a seguir.

#### 1\. Crie um pacote instalável com um prefixo específico

Seu projeto deve ser um pacote Python padrão e instalável. Crucialmente, o nome do seu pacote (conforme definido em `pyproject.toml` ou `setup.py`) deve começar com um desses prefixos:

- `lerobot_robot_` para um robô.
- `lerobot_camera_` para uma câmera.
- `lerobot_teleoperator_` para um dispositivo de teleoperação.
- `lerobot_strategy_` para uma estratégia `lerobot-rollout` (veja [Traga sua própria estratégia](./inference#bring-your-own-strategy)).

Esse sistema de prefixo é como o `lerobot` encontra automaticamente seu plugin no ambiente Python.

#### 2\. Siga o padrão de nomenclatura `SomethingConfig`/`Something`

A classe de implementação do seu dispositivo deve ser nomeada com base na sua classe de configuração, simplesmente removendo o sufixo `Config`.

- **Classe de configuração:** `MyAwesomeTeleopConfig`
- **Classe de dispositivo:** `MyAwesomeTeleop`

#### 3\. Organize seus arquivos em uma estrutura previsível

A classe de dispositivo (`MyAwesomeTeleop`) deve estar localizada em um módulo previsível em relação à sua classe de configuração (`MyAwesomeTeleopConfig`). O `lerobot` buscará automaticamente nestes locais:

- No **mesmo módulo** que a classe de configuração.
- Em um **submódulo com o nome do dispositivo** (por exemplo, `my_awesome_teleop.py`).

A estrutura recomendada e mais simples é colocá-los em arquivos separados, com nomes claros, dentro do mesmo diretório.

#### 4\. Expor classes em `__init__.py`

O arquivo `__init__.py` do seu pacote deve importar e expor tanto a classe de configuração quanto a classe de dispositivo, tornando-as facilmente acessíveis.

### Juntando tudo: um exemplo completo

Vamos criar um novo teleoperador chamado `my_awesome_teleop`.

#### Estrutura de Diretórios

É assim que a pasta do projeto deve ficar. O nome do pacote, `lerobot_teleoperator_my_awesome_teleop`, segue a **Convenção \#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
```

#### Conteúdo do Arquivo

- **`config_my_awesome_teleop.py`**: Define a classe de configuração. Observe o sufixo `Config` (**Convenção \#2**).

  ```python
  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`**: Implementa o dispositivo. O nome da classe `MyAwesomeTeleop` corresponde ao nome da classe de configuração (**Convenção \#2**). Essa estrutura de arquivo segue a **Convenção \#3**.

  ```python
  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`**: Expõe as classes principais (**Convenção \#4**).

  ```python
  from .config_my_awesome_teleop import MyAwesomeTeleopConfig
  from .my_awesome_teleop import MyAwesomeTeleop
  ```

### Instalação e Uso

1.  **Instale seu novo plugin no seu ambiente Python.** Você pode instalar seu pacote de plugin local usando o modo editável do `pip` ou a partir do PyPi.

    ```bash
    # 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.  **Use-o diretamente da linha de comando.** Agora, você pode usar seu dispositivo personalizado referenciando seu tipo.

    ```bash
    # Add the other arguments you need (robot type, ports, ids, ...)
    lerobot-teleoperate --teleop.type=my_awesome_teleop ...
    ```

And that's it\! Your custom device is now fully integrated.

### Procurando um exemplo?

A comunidade mantém dezenas de plugins construídos dessa forma. Navegue por [Robôs e Teleoperadores de Terceiros](./third_party_robots) e [Câmeras e Sensores de Terceiros](./third_party_sensors) para encontrar um próximo ao seu hardware, ou comece por estes dois pequenos pacotes:

- https://github.com/SpesRobotics/lerobot-robot-xarm
- https://github.com/SpesRobotics/lerobot-teleoperator-teleop

## Conclusão

Quando sua classe de robô estiver completa, você pode aproveitar o ecossistema do LeRobot:

- Controle seu robô com os teleoperadores disponíveis ou integre diretamente seu dispositivo de teleoperação
- Registre os dados de treinamento e visualize-os
- Integre-o em pipelines de aprendizado por reforço ou aprendizado por imitação

Depois que seu plugin for publicado, abra um PR adicionando-o a [Robôs e Teleoperadores de Terceiros](./third_party_robots) ou [Câmeras e Sensores de Terceiros](./third_party_sensors) para que outros usuários possam encontrá-lo.

Não hesite em procurar ajuda da comunidade em nosso [Discord](https://discord.gg/s3KuuzsPFb) 🤗

