feat(sono): madrugada silenciosa e N sons para acordar

Antes um único som acima do limiar, um quadro de movimento ou um pulso
do PIR acordavam o rosto; deitado na cama, ele acordava toda hora.

- Som: precisa de `somAcordar` barulhos (padrão 3) em 1 minuto; o mesmo
  barulho comprido conta uma vez.
- Madrugada silenciosa (padrão 23h às 7h, configurável): movimento,
  rastreamento, PIR e modo presença não acordam; só barulho ALTO
  repetido, toque ou conversa.
- Vida: de madrugada não acorda sozinho e volta a dormir mesmo com gente
  se mexendo, a não ser que alguém tenha interagido nos últimos 10 min.
- Painel: grupo "Sono e madrugada" nos Ajustes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-06 04:48:11 -03:00
co-authored by Claude Opus 5.5
parent 5fcd038861
commit dd40c730be
8 changed files with 135 additions and 22 deletions
+11 -5
View File
@@ -25,7 +25,8 @@ SONO_LENTO = 0.25
RITMOS = {'calmo': (60, 150), 'normal': (25, 75), 'agitado': (12, 35)} # segundos entre atos
PADRAO = dict(ligado=True, criar_emojis=True, max_emojis_dia=3, ritmo='normal', noite=True)
NOITE = (23, 7) # dorme das 23h às 7h, se ninguém estiver por perto
NOITE = (23, 7) # horário padrão da madrugada (o servidor usa o dos ajustes do celular)
PEDIU_ATENCAO = 10 * 60 # de madrugada, só fica acordado se alguém interagiu (carinho, conversa) há pouco
INTERVALO_EMOJI = 90 * 60 # no mínimo 90 min entre duas figurinhas criadas sozinho
# O que cada estímulo faz com as necessidades (e o gesto que sai na hora, se houver).
@@ -100,6 +101,8 @@ class Vida:
self.alguem_perto = lambda: False # PIR/movimento recente
self.bloqueado = lambda: False # E-Stop ou modo presença com ninguém em casa
self.pode_gemini = lambda: bool(os.environ.get('GEMINI_API_KEY'))
self.madrugada = self.noite_padrao # o main troca pelo horário dos ajustes
self.interagiu_em = 0.0
agora = time.time()
self.config = dict(PADRAO)
@@ -251,6 +254,8 @@ class Vida:
self.necessidades[k] = _limitar(self.necessidades[k] + v)
if tipo in CUIDADOS:
self.anotar('cuidado', CUIDADOS[tipo], agora)
if tipo in CUIDADOS or tipo == 'conversa':
self.interagiu_em = agora # alguém quis ele acordado (sensor não conta)
if gesto and not self.bloqueado():
if self.dormindo():
self.acordar(agora, None)
@@ -271,7 +276,8 @@ class Vida:
if motivo:
self.anotar('sono', motivo, agora)
def noite(self, agora):
@staticmethod
def noite_padrao(agora):
hora = time.localtime(agora).tm_hour
ini, fim = NOITE
return hora >= ini or hora < fim
@@ -340,12 +346,12 @@ class Vida:
n = self.necessidades
perto = self.alguem_perto()
if self.dormindo():
# acorda descansado de dia, ou sozinho quando a energia enche e tem alguém por perto
if n['energia'] >= 95 and (not self.noite(agora) or perto):
# de madrugada nunca acorda sozinho (nem com gente se mexendo na cama); de dia, quando descansou
if n['energia'] >= 95 and not self.madrugada(agora):
self.acordar(agora, 'Acordou descansado')
return 'acordar'
return None
if self.config['noite'] and self.noite(agora) and not perto:
if self.config['noite'] and self.madrugada(agora) and agora - self.interagiu_em > PEDIU_ATENCAO:
self.dormir(agora, 'Ficou tarde, foi dormir')
return 'dormir'
if n['energia'] < 20: