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
+32 -3
View File
@@ -741,6 +741,27 @@ const MIC = {
cooldown: parseFloat(par.get("somcooldown") || "5"), // s entre sustos
};
/* Acordar com som: precisa de N barulhos em 1 minuto (ajuste "Sons para acordar"). Na madrugada
silenciosa só barulho ALTO conta, e movimento/câmera não acordam (alguém se mexendo na cama). */
const DESPERTAR = { sons: 3, janela: 60, noite: true, ini: 23, fim: 7, eventos: [], ultimo: -Infinity, estava: false };
function madrugada() {
if (!DESPERTAR.noite || DESPERTAR.ini === DESPERTAR.fim) return false;
const h = new Date().getHours();
return DESPERTAR.ini < DESPERTAR.fim ? h >= DESPERTAR.ini && h < DESPERTAR.fim : h >= DESPERTAR.ini || h < DESPERTAR.fim;
}
/* Conta o INÍCIO de cada barulho (o mesmo barulho comprido vale um) e diz se já dá para acordar. */
function barulhoAcorda(ouviu, agora) {
const D = DESPERTAR;
if (ouviu && !D.estava && agora - D.ultimo >= 1.5) {
D.ultimo = agora;
D.eventos = D.eventos.filter((t) => agora - t < D.janela).concat(agora);
console.log("ouvido: barulho " + D.eventos.length + "/" + D.sons + (madrugada() ? " (madrugada)" : ""));
}
D.estava = ouviu;
if (D.eventos.length >= D.sons && agora - D.eventos[D.eventos.length - 1] < 1) { D.eventos = []; return true; }
return false;
}
function som(nivel) {
const n = Math.max(0, Math.min(1, Number(nivel) || 0));
const agora = performance.now() / 1000;
@@ -765,12 +786,14 @@ function som(nivel) {
console.log("ouvido nivel=" + n.toFixed(2) + " piso=" + (MIC.piso || 0).toFixed(2));
}
if (r.dormindo && temSom) { // acordou: tinha barulho
if (r.dormindo) {
if (barulhoAcorda(madrugada() ? alto : temSom, agora)) { // acordou: barulho repetido
r.dormindo = false;
animar("surpreso");
MIC.proxSurpresa = agora + MIC.cooldown;
console.log("ouvido: acordou nivel=" + n.toFixed(2));
relatarSom("acordou", n);
}
} else if (alto && !MIC.estavaAlto && agora >= MIC.proxSurpresa) {
MIC.proxSurpresa = agora + MIC.cooldown; // susto: barulho alto e repentino
animar("surpreso");
@@ -857,7 +880,7 @@ function rastrear(t) {
MOV.olharY+=((t.y-.5)*2*MOV.olharMax*.6-MOV.olharY)*.65;
if(t.atividade===true) {
MOV.ultimoMov=agora;
if(r.dormindo) { r.dormindo=false;animar('surpreso'); }
if(r.dormindo && !madrugada()) { r.dormindo=false;animar('surpreso'); }
}
}
function movimento(vel, cx, cy) {
@@ -887,12 +910,14 @@ function movimento(vel, cx, cy) {
}
const forte = v >= MOV.limiarForte;
if (r.dormindo && mexeu) { // acordou: algo se mexeu na frente
if (r.dormindo) {
if (mexeu && !madrugada()) { // acordou: algo se mexeu na frente (de dia)
r.dormindo = false;
animar("surpreso");
MOV.proxSurpresa = agora + MOV.cooldown;
console.log("presenca: acordou nivel=" + v.toFixed(2));
relatarMov("acordou", v);
}
} else if (forte && !MOV.estavaForte && agora >= MOV.proxSurpresa) {
// susto so no INICIO do movimento forte: se continuar se mexendo, nao repete
MOV.proxSurpresa = agora + MOV.cooldown;
@@ -1040,6 +1065,10 @@ function configurarApp(c) {
EXTRAS.boca = c.boca !== false; EXTRAS.sobrancelhas = c.sobrancelhas === true; EXTRAS.iris = c.iris === true;
if (typeof c.subirBoca === 'number') EXTRAS.subirBoca = c.subirBoca;
SONO.apagar = c.apagarTela !== false;
if (typeof c.somAcordar === 'number') DESPERTAR.sons = Math.max(1, Math.round(c.somAcordar));
DESPERTAR.noite = c.noiteSilenciosa !== false;
if (typeof c.noiteInicio === 'number') DESPERTAR.ini = Math.round(c.noiteInicio);
if (typeof c.noiteFim === 'number') DESPERTAR.fim = Math.round(c.noiteFim);
if (typeof c.apagarTelaMin === 'number') SONO.minutos = c.apagarTelaMin;
if (typeof c.distSobrancelha === 'number') EXTRAS.distSobrancelha = c.distSobrancelha;
AUTO = c.auto;