센서1·3(누수 노드)에 PMS7003 를 온습도와 동일 sensor_id 로 통합하고 metric_type='pms7003'
로 구분해 수집한다. LED 는 스위칭 없이 상시 점등 방식으로 확정.
- raspberry_pi4_pms7003/: PMS7003 노드 풀세트(드라이버·센서·install·systemd·vendor·배선·연결테스트·프레임 단위테스트)
- 서버: evaluate_metric_thresholds() 에 pm25/pm10 선택 인자 추가(온습도 전용 호출 후방호환),
sensor_data.php 수집 경로에 pms7003 metric 반영, config.php/예시에 PM 임계 추가
- SQL: migration_pms7003.sql · schema_sht30.sql 정합, tools/migration_leak_add_{pms7003,sht30} 마이그레이터
- tests/pm_threshold_test.php + run_tests.sh 러너 등록
- .gitignore: 노드별 config_local.py 커밋 차단
121 lines
7 KiB
Python
121 lines
7 KiB
Python
# =============================================================================
|
||
# config.py - Leak Detection System v2 (Pi 4 Model B) settings — non-secret defaults
|
||
#
|
||
# Operational/secret values such as server URL / API_KEY / device identifier are loaded from config_local.py.
|
||
# config_local.py is excluded from commits via .gitignore.
|
||
# Setup: cp config_local.example.py config_local.py → fill in with real values
|
||
# =============================================================================
|
||
|
||
# ─────────────────────────────────────────────
|
||
# Sensor GPIO settings (BCM numbering — same as Pi 4 40-pin header)
|
||
# GPIO17 = physical pin 11 on the 40-pin header
|
||
# ─────────────────────────────────────────────
|
||
SENSORS = [
|
||
{"id": 1, "gpio": 17, "name": "Sensor 1 (Server Room)"},
|
||
# Add more sensors below if present (BCM numbering)
|
||
# {"id": 2, "gpio": 27, "name": "Sensor 2 (Machine Room)"}, # physical pin 13
|
||
# {"id": 3, "gpio": 22, "name": "Sensor 3 (Restroom)"}, # physical pin 15
|
||
]
|
||
|
||
# Sensor active-level setting
|
||
# When using the e-con 5C-08 resistive sensor + 10MΩ pull-down:
|
||
# Dry: GPIO = LOW(0) → ACTIVE_LOW = False
|
||
# Leak: GPIO = HIGH(1)
|
||
ACTIVE_LOW = False
|
||
|
||
# GPIO internal pull-resistor setting
|
||
# Must stay False (PUD_OFF)!
|
||
# Enabling the internal pull-down (50kΩ) keeps the line from going HIGH even on a leak, causing detection failure.
|
||
# → Use only the external 10MΩ pull-down resistor
|
||
USE_INTERNAL_PULL = False # True = PUD_DOWN, False = PUD_OFF
|
||
|
||
# ─────────────────────────────────────────────
|
||
# Interval settings
|
||
# ─────────────────────────────────────────────
|
||
REPORT_INTERVAL = 600 # Normal-state periodic report interval (sec) = 10 min
|
||
# Must be shorter than the server's HEARTBEAT_TIMEOUT_SEC (default 1200 sec)
|
||
# to avoid offline false positives (600 sec = offline after 2 missed reports).
|
||
LEAK_REPORT_INTERVAL = 10 # Re-report interval while a leak is detected (sec)
|
||
DEBOUNCE_MS = 500 # Debounce time (ms) — set generously due to high impedance
|
||
|
||
# Confirm-read debounce: an edge alone never changes state. After an edge the pin is
|
||
# re-read CONFIRM_READS times, CONFIRM_INTERVAL_SEC apart; ALL reads must agree with the
|
||
# new state or the event is discarded as noise. Real water persists; sub-second EMI
|
||
# blips on the 10MΩ high-impedance line (server-room noise) do not.
|
||
# Total confirmation latency ≈ CONFIRM_READS × CONFIRM_INTERVAL_SEC ≈ 0.9 sec.
|
||
CONFIRM_READS = 3 # Consecutive identical reads required to accept a change
|
||
CONFIRM_INTERVAL_SEC = 0.3 # Delay between confirmation reads (sec)
|
||
|
||
# ─────────────────────────────────────────────
|
||
# Log settings
|
||
# ─────────────────────────────────────────────
|
||
LOG_FILE = "/var/log/leak_sensor.log"
|
||
LOG_MAX_BYTES = 5 * 1024 * 1024 # 5MB
|
||
LOG_BACKUP_COUNT = 3
|
||
|
||
# ─────────────────────────────────────────────
|
||
# Retry settings
|
||
# ─────────────────────────────────────────────
|
||
HTTP_TIMEOUT = 10 # HTTP request timeout (sec)
|
||
HTTP_RETRY_COUNT = 3 # Retry count
|
||
HTTP_RETRY_DELAY = 5 # Retry delay (sec)
|
||
|
||
# ─────────────────────────────────────────────
|
||
# Camera settings
|
||
# Pi 4 = 15-pin (1.0mm pitch) standard CSI, Zero 2 W = 22-pin (0.5mm pitch) CSI →
|
||
# ribbon/camera board not compatible (needs a Pi 4 15-pin ribbon or a 15↔22 adapter).
|
||
# Capture uses rpicam-still (Bookworm) / libcamera-still (Bullseye).
|
||
# ─────────────────────────────────────────────
|
||
CAMERA_ENABLED = True # Set to False to skip capture/upload
|
||
PHOTO_WIDTH = 640 # Capture resolution (width)
|
||
PHOTO_HEIGHT = 480 # Capture resolution (height)
|
||
PHOTO_QUALITY = 60 # JPEG quality (1-100, 60 ≈ 50-80KB)
|
||
|
||
# ─────────────────────────────────────────────
|
||
# Load operational/secret values (fail closed)
|
||
# If config_local.py is missing, stop immediately with a clear error
|
||
# instead of wrongly starting with placeholder/hardcoded secrets.
|
||
# ─────────────────────────────────────────────
|
||
try:
|
||
from config_local import ( # noqa: F401 (leak_sensor.py re-imports from config)
|
||
API_URL,
|
||
API_KEY,
|
||
PHOTO_UPLOAD_URL,
|
||
DEVICE_ID,
|
||
DEVICE_LOCATION,
|
||
)
|
||
except ImportError as e:
|
||
raise RuntimeError(
|
||
"config_local.py not found. "
|
||
"Run `cp config_local.example.py config_local.py`, then fill in the real server URL / API_KEY / "
|
||
"device info. (Never commit secret values.)"
|
||
) from e
|
||
|
||
# ─────────────────────────────────────────────
|
||
# Optional override — per-device values can be overridden in config_local.py.
|
||
# Editing the tracked file (config.py) per device causes deployment drift, so
|
||
# define SENSORS in config_local.py for a new site node (e.g. sensor_id 3).
|
||
# ─────────────────────────────────────────────
|
||
try:
|
||
from config_local import SENSORS # noqa: F811 — per-device sensor list override
|
||
except ImportError:
|
||
pass # use the default above if config_local has no SENSORS
|
||
|
||
# SENSORS format validation (fail closed) — prevents an override typo/format error from
|
||
# silently starting with a wrong sensor_id (e.g. default 1) and overwriting another site's state.
|
||
if not isinstance(SENSORS, list) or not SENSORS:
|
||
raise RuntimeError("SENSORS must be a non-empty list (check config_local.py).")
|
||
_ids, _gpios = set(), set()
|
||
for _s in SENSORS:
|
||
if not (isinstance(_s, dict)
|
||
and isinstance(_s.get("id"), int) and 1 <= _s["id"] <= 255
|
||
and isinstance(_s.get("gpio"), int)
|
||
and isinstance(_s.get("name"), str) and _s["name"]):
|
||
raise RuntimeError(
|
||
f"SENSORS entry format error: {_s!r} — id(int 1..255)/gpio(int)/name(str) required."
|
||
)
|
||
if _s["id"] in _ids or _s["gpio"] in _gpios:
|
||
raise RuntimeError(f"SENSORS duplicate id/gpio: {_s!r}")
|
||
_ids.add(_s["id"])
|
||
_gpios.add(_s["gpio"])
|
||
del _ids, _gpios
|