feat(web): migra Movimento com homem-morto e trava de hardware

- Controle: opt-in de movimento fisico, botao "segure para mover"
  (mouse, toque e teclado), sliders das articulacoes tambem so movem
  enquanto pressionados; velocidade e interpolacao ficam no servidor
- sequencias prontas e posturas salvas: previa so na tela ou execucao
  segurando o botao ate o fim; soltar interrompe na hora
- posturas com nome inline (sem prompt), exclusao confirmada inline,
  exportar/importar; posturas aparecem na paleta (Ctrl+K)
- Calibracao: testes de fim de curso por canal, marcar limites e tabela
  editavel com validacao antes de salvar
- Diagnostico (antigo Servos): ao vivo limitado a 10/s, bloqueado quando
  o braco esta com os motores
- desenho esquematico do braco em SVG com previa animada
- servidor: evento 'motores' tambem ao ligar canais; renovar no alvo
  devolve movendo=false para a sequencia avancar
- aba escondida ou E-Stop soltam o braco e desativam o movimento

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-05 19:56:48 -03:00
co-authored by Claude Opus 5.5
parent 947c0a3127
commit 673dea62e8
10 changed files with 1153 additions and 9 deletions
+9 -3
View File
@@ -84,7 +84,7 @@ class Braco:
try:
for channel,pulse in pulses.items():
self.servos.hw.pulse(channel,pulse)
self.servos.active[channel]=prazo
self.servos.ligar([(channel,prazo) for channel in pulses])
except OSError:
self.alvo=None; self.servos.stop()
raise
@@ -102,7 +102,13 @@ class Braco:
self.escrever(pulses,prazo)
self.atual=[float(a) for a in pose]
return dict(ok=True,pulsos=pulses,movendo=False)
self.alvo=[float(a) for a in pose]; self.velocidade=velocidade; self.lease=prazo
alvo=[float(a) for a in pose]
if all(abs(a-t)<.01 for a,t in zip(self.atual,alvo)):
# Já no alvo: só renova o prazo (e reenergiza canais que tenham desligado).
self.alvo=None
self.escrever(pulses,prazo)
return dict(ok=True,pulsos=pulses,movendo=False)
self.alvo=alvo; self.velocidade=velocidade; self.lease=prazo
for channel in list(self.servos.active): self.servos.active[channel]=prazo
return dict(ok=True,pulsos=pulses,movendo=True)
def cancelar(self):
@@ -142,7 +148,7 @@ class Braco:
self.servos.reservar('braco'); self.alvo=None; self.atual=None
for ch,us in outputs.items():
self.servos.hw.pulse(ch,us)
self.servos.active[ch]=time.monotonic()+2
self.servos.ligar([(ch,time.monotonic()+2) for ch in outputs])
return dict(ok=True,pulsos=outputs)
def rotas(self,h,method):
route=h.path.split('?',1)[0]