-
Notifications
You must be signed in to change notification settings - Fork 0
Record
Versão: Hacker Edition V9 + Voice Control
Base:lerobot.scripts.lerobot_recordcom 5 injeções de monkey-patch
Este script é um wrapper sobre o lerobot_record oficial que adiciona cinco camadas de funcionalidade via monkey-patch em tempo de importação, sem tocar no código do LeRobot:
| Injeção | O que faz |
|---|---|
| 1 — Roubo de Pressão | Intercepta get_observation() e captura os dados dos sensores táteis das mãos |
| 2 — Planta do Parquet | Reescreve o schema do dataset para separar pressão em colunas próprias e configurar o canal de depth |
| 3 — Contrabando de Frame | Injeta os dados de pressão de volta no pacote de cada frame gravado |
| 4 — Voz + Teclado | Habilita comandos de voz (pt-BR) e atalhos de teclado para controlar a gravação |
| 5 — Congelamento Motor | Permite pausar/despausar o robô na pose atual sem interromper a gravação |
python init_lerobot_record_v2.py --config_path=config/g1_record.yaml [OPÇÕES]O argumento --config_path é obrigatório. Todos os outros parâmetros do lerobot_record original (número de episódios, nome do dataset, etc.) são passados diretamente via YAML ou pela linha de comando.
| Flag | Descrição |
|---|---|
--sim ou --simulation=true
|
Força modo simulação. Internamente converte para --robot.is_simulation=true e --teleop.is_simulation=true antes de passar ao LeRobot. |
-h / --help
|
Exibe a documentação do LeRobot original. |
Alvo: UnitreeG1Dex3.get_observation
O driver do G1 retorna os dados de pressão das mãos como chaves left_hand_pressure e right_hand_pressure dentro do dict de observação. O LeRobot padrão não sabe o que fazer com elas e as perderia.
O que o patch faz:
- Chama o
get_observationoriginal - Detecta se as chaves de pressão estão presentes no resultado
- Remove essas chaves do dict (para não quebrar o pipeline do LeRobot)
- Salva os arrays
[33]num buffer global (buffer_pressao) O buffer global age como uma ponte entre o loop de observação (Thread do robô) e o empacotador de frames (Injeção 3).
A cada 50 frames, o patch imprime o status dos sensores no terminal para diagnóstico (comentado por padrão — descomente as linhas #print para ativar).
Alvo: lerobot.datasets.utils.hw_to_dataset_features
O LeRobot define o schema do arquivo Parquet (tipos e formas de cada coluna) a partir de um dict de features. Sem intervenção, a pressão não existiria como coluna separada.
O que o patch faz:
-
Remove os campos de pressão de
observation.state.names(caso o driver os tenha incluído lá erroneamente como parte do estado) - Cria duas novas colunas no schema:
"observation.left_hand_pressure" → dtype: float32, shape: (33,), 33 nomes
"observation.right_hand_pressure" → dtype: float32, shape: (33,), 33 nomes-
Configura o canal de depth (
observation.images.head_camera_depth) com os metadados de vídeo necessários:
{
"video.fps": 30,
"video.codec": "h264",
"video.pix_fmt": "yuv420p",
"video.channels": 3,
"has_audio": False,
"video.is_depth_map": True # ← flag especial para o depth
}Esse schema é fixo por dataset — a função é chamada apenas uma vez na criação.
Alvo: lerobot.datasets.utils.build_dataset_frame
A cada frame gravado, o LeRobot chama build_dataset_frame para montar o dict que vai para o Parquet. Sem esse patch, as colunas de pressão criadas na Injeção 2 ficariam vazias ou causariam erro.
O que o patch faz:
Antes de chamar a função original, injeta os 66 valores de pressão (33 esquerda + 33 direita) como chaves individuais no obs_dict:
obs_dict["left_hand_pressure_0"] = float(lp[0])
obs_dict["left_hand_pressure_1"] = float(lp[1])
# ... até left_hand_pressure_32
obs_dict["right_hand_pressure_0"] = float(rp[0])
# ... até right_hand_pressure_32O valor usado é sempre o do buffer global — ou seja, o dado mais recente capturado pela Injeção 1 naquele instante.
Esta injeção tem dois componentes que rodam em paralelo:
Roda em daemon thread. Requer pip install pynput.
| Tecla | Ação |
|---|---|
↓ (seta para baixo) |
Congela o robô na pose atual |
↑ (seta para cima) |
Destrava o robô |
Espaço ou P
|
Alterna entre congelado e destravado |
Roda em daemon thread. Requer pip install SpeechRecognition e acesso à internet (API Google pt-BR).
Aguarda 3 segundos após o início para deixar os motores e câmeras estabilizarem.
| Comando dito | Ação |
|---|---|
"salvar", "gravar", "próximo"
|
Dispara exit_early=True → salva o episódio e avança para o próximo |
"errei", "reboot", "voltar"
|
Dispara rerecord_episode=True → descarta o episódio atual e regrava |
"pausar", "congelar", "travar"
|
Congela o robô (ativa Injeção 5) |
"continuar", "destravar", "play"
|
Destrava o robô |
"finalizar", "sair", "fechar"
|
Dispara stop_recording=True → encerra toda a gravação |
Os eventos de voz usam o mesmo dict global_events que o LeRobot usa internamente para gerenciar o fluxo do loop de gravação — garantindo que salvar/descartar via voz é idêntico a pressionar as teclas originais do LeRobot.
Atenção: o reconhecimento de voz depende de conexão com a internet. Em ambientes sem internet, a thread falha silenciosamente e o controle por teclado continua funcionando.
Alvo: UnitreeG1Dex3.send_action
Permite pausar o robô fisicamente enquanto a gravação continua ativa — útil para reposicionar objetos de cena ou fazer uma pausa entre tentativas sem perder o episódio.
Lógica:
robot_paused = False → MODO NORMAL
└─ salva a action atual em frozen_action
└─ envia a action normalmente para os motores
robot_paused = True → MODO ESTÁTUA
└─ ignora completamente a action recebida do VR/teleop
└─ reenvia frozen_action (última pose) para manter os motores travados
Cuidado ao destravar: se o operador mover o controle VR enquanto o robô está congelado, o robô vai pular para a nova pose ao ser destravado. Recomendado alinhar o controle com a pose atual antes de pressionar
↑ou dizer "continuar".
Início do script
│
├─ Injeções 1–5 instaladas (monkey-patch)
│
▼
lerobot_record.main() inicia
│
▼
Loop de gravação (por episódio)
│
├─ robot.get_observation()
│ └─ [Injeção 1] captura pressão → buffer_pressao
│
├─ Operador teleopera o robô com VR
│ └─ [Injeção 5] se pausado, mantém frozen_action nos motores
│
├─ build_dataset_frame()
│ └─ [Injeção 3] injeta buffer_pressao no frame
│
├─ [Controle do episódio via voz/teclado — Injeção 4]
│ ├─ "salvar" → exit_early=True → próximo episódio
│ ├─ "errei" → rerecord_episode → regrava o mesmo
│ └─ "finalizar" → stop_recording → encerra tudo
│
└─ Parquet escrito com schema [Injeção 2]
├─ observation.state (juntas)
├─ observation.left_hand_pressure (33 floats)
├─ observation.right_hand_pressure (33 floats)
└─ observation.images.head_camera_depth (vídeo h264)
Além das dependências do LeRobot:
| Pacote | Uso | Obrigatório |
|---|---|---|
pynput |
Listener de teclado (setas, espaço, P) | Não — degrada graciosamente |
SpeechRecognition |
Reconhecimento de voz via Google | Não — degrada graciosamente |
pyaudio |
Backend de microfone para SpeechRecognition | Apenas se usar voz |
pip install pynput SpeechRecognition pyaudio| Sintoma | Causa | Solução |
|---|---|---|
left_hand_pressure ausente no Parquet |
Driver não enviou dados de pressão | Descomentar os #print na Injeção 1 e checar DRIVER NÃO ENVIOU
|
| Voz não responde |
SpeechRecognition não instalado ou sem internet |
Verificar pip install e conexão |
| Teclado não responde |
pynput não instalado |
pip install pynput |
| Robô pula ao destravar | Controle VR em posição diferente da pose congelada | Alinhar o controle antes de destravar |
| Schema incompatível ao reabrir dataset | Mudança nas chaves de pressão entre gravações | Recriar o dataset ou garantir que o mesmo script foi usado em todas as sessões |
KeyError: '_act_d_depth_feat' |
depth não foi configurado no schema | Verificar se head_camera_depth está presente nas features do YAML |