feat(celular): rosto e app usam SSE e token; remove telas legadas

- carinha.html assina /eventos (expressao, app_config, conversa, reacao,
  recarregar) e volta ao polling de /fila sozinho se o SSE cair; foto dos
  ajustes a cada (re)conexao; token (?t=) vai em todo fetch ao servidor
- APK 0.8-token: token nas preferencias e no menu de toque longo, enviado
  na pagina, no HTTP do Java e ao llama-server (--api-key); trocar servidor
  por Intent exige o token salvo; camera/microfone so para o servidor
  configurado ou a copia local
- cerebro/conversa mandam a chave ao llama-server do S10
- servidor: evento SSE 'recarregar'; remove /camera/frame e /app/connect;
  enderecos antigos redirecionam para a secao do painel novo
- remove dashboard, painel, conversa, emojis, treinamento, braco, servos,
  api, scripts e CSS antigos, audio-robot.js e servir-local.bat
- logica pura do painel em web/src/lib/nucleo.ts com testes node --test
  (substitui test_movimentos.js e test_rastreamento.js)
- testes de navegador portados para o painel novo (rtc, rosto, rastreamento)
- README, docs/PAINEL.md e docs/SEGURANCA.md reescritos; versao 0.8.0;
  carinha-pc.service le /etc/carinha.env

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-05 20:11:58 -03:00
co-authored by Claude Opus 5.5
parent 673dea62e8
commit c5fd54c1d9
67 changed files with 626 additions and 5904 deletions
+4 -44
View File
@@ -7,6 +7,9 @@
import { pedir, postar } from './api';
import { avisos } from './avisos.svelte';
import { estado } from './estado.svelte';
import { limites } from './nucleo';
export { sequencias } from './nucleo';
export interface Calibracao {
canal: number;
@@ -33,49 +36,6 @@ export interface StatusBraco {
export const PRAZO_MS = 450;
const RENOVAR_MS = 150;
function anguloDoPulso(p: number, c: Calibracao) {
const centro = c.centro ?? 1500;
return p <= centro ? (90 * (p - 500)) / (centro - 500) : 90 + (90 * (p - centro)) / (2500 - centro);
}
/** Faixa segura (graus) de cada articulação a partir da calibração; o canal 5 respeita o par 6 invertido. */
export function limites(st: StatusBraco): [number, number][] {
return st.articulacoes.map(({ canal }) => {
const c = st.calibracao[canal];
let lo = anguloDoPulso(c.minimo, c);
let hi = anguloDoPulso(c.maximo, c);
if (canal === 5) {
const par = st.calibracao[6];
lo = Math.max(lo, 180 - anguloDoPulso(par.maximo, par));
hi = Math.min(hi, 180 - anguloDoPulso(par.minimo, par));
}
return [Math.ceil(lo), Math.floor(hi)];
});
}
/** As 5 sequências prontas, escaladas para a faixa calibrada (porta de movimentos-core.js). */
export function sequencias(faixas: [number, number][]): { nome: string; passos: number[][] }[] {
const neutro = [0, 0, 0, 0, 0, 0, 0];
const base: [string, number[][]][] = [
['Acenar', [[0, -0.7, 0.5, 0.2, 0, 0.1, 0], [0, 0.7, -0.5, 0.2, 0, 0.1, 0], [0, -0.7, 0.5, 0.2, 0, 0.1, 0], [0, 0.7, -0.5, 0.2, 0, 0.1, 0], neutro]],
['Olhar para os lados', [[0, 0.2, -0.4, 0, 0, 0, -0.7], [0, -0.2, 0.4, 0, 0, 0, 0.7], neutro]],
['Alcançar e recolher', [[0, 0, 0.2, 0.6, -0.5, 0.4, 0], [0, 0, -0.2, -0.4, 0.5, -0.4, 0], neutro]],
['Garra e punho', [[-0.7, 0.3, 0, 0.2, 0, 0.1, 0], [0.7, -0.3, 0, 0.2, 0, 0.1, 0], [-0.7, 0.3, 0, 0, 0, 0, 0], neutro]],
['Apresentação', [[0.3, 0.3, -0.3, 0.4, -0.3, 0.3, -0.6], [-0.3, -0.3, 0.3, -0.3, 0.4, -0.3, 0.6], [0.5, 0.4, 0.2, 0.3, -0.3, 0.2, 0], neutro]],
];
return base.map(([nome, passos]) => ({
nome,
passos: passos.map((s) =>
s.map((v, i) => {
const [lo, hi] = faixas[i];
// Centro 90°, mas sempre dentro da faixa calibrada.
const meio = Math.max(lo, Math.min(hi, 90));
return Math.round(meio + v * (v < 0 ? meio - lo : hi - meio));
}),
),
}));
}
class Braco {
status = $state<StatusBraco | null>(null);
alvo = $state<number[]>(Array(7).fill(90));
@@ -90,7 +50,7 @@ class Braco {
private sequencia: number[][] | null = null;
private pausaAte = 0;
faixas = $derived(this.status ? limites(this.status) : []);
faixas = $derived(this.status ? limites(this.status.calibracao, this.status.articulacoes) : []);
async carregar() {
try {