- 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>
67 lines
3.4 KiB
Python
67 lines
3.4 KiB
Python
"""Inferencia MediaPipe real, com modelos locais e imagens oficiais de teste."""
|
|
import importlib.util
|
|
from pathlib import Path
|
|
import sys
|
|
import tempfile
|
|
import threading
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
ROOT=Path(__file__).resolve().parents[1]
|
|
FIXTURES=ROOT.parents[1]/'work/tracking-fixtures'
|
|
sys.path.insert(0,str(ROOT))
|
|
spec=importlib.util.spec_from_file_location('kiosk_tracking',ROOT/'kiosk-server.py')
|
|
kiosk=importlib.util.module_from_spec(spec);spec.loader.exec_module(kiosk)
|
|
|
|
class Handler(kiosk.Handler):
|
|
def do_GET(self):
|
|
if self.path in ('/test-portrait','/test-hands'):
|
|
name='portrait.jpg' if self.path=='/test-portrait' else 'right_hands.jpg'
|
|
return self.enviar((FIXTURES/name).read_bytes(),'image/jpeg')
|
|
super().do_GET()
|
|
|
|
def main():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
server=kiosk.Servidor(('127.0.0.1',0),Handler)
|
|
server.controle=kiosk.ControlePC(Path(tmp)/'config.json')
|
|
thread=threading.Thread(target=server.serve_forever,daemon=True);thread.start()
|
|
base='http://127.0.0.1:'+str(server.server_port)
|
|
try:
|
|
with sync_playwright() as p:
|
|
browser=p.chromium.launch(headless=True)
|
|
page=browser.new_page();errors=[];external=[]
|
|
page.on('pageerror',lambda e: errors.append(str(e)))
|
|
page.on('console',lambda m: print('WORKER',m.text[:180]) if m.type=='error' else None)
|
|
def route(r):
|
|
if not r.request.url.startswith(base): external.append(r.request.url);r.abort()
|
|
else:r.continue_()
|
|
page.route('**/*',route)
|
|
page.goto(base+'/') # painel novo: nada pode sair para a internet
|
|
results=page.evaluate("""async () => {
|
|
const worker=new Worker('/rastreador-worker.js');
|
|
const send=msg=>new Promise((resolve,reject)=>{
|
|
worker.onmessage=({data})=>data.tipo==='erro'?reject(Error(data.erro)):resolve(data);
|
|
worker.onerror=e=>reject(Error(e.message));
|
|
worker.postMessage(msg,msg.bitmap?[msg.bitmap]:[]);
|
|
});
|
|
const config={rosto:true,maos:true};
|
|
await send({config});
|
|
const a=await createImageBitmap(await (await fetch('/test-portrait')).blob());
|
|
const face=await send({config,bitmap:a,timestamp:1000});
|
|
const b=await createImageBitmap(await (await fetch('/test-hands')).blob());
|
|
const hand=await send({config,bitmap:b,timestamp:2000});
|
|
worker.terminate();return {face,hand};
|
|
}""")
|
|
assert results['face']['faces'], 'Rosto da imagem oficial nao detectado'
|
|
assert results['hand']['hands'], 'Maos da imagem oficial nao detectadas'
|
|
assert len(results['hand']['hands'][0])==21
|
|
print('Inferencia real: %s rosto(s), %s mao(s); %.1f/%.1f ms' % (
|
|
len(results['face']['faces']),len(results['hand']['hands']),results['face']['ms'],results['hand']['ms']))
|
|
assert not external,external
|
|
assert not errors,errors
|
|
browser.close()
|
|
print('Inferencia real no worker e painel sem acesso externo: OK')
|
|
finally:
|
|
server.shutdown();server.server_close();thread.join()
|
|
|
|
if __name__=='__main__':main()
|