Módulo 4.2: Sim-to-Real Transfer
🤖 Módulo 4.2: Sim-to-Real Transfer
Da simulação perfeita para o mundo caótico: domine a arte de transferir IA para robôs reais
📋 Informações do Módulo
| Atributo | Detalhes |
|---|---|
| Duração estimada | 10-15 horas |
| Nível | Profissional |
| Pré-requisitos | Nível 3 + Módulo 4.1 |
| Tipo | Técnico + Prático |
🎯 Objetivos de Aprendizado
Ao completar este módulo, você será capaz de:
- ✅ Entender o “domain gap” entre simulação e realidade
- ✅ Aplicar técnicas de domain randomization
- ✅ Realizar system identification de robôs reais
- ✅ Fine-tunar políticas treinadas em simulação
- ✅ Fazer deploy seguro de IA em hardware
- ✅ Debugar e troubleshoot problemas de Sim2Real
- ✅ Monitorar robôs em produção
📚 Conteúdo Teórico
1. O Desafio do Sim-to-Real
1.1 Por que Políticas Falham no Mundo Real?
Você treinou seu robô perfeitamente no Isaac Sim. Ele caminha, desvia de obstáculos, pega objetos. Taxa de sucesso: 95%.
Aí você transfere para o robô real e…
❌ Ele cai no primeiro passo ❌ Sensores retornam dados ruidosos ❌ Latência de rede atrasa comandos ❌ Bateria fraca muda dinâmica ❌ Piso escorregadio vs simulado
Taxa de sucesso no real: 20%
1.2 O “Domain Gap” (Lacuna de Domínio)
Diferenças entre Simulação e Realidade:
| Aspecto | Simulação | Realidade |
|---|---|---|
| Física | Deterministíca, perfeita | Ruidosa, imperfeita |
| Sensores | Zero ruído | Ruído, drift, falhas |
| Atuadores | Resposta instantânea | Latência, backlash |
| Ambiente | Controlado | Imprevisível |
| Computação | Ilimitada | Limitada (bateria, CPU) |
| Tempo | Acelerável (10x) | Tempo real |
Exemplo concreto:
# Simulação
joint_position = robot.get_joint_position("left_knee")
# Retorna: 1.5708 (sempre exato)
# Realidade
joint_position = robot.get_joint_position("left_knee")
# Retorna: 1.5701, 1.5715, 1.5693, ... (varia)
# + Às vezes retorna None (sensor perdeu sinal)
# + Latência de 20-50ms
1.3 Abordagens para Sim2Real
Abordagem 1: Simulação Ultra-Realista
Fazer a simulação ser tão real quanto possível.
Prós:
- ✅ Transfere melhor
- ✅ Menos ajustes no real
Contras:
- ❌ Muito custoso computacionalmente
- ❌ Impossível replicar TODA realidade
- ❌ Treino mais lento
Abordagem 2: Domain Randomization
Tornar a simulação propositalmente DIVERSA e ruidosa.
Prós:
- ✅ Política aprende a ser robusta
- ✅ Generaliza melhor
- ✅ Mais usado na prática
Contras:
- ❌ Requer mais amostras de treino
- ❌ Pode ser “conservadora” demais
Abordagem 3: Híbrido (Sim + Fine-tuning no Real)
Treina na simulação, ajusta com dados reais.
Prós:
- ✅ Melhor de dois mundos
- ✅ Menos dados reais necessários
Contras:
- ❌ Requer acesso ao robô real
- ❌ Risco de overfit no ambiente específico
2. Domain Randomization
2.1 Conceito
Ideia: Se a política funciona em 1000 versões diferentes da simulação, ela funcionará na realidade (que é apenas “mais uma versão”).
Parâmetros para Randomizar:
A) Física
# A cada episódio, variar:
gravity = random.uniform(9.6, 10.0) # Terra varia por altitude
friction = random.uniform(0.3, 1.2) # Piso liso vs áspero
mass = random.uniform(0.9, 1.1) * nominal_mass # Tolerância fabricação
B) Visual
# Câmera / Visão
lighting = random.choice(['bright', 'dim', 'mixed'])
camera_noise = add_gaussian_noise(image, std=random.uniform(0, 0.1))
color_shift = random_hue_saturation(image)
occlusion = randomly_block_parts_of_image(image, prob=0.1)
C) Sensores
# IMU (equilíbrio)
imu_reading = true_value + random.normal(0, 0.05) # Ruído
imu_drift = accumulated_error * timestep # Drift ao longo do tempo
# Encoders dos motores
encoder_delay = random.uniform(0, 50) # ms de latência
encoder_dropout = (random.random() < 0.02) # 2% de falha
D) Atuadores
# Motores
motor_force = command * random.uniform(0.95, 1.05) # Variação
motor_delay = random.uniform(10, 30) # ms
backlash = random.uniform(0, 2) # graus de folga
2.2 Implementação no Isaac Sim
Código de exemplo:
import omni
from omni.isaac.core import World
from omni.isaac.core.utils.nucleus import get_assets_root_path
import numpy as np
class DomainRandomizer:
def __init__(self, world: World):
self.world = world
def randomize_physics(self):
"""Randomiza parâmetros físicos do ambiente"""
# 1. Gravidade (±5%)
gravity_scale = np.random.uniform(0.95, 1.05)
self.world.get_physics_context().set_gravity(
-9.81 * gravity_scale
)
# 2. Fricção do chão (0.3 a 1.2)
ground = self.world.scene.get_object("ground_plane")
friction = np.random.uniform(0.3, 1.2)
ground.get_applied_physics_material().set_static_friction(friction)
ground.get_applied_physics_material().set_dynamic_friction(friction)
# 3. Massa do robô (±10%)
robot = self.world.scene.get_object("bumi")
for link in robot.get_links():
original_mass = link.get_mass()
new_mass = original_mass * np.random.uniform(0.9, 1.1)
link.set_mass(new_mass)
def randomize_visuals(self):
"""Randomiza aparência visual"""
# 1. Iluminação
lights = self.world.stage.GetPrimAtPath("/World/Lights")
intensity = np.random.uniform(500, 3000)
for light in lights.GetChildren():
light.GetAttribute("intensity").Set(intensity)
# 2. Cor do chão
ground = self.world.stage.GetPrimAtPath("/World/ground")
color = np.random.uniform(0.2, 0.9, size=3) # RGB
ground.GetAttribute("color").Set(color)
# 3. Posição de objetos aleatórios (distratores)
for i in range(np.random.randint(0, 5)):
pos = np.random.uniform(-2, 2, size=3)
pos[2] = 0.5 # altura fixa
self.spawn_random_object(pos)
def randomize_sensors(self, sensor_data):
"""Adiciona ruído a leituras de sensores"""
# 1. IMU (acelerômetro + giroscópio)
imu_noise = np.random.normal(0, 0.05, size=sensor_data['imu'].shape)
sensor_data['imu'] += imu_noise
# 2. Encoders (posição das juntas)
encoder_noise = np.random.normal(0, 0.01, size=sensor_data['joints'].shape)
sensor_data['joints'] += encoder_noise
# 3. Dropout (2% de chance de perder leitura)
if np.random.random() < 0.02:
sensor_data['joints'] = None
# 4. Câmera
if 'camera' in sensor_data:
# Adiciona ruído gaussiano
noise = np.random.normal(0, 10, size=sensor_data['camera'].shape)
sensor_data['camera'] = np.clip(sensor_data['camera'] + noise, 0, 255)
return sensor_data
def randomize_actuators(self, action):
"""Simula imperfeições dos motores"""
# 1. Força variável (±5%)
force_variation = np.random.uniform(0.95, 1.05, size=action.shape)
action = action * force_variation
# 2. Latência (10-30ms)
# (requer buffer de ações passadas)
delay_steps = np.random.randint(1, 3) # 1-3 steps @ 100Hz = 10-30ms
action = self.action_buffer[-delay_steps]
# 3. Saturation (motores têm limite)
action = np.clip(action, -1.0, 1.0)
# 4. Dead zone (pequenos comandos são ignorados)
action[np.abs(action) < 0.05] = 0
return action
# Uso durante treino
world = World()
randomizer = DomainRandomizer(world)
for episode in range(10000):
# A cada episódio, randomizar tudo
randomizer.randomize_physics()
randomizer.randomize_visuals()
world.reset()
for step in range(1000):
# Observação com ruído
obs = get_observations()
obs = randomizer.randomize_sensors(obs)
# Ação da política
action = policy.predict(obs)
# Ação com imperfeições
action = randomizer.randomize_actuators(action)
# Executa
world.step(action)
2.3 Boas Práticas de Randomização
1. Comece Simples, Aumente Gradualmente
# Fase 1: Sem randomização (baseline)
randomization_level = 0.0
# Fase 2: Randomização leve
randomization_level = 0.3
# Fase 3: Randomização agressiva
randomization_level = 1.0
2. Randomize o que Importa
Não randomize tudo cegamente. Foque nas diferenças reais.
Exemplo:
- ✅ Randomizar fricção (pisos variam muito)
- ✅ Randomizar latência (Wi-Fi varia)
- ❌ Randomizar cores aleatoriamente (se seu robô sempre vai atuar em escritórios cinzas)
3. Valide com “Sanity Check”
Grave vídeos da simulação randomizada. Parecem plausíveis?
- ✅ Robô caminhando em pisos diferentes: OK
- ❌ Robô flutuando porque gravidade ficou negativa: NÃO OK
3. System Identification
3.1 O que é?
System Identification: Processo de estimar os parâmetros reais do robô físico.
Por que fazer?
- Simulação usa parâmetros nominais (do manual)
- Robô real tem variações de fabricação
- Desgaste altera parâmetros ao longo do tempo
Parâmetros a Identificar:
| Parâmetro | Nominal (Manual) | Real (Medido) |
|---|---|---|
| Massa total | 8.0 kg | 8.3 kg |
| Fricção juntas | 0.01 Nm | 0.015 Nm |
| Inércia link | 0.05 kg·m² | 0.053 kg·m² |
| Latência motores | 5 ms | 18 ms |
| Constante motor | 0.05 Nm/A | 0.048 Nm/A |
3.2 Técnicas de Identificação
Método 1: Análise de Resposta ao Degrau
Aplique comando fixo, observe resposta.
import numpy as np
import matplotlib.pyplot as plt
def identify_motor_response(robot, joint_name):
"""
Identifica constante de tempo e ganho do motor
"""
# 1. Aplicar comando step
robot.set_joint_effort(joint_name, 1.0) # 100% torque
# 2. Registrar posição ao longo do tempo
positions = []
times = []
for t in np.arange(0, 2.0, 0.01): # 2 segundos @ 100Hz
pos = robot.get_joint_position(joint_name)
positions.append(pos)
times.append(t)
time.sleep(0.01)
# 3. Fit modelo de 1ª ordem: y(t) = K(1 - e^(-t/τ))
from scipy.optimize import curve_fit
def model(t, K, tau):
return K * (1 - np.exp(-t / tau))
params, _ = curve_fit(model, times, positions)
K, tau = params
print(f"Ganho (K): {K:.3f} rad")
print(f"Constante de tempo (τ): {tau:.3f} s")
# 4. Plotar
plt.plot(times, positions, label='Real')
plt.plot(times, model(times, K, tau), '--', label='Modelo')
plt.legend()
plt.show()
return K, tau
Interpretação:
K(ganho): Quanto o motor se move para comando máximoτ(tau): Quão rápido responde (menor = mais rápido)
Método 2: System ID via Otimização
Rode simulação com parâmetros variáveis até match com real.
def optimize_simulation_params(real_robot_data, sim_robot):
"""
Ajusta parâmetros da simulação para match com dados reais
"""
from scipy.optimize import minimize
def loss_function(params):
"""
Roda simulação com params e calcula erro vs real
"""
mass, friction, inertia = params
# Atualizar simulação
sim_robot.set_mass(mass)
sim_robot.set_friction(friction)
sim_robot.set_inertia(inertia)
# Rodar mesmo experimento que no robô real
sim_trajectory = run_experiment(sim_robot)
# Calcular distância entre trajetórias
error = np.mean((sim_trajectory - real_robot_data) ** 2)
return error
# Otimizar
initial_params = [8.0, 0.01, 0.05] # Valores nominais
bounds = [(7.0, 9.0), (0.005, 0.02), (0.04, 0.06)]
result = minimize(
loss_function,
initial_params,
bounds=bounds,
method='L-BFGS-B'
)
optimal_params = result.x
print(f"Parâmetros identificados: {optimal_params}")
return optimal_params
Método 3: Gray-Box Identification com ML
Use rede neural para aprender resíduos (diferenças não modeladas).
import torch
import torch.nn as nn
class ResidualDynamicsModel(nn.Module):
"""
Aprende a diferença entre modelo nominal e real
"""
def __init__(self, state_dim, action_dim):
super().__init__()
self.net = nn.Sequential(
nn.Linear(state_dim + action_dim, 128),
nn.ReLU(),
nn.Linear(128, 128),
nn.ReLU(),
nn.Linear(128, state_dim)
)
def forward(self, state, action):
x = torch.cat([state, action], dim=-1)
residual = self.net(x)
return residual
# Treinar com dados do robô real
model = ResidualDynamicsModel(state_dim=18, action_dim=12)
for epoch in range(100):
for state, action, next_state in real_robot_dataset:
# Predição do modelo nominal (simulação)
nominal_next = simulate_step(state, action)
# Residual real
true_residual = next_state - nominal_next
# Predição do modelo ML
pred_residual = model(state, action)
# Loss
loss = nn.MSELoss()(pred_residual, true_residual)
loss.backward()
optimizer.step()
# Agora, durante simulação:
# next_state = simulate_step(state, action) + model(state, action)
4. Fine-tuning On-Robot (Safety First!)
4.1 Por que Fine-tunar?
Mesmo com domain randomization perfeito, ajustes finais no robô real melhoram performance em 10-30%.
Mas cuidado: Treinar no robô real pode quebrá-lo!
4.2 Protocolo de Segurança
Antes de ligar o robô:
CHECKLIST DE SEGURANÇA
Hardware:
- [ ] Área de 3m x 3m livre de obstáculos
- [ ] Piso antiderrapante ou tapete
- [ ] Almofadas/colchões ao redor (amortecer quedas)
- [ ] Botão de emergência acessível (< 2m de distância)
- [ ] Bateria carregada mas não ao máximo (80% ideal)
- [ ] Inspeção visual: parafusos apertados, cabos firmes
Software:
- [ ] Limites de torque configurados (50% do máximo)
- [ ] Watchdog timer ativo (desliga após 100ms sem comando)
- [ ] Kill switch via rede (SSH remoto)
- [ ] Logging ativo (registrar tudo!)
- [ ] Backup da política anterior (rollback fácil)
Equipe:
- [ ] 2+ pessoas presentes (1 no computador, 1 pronta pra segurar robô)
- [ ] Todos usam óculos de proteção
- [ ] Celular carregado (ligar pra suporte se necessário)
- [ ] Plano de emergência definido
4.3 Estratégias de Fine-tuning Seguro
Estratégia 1: Inicialização Segura (Safe Exploration)
Comece de poses conservadoras, aumente risco gradualmente.
class SafeExploration:
def __init__(self, robot):
self.robot = robot
self.episode_count = 0
def get_initial_pose(self):
"""Pose inicial depende do progresso"""
if self.episode_count < 10:
# Muito seguro: joelhos flexionados, centro de massa baixo
return "crouch_pose"
elif self.episode_count < 50:
# Moderado: em pé mas joelhos levemente flexionados
return "standing_safe"
else:
# Normal: pode começar de qualquer pose
return "random_pose"
def scale_actions(self, action):
"""Escala ações agressivas no início"""
if self.episode_count < 20:
# Primeiros episódios: max 30% de torque
scale = 0.3
elif self.episode_count < 100:
# Episódios iniciais: max 60%
scale = 0.6
else:
# Após 100 episódios: sem limitações
scale = 1.0
return action * scale
Estratégia 2: Human-in-the-Loop
Humano pode intervir e corrigir o robô.
class HumanSupervisor:
def __init__(self, robot):
self.robot = robot
self.intervention_count = 0
def should_intervene(self, state):
"""Detecta situações perigosas"""
# 1. Vai cair?
if abs(state['base_orientation_x']) > 0.3: # > 17 graus
return True, "Inclinação excessiva"
# 2. Torques muito altos?
if any(state['joint_efforts'] > 20.0): # Nm
return True, "Torque perigoso"
# 3. Velocidade angular alta?
if np.linalg.norm(state['angular_velocity']) > 3.0: # rad/s
return True, "Girando rápido demais"
return False, None
def intervene(self):
"""Para robô e registra intervenção"""
print("🚨 INTERVENÇÃO HUMANA!")
self.robot.emergency_stop()
self.intervention_count += 1
# Registrar para aprendizado
self.log_intervention(state, action, reason)
# Humano corrige manualmente
print("Mova o robô para pose segura e pressione ENTER")
input()
def collect_correction(self):
"""Registra correção humana como dado de treino"""
# Estado perigoso -> Ação humana (correção)
# Isso vira dado de treino supervisionado
return (state_before, human_action)
# Loop de treino
supervisor = HumanSupervisor(robot)
while True:
state = robot.get_state()
# Predição da política
action = policy.predict(state)
# Humano pode intervir
should_stop, reason = supervisor.should_intervene(state)
if should_stop:
supervisor.intervene()
continue
# Executa ação
robot.step(action)
Estratégia 3: Sim-to-Real Gradual
Treina em simulações cada vez mais realistas.
# Nível 0: Simulação limpa
train_policy(sim_clean, episodes=1000)
# Nível 1: Simulação com leve randomização
train_policy(sim_light_random, episodes=500, init_from_previous=True)
# Nível 2: Simulação com randomização agressiva
train_policy(sim_heavy_random, episodes=500, init_from_previous=True)
# Nível 3: Simulação com parâmetros identificados do robô real
train_policy(sim_matched_to_real, episodes=200, init_from_previous=True)
# Nível 4: Fine-tuning no robô real (poucos episódios!)
train_policy(real_robot, episodes=50, init_from_previous=True, safe_mode=True)
5. Deployment em Produção
5.1 Arquitetura de Software
Stack típico para robô em produção:
┌─────────────────────────────────────────┐
│ APLICAÇÃO / UI │ ← Dashboard, API REST
├─────────────────────────────────────────┤
│ ORQUESTRAÇÃO (ROS 2) │ ← Nodes, topics, services
├──────────────┬──────────────────────────┤
│ PERCEPÇÃO │ CONTROLE │ NAVEGAÇÃO │
│ (Visão, IA) │ (Motores) │ (SLAM) │
├──────────────┴──────────────────────────┤
│ MIDDLEWARE (DDS) │ ← Comunicação entre nodes
├─────────────────────────────────────────┤
│ DRIVERS DE HARDWARE │ ← Interface com sensores/atuadores
├─────────────────────────────────────────┤
│ HARDWARE FÍSICO │ ← Robô Bumi, Unitree, etc
└─────────────────────────────────────────┘
5.2 Containerização com Docker
Por que usar Docker?
- ✅ Ambiente consistente (dev = prod)
- ✅ Fácil atualização remota
- ✅ Rollback rápido se algo quebrar
- ✅ Isola dependências (não quebra sistema)
Dockerfile exemplo:
# Base: ROS 2 Humble + CUDA (para IA)
FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04
# Instalar ROS 2
RUN apt-get update && apt-get install -y \
curl \
gnupg2 \
lsb-release
RUN curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key | apt-key add -
RUN echo "deb http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" > /etc/apt/sources.list.d/ros2.list
RUN apt-get update && apt-get install -y \
ros-humble-desktop \
python3-pip
# Instalar dependências Python
COPY requirements.txt /tmp/
RUN pip3 install -r /tmp/requirements.txt
# Copiar código da aplicação
WORKDIR /app
COPY src/ /app/src/
COPY launch/ /app/launch/
COPY config/ /app/config/
# Variáveis de ambiente
ENV ROS_DOMAIN_ID=42
ENV PYTHONUNBUFFERED=1
# Source ROS 2
RUN echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
# Entry point
CMD ["ros2", "launch", "bumi_control", "full_stack.launch.py"]
requirements.txt:
torch==2.0.1
torchvision==0.15.2
opencv-python==4.8.0.74
numpy==1.24.3
scipy==1.11.1
matplotlib==3.7.2
pandas==2.0.3
Build e Deploy:
# 1. Build imagem
docker build -t bumi-control:v1.2.0 .
# 2. Testar localmente
docker run --rm --gpus all \
-v /dev:/dev \
--privileged \
bumi-control:v1.2.0
# 3. Push para registry (se deploy remoto)
docker tag bumi-control:v1.2.0 registry.empresa.com/bumi-control:v1.2.0
docker push registry.empresa.com/bumi-control:v1.2.0
# 4. No robô real (via SSH)
ssh bumi@192.168.1.100
docker pull registry.empresa.com/bumi-control:v1.2.0
docker run -d --restart unless-stopped \
--name bumi-prod \
--gpus all \
-v /dev:/dev \
--privileged \
registry.empresa.com/bumi-control:v1.2.0
5.3 Monitoramento e Logging
O que monitorar:
import logging
from prometheus_client import Counter, Histogram, Gauge
# Métricas Prometheus
steps_executed = Counter('robot_steps_total', 'Total steps executed')
action_latency = Histogram('robot_action_latency_seconds', 'Action processing time')
battery_level = Gauge('robot_battery_percent', 'Battery level')
temperature = Gauge('robot_motor_temperature_celsius', 'Motor temperature')
falls_detected = Counter('robot_falls_total', 'Number of falls')
class RobotMonitoring:
def __init__(self, robot):
self.robot = robot
self.logger = logging.getLogger('bumi')
def log_step(self, state, action, reward):
"""Loga cada passo de controle"""
steps_executed.inc()
# Log estruturado
self.logger.info({
'timestamp': time.time(),
'state': {
'base_position': state['base_pos'].tolist(),
'base_orientation': state['base_ori'].tolist(),
'joint_positions': state['joint_pos'].tolist(),
'battery': state['battery']
},
'action': action.tolist(),
'reward': reward
})
# Alertas
if state['battery'] < 20:
self.logger.warning(f"⚠️ Bateria baixa: {state['battery']}%")
battery_level.set(state['battery'])
for i, temp in enumerate(state['motor_temps']):
if temp > 70:
self.logger.error(f"🔥 Motor {i} superaquecendo: {temp}°C")
temperature.set(temp)
def detect_fall(self, state):
"""Detecta quedas"""
roll, pitch, _ = state['base_orientation']
if abs(roll) > 1.0 or abs(pitch) > 1.0: # > 57 graus
falls_detected.inc()
self.logger.error("🚨 QUEDA DETECTADA!")
self.robot.emergency_stop()
self.send_alert_to_team()
return True
return False
Dashboard (Grafana):
# grafana-dashboard.json (simplificado)
{
"panels": [
{
"title": "Battery Level",
"targets": [{"expr": "robot_battery_percent"}],
"type": "gauge",
"alert": {
"conditions": [{"value": 20, "operator": "<"}]
}
},
{
"title": "Action Latency (p95)",
"targets": [{"expr": "histogram_quantile(0.95, robot_action_latency_seconds)"}],
"type": "graph"
},
{
"title": "Falls (last 24h)",
"targets": [{"expr": "increase(robot_falls_total[24h])"}],
"type": "stat"
}
]
}
5.4 Atualizações Over-The-Air (OTA)
Desafio: Como atualizar software em 100 robôs em campo?
Solução:
# Servidor central
class OTAManager:
def __init__(self):
self.fleet = {} # {robot_id: status}
def push_update(self, version, target_robots='all'):
"""
Envia nova versão para robôs
"""
update_package = {
'version': version,
'docker_image': f'registry/bumi-control:{version}',
'checksum': self.compute_checksum(version),
'rollback_enabled': True
}
if target_robots == 'all':
robots = self.fleet.keys()
else:
robots = target_robots
# Estratégia: canary deployment
# 1. Atualiza 5% primeiro
canary_robots = robots[:len(robots)//20]
self.deploy_to_robots(canary_robots, update_package)
# 2. Monitora por 1 hora
time.sleep(3600)
if self.check_health(canary_robots):
# 3. Se OK, atualiza resto
remaining = robots[len(canary_robots):]
self.deploy_to_robots(remaining, update_package)
else:
# Rollback canary
self.rollback(canary_robots)
def deploy_to_robots(self, robots, package):
"""Deploy gradual"""
for robot_id in robots:
try:
# SSH para o robô
ssh = self.connect_ssh(robot_id)
# Pull nova imagem Docker
ssh.exec(f"docker pull {package['docker_image']}")
# Parar container antigo
ssh.exec("docker stop bumi-prod")
# Iniciar novo (com health check)
ssh.exec(f"docker run -d --name bumi-prod-new {package['docker_image']}")
# Se health check passa em 60s, remove antigo
if self.wait_for_health(robot_id, timeout=60):
ssh.exec("docker rm bumi-prod")
ssh.exec("docker rename bumi-prod-new bumi-prod")
self.fleet[robot_id]['version'] = package['version']
else:
# Rollback
ssh.exec("docker stop bumi-prod-new && docker rm bumi-prod-new")
ssh.exec("docker start bumi-prod")
raise Exception("Health check failed")
except Exception as e:
logging.error(f"Deploy falhou em {robot_id}: {e}")
self.fleet[robot_id]['status'] = 'failed'
6. Troubleshooting Comum
6.1 Problema: “Robô cai imediatamente”
Diagnóstico:
# Verificar se simulação está muito otimista
print("Centro de massa (sim):", sim_robot.center_of_mass)
print("Centro de massa (real):", real_robot.center_of_mass)
# Diferença > 5cm? Problema!
Soluções:
- Refazer system identification (massa real pode estar errada)
- Aumentar domain randomization em massa e geometria
- Treinar política com “peso extra” na simulação
6.2 Problema: “Ações têm delay visível”
Diagnóstico:
import time
def measure_latency(robot):
latencies = []
for _ in range(100):
t0 = time.time()
robot.set_joint_effort("left_knee", 1.0)
t1 = time.time()
latencies.append((t1 - t0) * 1000) # ms
print(f"Latência média: {np.mean(latencies):.1f} ms")
print(f"Latência p95: {np.percentile(latencies, 95):.1f} ms")
Soluções:
- Se latência > 50ms: problema de rede ou CPU
- Usar conexão cabeada (não Wi-Fi)
- Rodar inferência on-device (não em servidor remoto)
- Se latência variável: adicionar prediction buffer
- Prever ação para T+2 (compensa delay)
6.3 Problema: “Funciona 5 min depois para”
Diagnóstico:
# Checar logs
docker logs bumi-prod --tail 100
# Comum:
# - "CUDA out of memory" → Modelo de IA muito grande
# - "CPU temperature: 95°C" → Superaquecimento
# - "Battery critical" → Bateria acabou
Soluções:
- Memory leak → Adicionar
torch.cuda.empty_cache()periodicamente - Superaquecimento → Reduzir frequência de controle (100Hz → 50Hz)
- Bateria → Adicionar auto-docking para recarga
🛠️ Atividades Práticas
Exercício 1: Domain Randomization
Tempo: 90 minutos
Tarefa: Implementar domain randomization completo no Isaac Sim.
Código starter:
# Seu objetivo: preencher essa classe
class MyDomainRandomizer:
def randomize_all(self, world):
# TODO: randomizar física
# TODO: randomizar visual
# TODO: randomizar sensores
pass
# Teste
for episode in range(100):
randomizer.randomize_all(world)
# Verificar: episódios parecem diferentes?
# Tirar screenshot de 10 episódios
Entrega:
- Código Python completo
- 10 screenshots mostrando diversidade
- Relatório: quais parâmetros randomizou e por quê?
Exercício 2: System Identification
Tempo: 60 minutos
Tarefa: Identificar parâmetros de um motor simulado.
Você vai receber um “robô caixa-preta” (simulado). Seu trabalho:
- Aplicar comandos de teste
- Registrar respostas
- Estimar: ganho, constante de tempo, fricção
Código starter fornecido: system_id_starter.py
Critério de sucesso:
- Erro < 5% vs parâmetros reais (que serão revelados depois)
Exercício 3: Deployment com Docker
Tempo: 120 minutos
Tarefa: Criar Dockerfile para sua aplicação de controle.
Requisitos:
- Base: ROS 2 Humble
- Incluir sua política treinada (arquivo .pth)
- Incluir script de launch
- Testar localmente
Entrega:
Dockerfilerequirements.txtlaunch/control.launch.py- Screenshot do container rodando
📊 Projeto do Módulo: Sim-to-Real Transfer Completo
Objetivo: Transferir uma política de caminhada da simulação para um robô “real” (ou simulado com parâmetros realistas).
Especificações
Parte 1: Treino com Domain Randomization
- Treinar política de caminhada em simulação limpa (baseline)
- Adicionar domain randomization agressivo
- Retreinar (pode usar transfer learning)
- Comparar performance: com vs sem randomization
Parte 2: System Identification
- Identificar pelo menos 3 parâmetros do robô-alvo
- Ajustar simulação para match
- Treinar política na simulação ajustada
Parte 3: Teste no Robô-Alvo
- Deploy cada política (baseline, randomized, finetuned)
- Medir taxa de sucesso em 20 episódios cada
- Analisar: qual foi melhor? Por quê?
Parte 4: Relatório
- 4-6 páginas
- Gráficos comparativos
- Vídeos das tentativas
- Lições aprendidas
Critérios de Avaliação
| Critério | Peso | Descrição |
|---|---|---|
| Domain Randomization | 25% | Implementação completa e justificada |
| System Identification | 20% | Precisão dos parâmetros identificados |
| Resultados experimentais | 30% | Taxa de sucesso, análise rigorosa |
| Relatório | 25% | Clareza, insights, qualidade técnica |
Entrega
📤 Upload: Projeto Final Módulo 4.2
Prazo: Até final do Módulo 4.2
📚 Recursos Complementares
Papers Fundamentais
📄 Sim-to-Real Transfer:
- “Sim-to-Real Transfer of Robotic Control with Dynamics Randomization” (OpenAI, 2017)
- “Sim-to-Real: Learning Agile Locomotion For Quadruped Robots” (Google, 2018)
- “Learning to Walk in Minutes Using Massively Parallel Deep RL” (NVIDIA, 2022)
Tutoriais e Cursos
🎥 Vídeos:
- “Sim2Real in 30 Minutes” - Lex Fridman
- “Domain Randomization Explained” - Two Minute Papers
- “ROS 2 + Docker Best Practices” - The Construct
📚 Livros (capítulos específicos):
- “Learning from Randomness” - Cap. 7 de “Reinforcement Learning” (Sutton & Barto)
Ferramentas
🛠️ Software:
- ROS 2 Humble - Middleware de robótica
- Docker + NVIDIA Container Toolkit - Containerização com GPU
- Prometheus + Grafana - Monitoramento
- Weights & Biases - Tracking de experimentos
📥 Código de exemplo:
🎯 Checklist de Conclusão
Antes de avançar para o Módulo 4.3, certifique-se:
- Entendo o conceito de “domain gap”
- Implementei domain randomization em pelo menos 3 dimensões
- Executei system identification em um robô
- Criei um Dockerfile funcional
- Entendo protocolo de segurança para teste em hardware
- Concluí o projeto de Sim-to-Real Transfer
- Li pelo menos 1 paper sobre Sim2Real
- Testei fine-tuning (mesmo que em simulação)
💬 Discussão e Comunidade
Participe das discussões:
💭 Fórum do Módulo:
- Compartilhe seus resultados de Sim2Real
- Discuta estratégias de domain randomization
- Tire dúvidas sobre deployment
🎤 Live Semanal (Terça 20h):
- Demo de transferências bem-sucedidas
- Q&A com especialistas em Sim2Real
- Troubleshooting coletivo
→ Entrar na Comunidade Discord
➡️ Próximo Módulo
Parabéns! Seu robô agora funciona no mundo real!
No Módulo 4.3, você vai aprender a criar protótipos completos e aplicações reais prontas para o mercado.
No Módulo 4.3 você vai aprender:
- Design de produto para robótica
- Integração de sistemas complexos
- Testes de campo e validação
- Documentação profissional
- Apresentação para stakeholders
→ Iniciar Módulo 4.3: Protótipos e Aplicações
Última atualização: 2025-10-29 Autor: Equipe FTH Revisores: 2 engenheiros de robótica + 1 especialista em Sim2Real (ex-Boston Dynamics)