Nucleo propio: oye con whisper.cpp, piensa con un modelo de Ollama, habla con Piper, y hace RAG sobre los apuntes del usuario. 100% local, sin cuentas ni claves. Escrito bajo una restriccion dura, 4 GB de VRAM: el cerebro y whisper comparten tarjeta y solo caben porque estan dimensionados para ello. El RAG usa embeddings estaticos con busqueda hibrida; la voz clonada se sirve de una cache de frases. Incluye instalador (install.sh), requisitos, y documentacion del stack, del manejo de root y de las acciones. Los apuntes indexados y el diario NO se incluyen: son privados y el .gitignore los bloquea.
133 lines
4.6 KiB
Python
Executable file
133 lines
4.6 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""El candado: que tape, que rechace lo que no es, y que NO obedezca.
|
|
|
|
./venv/bin/python pruebas/prueba_candado.py
|
|
|
|
Lo que de verdad importa aqui no es que el panel se ponga negro —eso es
|
|
cosmetica— sino que con el candado puesto **no se ejecute nada**. El panel tapado
|
|
pero obedeciendo seria peor que no tener candado, porque da sensacion de
|
|
seguridad sin darla.
|
|
|
|
No se prueba con la contraseña de verdad: no la tenemos ni hace falta. Se
|
|
comprueba que una mala se rechaza y que mientras tanto no pasa ni una orden.
|
|
"""
|
|
import os
|
|
import sys
|
|
import threading
|
|
import time
|
|
|
|
AQUI = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
sys.path.insert(0, AQUI)
|
|
|
|
from boca.voz import Boca # noqa: E402
|
|
from cara.panel import Panel # noqa: E402
|
|
from manos import Manos # noqa: E402
|
|
|
|
fallos = []
|
|
|
|
|
|
def afirma(condicion, queja):
|
|
print(f" {'OK ' if condicion else 'MAL'} {queja}")
|
|
if not condicion:
|
|
fallos.append(queja)
|
|
|
|
|
|
class CaraFalsa:
|
|
"""Se queda con lo que el panel manda, sin levantar ningun servidor."""
|
|
|
|
def __init__(self):
|
|
self.mensajes = []
|
|
self.conectados = 1
|
|
|
|
def manda(self, tipo, datos):
|
|
self.mensajes.append((tipo, datos))
|
|
|
|
def de(self, tipo):
|
|
return [d for t, d in self.mensajes if t == tipo]
|
|
|
|
|
|
class BocaMuda(Boca):
|
|
"""Como la de verdad pero sin sonar: la prueba no tiene que hablar."""
|
|
|
|
def __init__(self):
|
|
super().__init__("davefx")
|
|
self.dicho = []
|
|
|
|
def di(self, texto):
|
|
self.dicho.append(texto); return True
|
|
|
|
def di_si_libre(self, texto):
|
|
self.dicho.append(texto); return True
|
|
|
|
|
|
def main() -> int:
|
|
manos = Manos()
|
|
cara = CaraFalsa()
|
|
boca = BocaMuda()
|
|
p = Panel(cara, boca, manos, candado=True)
|
|
|
|
print("\n al abrir")
|
|
p.arranca()
|
|
time.sleep(0.2)
|
|
afirma(not p.desbloqueado, "nace bloqueado")
|
|
afirma(bool(cara.de("bloqueo")), "pide la contraseña nada mas abrir")
|
|
|
|
print("\n con el candado puesto NO obedece")
|
|
antes = len(cara.mensajes)
|
|
for orden in ("prueba", "voz:siguiente", "parar"):
|
|
p.pulsado("accion", orden)
|
|
time.sleep(1.0)
|
|
afirma(not boca.dicho, f"no ha hablado ({boca.dicho})")
|
|
# Desde que la tanda va por tareas, lo que se ve al ejecutar son mensajes
|
|
# "tarea" (una tarjeta por orden), no "respuesta".
|
|
hechas = [d for t, d in cara.mensajes[antes:] if t in ("tarea", "respuesta")]
|
|
afirma(not hechas, f"no ha ejecutado nada ({len(hechas)} tareas o respuestas)")
|
|
# Solo se miran las respuestas a lo pulsado: la telemetria sigue latiendo
|
|
# en su hilo cada segundo y se cuela entre medias.
|
|
contestaciones = [t for t, _ in cara.mensajes[antes:] if t != "telemetria"]
|
|
afirma(contestaciones and all(t == "bloqueo" for t in contestaciones),
|
|
f"a cada intento contesta pidiendo la contraseña ({contestaciones})")
|
|
|
|
print("\n una contraseña mala se rechaza")
|
|
antes = len(cara.mensajes)
|
|
p.pulsado("accion", "desbloquear:no-es-esta-contrasena-12345")
|
|
time.sleep(0.5)
|
|
afirma(not p.desbloqueado, "sigue bloqueado")
|
|
ultimos = cara.de("bloqueo")[-1:] or [{}]
|
|
afirma(ultimos[0].get("error") is True, "y lo dice como error")
|
|
afirma(not cara.de("desbloqueado"), "no manda 'desbloqueado'")
|
|
|
|
print("\n una vacia ni se intenta")
|
|
antes = len(cara.mensajes)
|
|
p.pulsado("accion", "desbloquear:")
|
|
afirma(len(cara.mensajes) == antes, "no molesta a sudo con una clave vacia")
|
|
|
|
print("\n sin candado, obedece")
|
|
cara2, boca2 = CaraFalsa(), BocaMuda()
|
|
p2 = Panel(cara2, boca2, manos, candado=False)
|
|
p2.arranca()
|
|
afirma(p2.desbloqueado, "nace desbloqueado")
|
|
afirma(not cara2.de("bloqueo"), "no pide contraseña")
|
|
p2.pulsado("accion", "prueba")
|
|
time.sleep(3.0)
|
|
tarjetas = {d["id"] for d in cara2.de("tarea")}
|
|
afirma(bool(tarjetas), f"ejecuta de verdad ({len(tarjetas)} tarjetas)")
|
|
afirma(any(d["estado"] == "hecha" for d in cara2.de("tarea")),
|
|
"y las tareas llegan a terminar")
|
|
|
|
print("\n y se puede volver a echar")
|
|
p2.bloquea()
|
|
afirma(not p2.desbloqueado, "bloquea() cierra aunque arrancara sin candado")
|
|
antes = len(cara2.mensajes)
|
|
p2.pulsado("accion", "prueba")
|
|
time.sleep(0.6)
|
|
afirma(not [d for t, d in cara2.mensajes[antes:] if t in ("tarea", "respuesta")],
|
|
"y despues de cerrar ya no obedece")
|
|
|
|
p.para(); p2.para()
|
|
print(f"\n {'todo en orden' if not fallos else str(len(fallos)) + ' fallan'}\n")
|
|
return 1 if fallos else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|