feat(vida): modo autônomo, o Carinha como bichinho virtual

Novo módulo vida.py: energia, alegria, companhia e diversão caem com o
tempo e sobem com presença (PIR), movimento, conversa e carinho. Delas sai
o humor e, do humor, os atos: gestos, cochilo quando cansado, dormir à
noite sem ninguém por perto, acenar pedindo atenção quando carente com
gente perto, tocar reações da biblioteca e inventar figurinhas novas com
o Gemini (no máximo 3 por dia, 90 min entre elas).

- O celular recebe "humor x" e escolhe os gestos soltos por ele.
- Toque na tela do celular conta como carinho/cutucada (/vida/cuidar).
- Respeita E-Stop e o modo presença (ninguém em casa = quieto).
- Painel: cartão Vida no Ao vivo com humor, barras, cuidados, ritmo e
  diário; liga por padrão.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-06 04:21:23 -03:00
co-authored by Claude Opus 5.5
parent 8d410dbf24
commit b456f80716
11 changed files with 1036 additions and 16 deletions
+31 -4
View File
@@ -41,6 +41,7 @@ from conversa import Conversa
from treinamento import Treinamento
from api_config import ConfigAPI
from historico import Historico
from vida import Vida
TRAVA = threading.Lock()
@@ -70,7 +71,7 @@ ESTATICOS = {"/carinha.html", "/camera.html", "/reacoes-render.js", "/rastreador
# Rotas que não exigem token: só o necessário para a página do rosto abrir e o app se achar.
PUBLICAS_GET = {"/app/health", "/faces", "/gestos", "/config", "/ia/modelo", "/app.apk"}
PREFIXOS_API = ("/eventos", "/estado", "/fila", "/rtc/", "/conversa/", "/treinamento/", "/historico/",
PREFIXOS_API = ("/eventos", "/estado", "/fila", "/rtc/", "/conversa/", "/treinamento/", "/historico/", "/vida/",
"/app/", "/braco/", "/servos/", "/ia/", "/api/", "/gesto/", "/face/", "/diag/")
@@ -237,7 +238,7 @@ class Handler(SimpleHTTPRequestHandler):
def modulos(self):
s = self.server
return [getattr(s, nome) for nome in ("api_config", "historico", "treinamento", "conversa", "cerebro",
return [getattr(s, nome) for nome in ("api_config", "historico", "vida", "treinamento", "conversa", "cerebro",
"braco", "servos", "controle") if hasattr(s, nome)]
def estado(self):
@@ -262,6 +263,8 @@ class Handler(SimpleHTTPRequestHandler):
dados["api"] = s.api_config.status()
if hasattr(s, "historico"):
dados["pir"] = dict(s.historico.pir, presenca=dict(s.historico.presenca))
if hasattr(s, "vida"):
dados["vida"] = s.vida.status()
return dados
def do_GET(self):
@@ -454,7 +457,7 @@ class Servidor(ThreadingHTTPServer):
def ligar_eventos(servidor):
for nome in ("controle", "servos", "cerebro", "conversa", "treinamento", "historico"):
for nome in ("controle", "servos", "cerebro", "conversa", "treinamento", "historico", "vida"):
if hasattr(servidor, nome):
getattr(servidor, nome).publicar = EVENTOS.publicar
@@ -479,10 +482,34 @@ def main():
servidor.cerebro.chave = servidor.token
servidor.conversa = Conversa(Path.cwd() / 'memoria.db', servidor.cerebro)
servidor.treinamento = Treinamento(Path.cwd() / 'treinamento.db')
servidor.conversa.memoria.on_registrar = servidor.treinamento.registrar
servidor.historico = Historico(Path.cwd())
servidor.vida = vida = Vida(Path.cwd() / 'vida.json')
def registrar_conversa(papel, texto):
servidor.treinamento.registrar(papel, texto)
if papel == 'user':
vida.estimulo('conversa') # alguém conversou com ele: companhia
servidor.conversa.memoria.on_registrar = registrar_conversa
def celular_dormindo():
resumo = servidor.controle.resumo()
return bool(resumo['estado'].get('dormindo')) if resumo['online'] else None
def alguem_perto():
h = servidor.historico
recente = max(h.pir.get('ultimo') or 0, h.ultimo.get('movimento', 0))
return bool(h.pir.get('ativo')) or time.time() - recente < 120
vida.enfileirar = enfileirar
vida.treinamento = servidor.treinamento
vida.celular_dormindo = celular_dormindo
vida.celular_online = lambda: servidor.controle.resumo()['online']
vida.alguem_perto = alguem_perto
# E-Stop parado, ou modo presença com ninguém em casa (dormindo em silêncio): não faz nada
vida.bloqueado = lambda: ESTOP["ativo"] or servidor.historico.presenca.get("estado") == "ausente"
def presenca(tipo):
vida.estimulo(tipo)
# PIR viu alguém: se o rosto estiver dormindo (e talvez com a tela apagada), acorda
if tipo == "pir":
with servidor.controle.lock: