Respostas¶
Pelo menos 2 jeitos de resolver cada atividade de Atividades. Não é "o jeito certo" — é pra comparar com o que você fez e ver que tem mais de um caminho válido.
Os trechos abaixo pressupõem que robo já é um RoboEV3 conectado
(with RoboEV3(...) as robo:) e que import time,
from jacarev3 import protocolo as p e
from jacarev3.topicos import circuito, desvio, controle já foram
feitos, do jeito que os tutoriais ensinam.
Básico¶
Nível 1 — bipes em frequência/volume diferentes¶
Nível 2 — tabela de leituras por distância¶
for d in (5, 10, 20, 40, 80):
input(f"Posiciona o objeto a {d}cm e aperta Enter")
amostras = [robo.ler_sensor(4, p.MODO_ULTRASSONICO_CM) for _ in range(5)]
media = sum(amostras) / len(amostras)
print(f"{d}cm real -> média de 5 leituras: {media:.1f}cm "
f"(min {min(amostras):.1f}, max {max(amostras):.1f})")
Nível 3 — contar apertos do sensor de toque¶
def contar_toques(robo, porta=2, tempo_total=60):
contagem = 0
estado = robo.ler_sensor(porta, p.MODO_TOQUE) > 0.5
inicio = time.time()
while time.time() - inicio < tempo_total:
restante = tempo_total - (time.time() - inicio)
mudou = robo.espera_mudar(
lambda: robo.ler_sensor(porta, p.MODO_TOQUE) > 0.5,
tempo_limite=restante,
)
if not mudou:
break
estado = not estado
if estado: # virou True = acabou de ser pressionado
contagem += 1
print(f"Aperto número {contagem}")
return contagem
espera_mudar já existe na lib (usado por testar_toque por baixo
dos panos) — aqui é só reaproveitado direto, sem escrever o loop de
polling na mão.
Nível 4 — "termômetro" de proximidade¶
while True:
distancia = robo.ler_sensor(4, p.MODO_ULTRASSONICO_CM)
if distancia < 10:
robo.led('VERMELHO'); robo.apitar(frequencia=1800, duracao_ms=80); time.sleep(0.05)
elif distancia < 30:
robo.led('VERMELHO'); robo.apitar(frequencia=900, duracao_ms=80); time.sleep(0.2)
elif distancia < 60:
robo.led('AMBAR'); robo.apitar(frequencia=500, duracao_ms=80); time.sleep(0.4)
else:
robo.led('VERDE'); time.sleep(0.6)
O jeito 1 é o mesmo tipo de controle proporcional do nível 4 de
circuito; o jeito 2 é bang-bang, como o seguir_linha padrão —
os dois nomes que você vai ver de novo lá.
Seguir linha¶
Nível 1 — comparar velocidades¶
Nível 2 — chute vs. calibrado¶
inicio = time.time()
circuito.seguir_linha(robo, porta_sensor=1, limiar=50, duracao_s=20) # chute
tempo_chute = time.time() - inicio
limiar = circuito.calibrar(robo, porta_sensor=1)
inicio = time.time()
circuito.seguir_linha(robo, porta_sensor=1, limiar=limiar, duracao_s=20)
tempo_calibrado = time.time() - inicio
print(f"Chute: {tempo_chute:.1f}s | Calibrado: {tempo_calibrado:.1f}s")
Nível 3 — detectar linha perdida¶
limiar = circuito.calibrar(robo, porta_sensor=1)
ultimo_valor = None
ultimo_tempo_mudou = time.time()
while True:
leitura = robo.ler_sensor(1, p.MODO_COR_REFLETIDA)
if ultimo_valor is None or abs(leitura - ultimo_valor) > 2:
ultimo_tempo_mudou = time.time()
ultimo_valor = leitura
if time.time() - ultimo_tempo_mudou > 2:
robo.parar_motor('B'); robo.parar_motor('C')
robo.apitar(); robo.apitar(); robo.apitar()
print("Perdi a linha!")
break
if leitura > limiar:
robo.mover_continuo('B', 25); robo.mover_continuo('C', 8)
else:
robo.mover_continuo('B', 8); robo.mover_continuo('C', 25)
time.sleep(0.05)
limiar = circuito.calibrar(robo, porta_sensor=1)
leituras_paradas = 0
ultimo_valor = None
while True:
leitura = robo.ler_sensor(1, p.MODO_COR_REFLETIDA)
if ultimo_valor is not None and abs(leitura - ultimo_valor) < 2:
leituras_paradas += 1
else:
leituras_paradas = 0
ultimo_valor = leitura
if leituras_paradas > 40: # 40 * 0.05s ≈ 2s
robo.parar_motor('B'); robo.parar_motor('C')
robo.apitar(); robo.apitar(); robo.apitar()
print("Perdi a linha!")
break
if leitura > limiar:
robo.mover_continuo('B', 25); robo.mover_continuo('C', 8)
else:
robo.mover_continuo('B', 8); robo.mover_continuo('C', 25)
time.sleep(0.05)
Os dois medem "faz tempo que não muda" — só que um mede tempo de
verdade (time.time()) e o outro conta voltas do loop. O segundo é
mais simples, mas quebra se o time.sleep() mudar de valor depois.
Nível 4 — controle proporcional¶
limiar = circuito.calibrar(robo, porta_sensor=1)
velocidade_base = 30
ganho = 0.6
while True:
leitura = robo.ler_sensor(1, p.MODO_COR_REFLETIDA)
erro = leitura - limiar
v_esquerda = velocidade_base + erro * ganho
v_direita = velocidade_base - erro * ganho
robo.mover_continuo('B', int(max(-100, min(100, v_esquerda))))
robo.mover_continuo('C', int(max(-100, min(100, v_direita))))
time.sleep(0.03)
limiar = circuito.calibrar(robo, porta_sensor=1)
velocidade_base = 35
ganho = 0.8
while True:
leitura = robo.ler_sensor(1, p.MODO_COR_REFLETIDA)
erro = leitura - limiar
curva = min(1.0, abs(erro) / 50) # 0 (reto) a 1 (curva fechada)
v = velocidade_base * (1 - 0.5 * curva)
v_esquerda = v + erro * ganho
v_direita = v - erro * ganho
robo.mover_continuo('B', int(max(-100, min(100, v_esquerda))))
robo.mover_continuo('C', int(max(-100, min(100, v_direita))))
time.sleep(0.03)
O jeito 2 é o exercício "curva mais fechada vs. reta" do próprio tutorial (seção 5) — dá pra sentir a diferença de estabilidade nas curvas fechadas.
Desviar de obstáculo¶
Nível 1 — comparar distância mínima¶
Nível 2 — mapear a leitura do seu sensor montado¶
Nível 3 — contar obstáculos desviados¶
contagem = 0
estava_perto = False
inicio = time.time()
while time.time() - inicio < 60:
distancia = robo.ler_sensor(4, p.MODO_ULTRASSONICO_CM)
perto = distancia < 15
if perto and not estava_perto:
contagem += 1
estava_perto = perto
# ... lógica de desvio de verdade entraria aqui
time.sleep(0.1)
print(f"Desviou de {contagem} obstáculos")
contagem = 0
ultimo_desvio = 0
inicio = time.time()
while time.time() - inicio < 60:
distancia = robo.ler_sensor(4, p.MODO_ULTRASSONICO_CM)
agora = time.time()
if distancia < 15 and (agora - ultimo_desvio) > 3:
contagem += 1
ultimo_desvio = agora
# ... lógica de desvio de verdade entraria aqui
time.sleep(0.1)
print(f"Desviou de {contagem} obstáculos")
O jeito 1 pode contar duas vezes o mesmo obstáculo se o robô oscilar perto do limite; o cooldown do jeito 2 evita isso — troca de complexidade por robustez.
Nível 4 — seguir linha + desviar + reencontrar¶
estado = 'seguindo'
limiar = circuito.calibrar(robo, porta_sensor=1)
while True:
if estado == 'seguindo':
if robo.ler_sensor(4, p.MODO_ULTRASSONICO_CM) < 15:
robo.parar_motor('B'); robo.parar_motor('C')
estado = 'desviando'
continue
leitura = robo.ler_sensor(1, p.MODO_COR_REFLETIDA)
if leitura > limiar:
robo.mover_continuo('B', 25); robo.mover_continuo('C', 8)
else:
robo.mover_continuo('B', 8); robo.mover_continuo('C', 25)
elif estado == 'desviando':
robo.mover_continuo('B', 25); robo.mover_continuo('C', -25)
time.sleep(0.6)
robo.mover_continuo('B', 25); robo.mover_continuo('C', 25)
time.sleep(0.5)
estado = 'procurando'
elif estado == 'procurando':
if robo.ler_sensor(1, p.MODO_COR_REFLETIDA) < limiar:
estado = 'seguindo'
else:
robo.mover_continuo('B', 15); robo.mover_continuo('C', -15)
time.sleep(0.05)
def desviar_e_procurar(robo, limiar):
robo.parar_motor('B'); robo.parar_motor('C')
robo.mover_continuo('B', 25); robo.mover_continuo('C', -25)
time.sleep(0.6)
robo.mover_continuo('B', 25); robo.mover_continuo('C', 25)
time.sleep(0.5)
while robo.ler_sensor(1, p.MODO_COR_REFLETIDA) >= limiar:
robo.mover_continuo('B', 15); robo.mover_continuo('C', -15)
time.sleep(0.05)
limiar = circuito.calibrar(robo, porta_sensor=1)
while True:
if robo.ler_sensor(4, p.MODO_ULTRASSONICO_CM) < 15:
desviar_e_procurar(robo, limiar)
continue
leitura = robo.ler_sensor(1, p.MODO_COR_REFLETIDA)
if leitura > limiar:
robo.mover_continuo('B', 25); robo.mover_continuo('C', 8)
else:
robo.mover_continuo('B', 8); robo.mover_continuo('C', 25)
time.sleep(0.05)
O jeito 1 é explícito sobre "em qual fase eu tô" (bom quando o projeto cresce e ganha mais fases); o jeito 2 esconde a fase de desvio dentro de uma função que só volta quando termina — menos código, mas o loop principal não sabe que ela existe enquanto roda.
Controle remoto¶
Nível 1 — ajustar velocidade/giro¶
Nível 2 — sentir zona morta e suavização¶
from jacarev3.fontes import FontePygame
presets = [
{"zona_morta_giro": 0.0, "taxa_suavizacao": 1.0}, # cru, sem filtro
{"zona_morta_giro": 0.3, "taxa_suavizacao": 0.05}, # bem suave e travado
{"zona_morta_giro": 0.1, "taxa_suavizacao": 0.25}, # padrão, pra comparar
]
for preset in presets:
print(f"Testando: {preset}")
with FontePygame(**preset) as fonte:
controle.controle_remoto(robo, fonte=fonte, verboso=True)
input("Enter pro próximo preset...")
Nível 3 — um botão extra aciona LED/bipe¶
from jacarev3.fontes import FontePygame
class FontePygameComBuzina(FontePygame):
def __init__(self, robo, botao_buzina=3, **kwargs):
super().__init__(**kwargs)
self.robo = robo
self.botao_buzina = botao_buzina
def ler(self):
if self._joystick.get_button(self.botao_buzina):
self.robo.apitar()
return super().ler()
with FontePygameComBuzina(robo) as fonte:
controle.controle_remoto(robo, fonte=fonte)
O jeito 1 é o "hack rápido pra testar agora"; o jeito 2 é o jeito
que se encaixa na arquitetura da lib — outro código que só use
fonte.ler() (ex: gravar uma sessão de controle) já ganha a buzina
de graça.
Nível 4 — sua própria fonte¶
import pygame
from jacarev3.fontes import SAIR
class FonteMouse:
def __enter__(self):
pygame.init()
pygame.display.set_mode((300, 300))
return self
def __exit__(self, *exc):
pygame.quit()
return False
def ler(self):
for evento in pygame.event.get():
if evento.type == pygame.QUIT:
return SAIR
x, y = pygame.mouse.get_pos()
velocidade = max(-1.0, min(1.0, (150 - y) / 150))
giro = max(-1.0, min(1.0, (x - 150) / 150))
return (velocidade, giro)
def ajuda(self):
return "Mouse: cima/baixo acelera/ré, esquerda/direita vira."
from jacarev3.fontes import SAIR
class FonteGravada:
"""Reproduz uma sequência gravada: [(velocidade, direcao, passos), ...]"""
def __init__(self, roteiro):
self.roteiro = list(roteiro)
self._indice = 0
self._restantes = 0
self._atual = (0, 0)
def __enter__(self):
return self
def __exit__(self, *exc):
return False
def ler(self):
if self._restantes <= 0:
if self._indice >= len(self.roteiro):
return SAIR
velocidade, direcao, passos = self.roteiro[self._indice]
self._atual = (velocidade, direcao)
self._restantes = passos
self._indice += 1
self._restantes -= 1
return self._atual
def ajuda(self):
return "Reproduzindo uma coreografia gravada."
roteiro = [(1, 0, 20), (1, 1, 10), (1, 0, 20), (0, 0, 5)]
with FonteGravada(roteiro) as fonte:
controle.controle_remoto(robo, fonte=fonte, intervalo=0.1)
Os dois respeitam o mesmo contrato (ler() + with), mas servem
pra coisas bem diferentes: uma é entrada humana ao vivo, a outra é
automação sem humano nenhum no loop — o controle_remoto() não
sabe (nem precisa saber) qual das duas tá rodando.