누수 감지↔복구 플래핑 SMS 폭탄 방지, 온습도 측정 주기 1시간화(부수 오탐
차단), 센서별 SMS 수신자 그룹 지정 3종을 함께 반영한다.
1) 누수 디바운스 (서버 전용, 펌웨어 무변경)
- leak_sensor_status 에 leak_confirmed / leak_pending_since 컬럼 추가.
- leak_debounce_decide(): 단일 대칭 타이머 순수 함수. raw 상태가 확인창
동안 지속돼야 확정하고 확정 엣지에서만 SMS 1회. dry 블립<복구창이면
복구 미확정 → 확정 유지 → 재감지·복구 SMS 둘 다 차단(폭탄 원천 봉쇄).
- apply_leak_debounce()/sweep_pending_leak_transitions(): 센서 네임드 락으로
직렬화, cron sweep 으로 보고 끊겨도 확정 해소. 사고(leak_incident)
open/close 도 확정 엣지로 이동(월간보고 사고건수 정합). raw 이벤트는
leak_sensor_log 에 그대로 보존(플래핑 진단).
- 컬럼 없는 구배포는 레거시 즉시 발송으로 graceful degrade.
- 확인창 기본: 감지 60s / 복구 180s (config.local.php 조정).
2) 온습도 1시간 주기 + 센서별 히트비트 타임아웃
- raspberry_pi4_sht30/config.py REPORT_INTERVAL 300→3600.
- heartbeat_timeout_for(): 명시 오버라이드 > 온습도 기본(7500s) > 전역(1200s).
cron_heartbeat / dashboard 오프라인 판정을 센서별로. 매시간 오탐 제거,
누수 오프라인은 20분 유지.
- STUCK_DETECT_SAMPLES 6→3(1시간 주기 ≈3시간), 관련 주석 정정.
3) 센서별 수신자 그룹
- SENSOR_RECIPIENTS 맵 + recipients_for_sensor() 전역 폴백.
- sms_dispatch_to_recipients 가 센서별로 수신자 해소(에스컬레이션 TIER2
명시 전달은 유지, 헬스/잔액은 전역).
대시보드: 누수 카드 '확인중(디바운스)' 배지, 시스템 탭에 확인창·센서별
타임아웃·수신자 그룹 표시.
마이그레이션: sql/migration_leak_debounce.sql (컬럼 2개), schema_sht30.sql 갱신.
테스트: leak_debounce(12) / recipients_group / heartbeat_timeout 순수 단위 추가.
설계: docs/superpowers/specs/2026-07-13-leak-debounce-sensor-config-design.md
50 lines
1.9 KiB
PHP
50 lines
1.9 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
// 센서별 히트비트(오프라인 판정) 타임아웃 해소 순수 로직 테스트 (DB/네트워크 없음).
|
|
// resolve_heartbeat_timeout(): 명시 오버라이드 > 온습도(METRIC) 기본 > 전역 기본.
|
|
|
|
$root = dirname(__DIR__);
|
|
require_once $root . '/php/config.php'; // resolve_heartbeat_timeout() 포함
|
|
|
|
$failures = [];
|
|
function check(bool $cond, string $msg): void {
|
|
global $failures;
|
|
if (!$cond) { $failures[] = $msg; }
|
|
}
|
|
|
|
check(function_exists('resolve_heartbeat_timeout'), 'resolve_heartbeat_timeout() should exist.');
|
|
|
|
$overrides = [3 => 900]; // 3번만 명시 오버라이드
|
|
$metricIds = [2, 4]; // 온습도 센서
|
|
$metricDef = 7500;
|
|
$globalDef = 1200;
|
|
|
|
// ── 1) 명시 오버라이드 최우선(온습도여도) ──
|
|
check(resolve_heartbeat_timeout(3, $overrides, $metricIds, $metricDef, $globalDef) === 900,
|
|
'explicit override wins');
|
|
|
|
// ── 2) 온습도 센서 → METRIC 기본 ──
|
|
check(resolve_heartbeat_timeout(2, $overrides, $metricIds, $metricDef, $globalDef) === 7500,
|
|
'metric sensor -> metric default');
|
|
check(resolve_heartbeat_timeout(4, $overrides, $metricIds, $metricDef, $globalDef) === 7500,
|
|
'metric sensor 4 -> metric default');
|
|
|
|
// ── 3) 그 외(누수 등) → 전역 기본 ──
|
|
check(resolve_heartbeat_timeout(1, $overrides, $metricIds, $metricDef, $globalDef) === 1200,
|
|
'leak/other sensor -> global default');
|
|
|
|
// ── 4) 명시 오버라이드가 온습도 기본을 이긴다 ──
|
|
$ov2 = [2 => 4000];
|
|
check(resolve_heartbeat_timeout(2, $ov2, $metricIds, $metricDef, $globalDef) === 4000,
|
|
'explicit override beats metric default for a metric sensor');
|
|
|
|
if ($failures) {
|
|
fwrite(STDERR, "Heartbeat timeout test failures:\n");
|
|
foreach ($failures as $f) {
|
|
fwrite(STDERR, "- {$f}\n");
|
|
}
|
|
exit(1);
|
|
}
|
|
|
|
echo "Heartbeat timeout resolution checks passed.\n";
|