3번째 Pi(신규 지점, 누수+카메라)를 코드 수정 없이 배포 가능하게: - raspberry_pi4/config.py: config_local.py 에서 SENSORS 오버라이드 허용 + fail-closed 형식 검증(id 1..255/gpio/name, 중복 금지) — 오설정으로 기본 sensor_id 1 로 조용히 기동해 타 지점 상태를 덮어쓰는 사고 방지. - REPORT_INTERVAL 3600→600: 서버 HEARTBEAT_TIMEOUT_SEC(1200) 절반 — 기존 하트비트 오탐(매시간 오프라인/복귀 SMS) 해소. - php/config.php LEAK_SENSOR_IDS 상수(기본 [1], 1..255 정규화·빈값 복귀). - php/dashboard.php: 누수 카드 sensor_id===1 하드코딩 → LEAK_SENSOR_IDS 루프. - php/api/sensor_data.php: periodic 보고 전면 skip_log(누수 노드 증가 시 이벤트 로그 창 보존) + CORS 헤더 제거(서버간 POST 전용, 도메인 지문 노출 차단). - test_connection.py(양 모듈): 하드코딩 sensor_id 제거→기기 SENSORS 기반, 서명 200 실패 시 exit 1(setup.sh 원샷 게이트용). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01821XP36Q6t9eWKTPSEumnC
113 lines
6.6 KiB
Python
113 lines
6.6 KiB
Python
# =============================================================================
|
|
# config.py - 누수감지 시스템 v2 (Pi 4 Model B) 설정 — 비밀이 아닌 기본값
|
|
#
|
|
# 서버 URL / API_KEY / 기기 식별자 등 운영·비밀값은 config_local.py 에서 로드한다.
|
|
# config_local.py 는 .gitignore 로 커밋 제외 대상이다.
|
|
# 설정 방법: cp config_local.example.py config_local.py → 실제 값으로 채움
|
|
# =============================================================================
|
|
|
|
# ─────────────────────────────────────────────
|
|
# 센서 GPIO 설정 (BCM 번호 기준 — Pi 4 40핀 헤더와 동일)
|
|
# GPIO17 = 40핀 헤더 물리 11번 핀
|
|
# ─────────────────────────────────────────────
|
|
SENSORS = [
|
|
{"id": 1, "gpio": 17, "name": "1번 센서 (서버실)"},
|
|
# 추가 센서가 있으면 아래에 추가 (BCM 번호 기준)
|
|
# {"id": 2, "gpio": 27, "name": "2번 센서 (기계실)"}, # 물리 13번
|
|
# {"id": 3, "gpio": 22, "name": "3번 센서 (화장실)"}, # 물리 15번
|
|
]
|
|
|
|
# 센서 활성 레벨 설정
|
|
# e-con 5C-08 저항형 센서 + 4.7MΩ 풀다운 사용 시:
|
|
# 건조: GPIO = LOW(0) → ACTIVE_LOW = False
|
|
# 누수: GPIO = HIGH(1)
|
|
ACTIVE_LOW = False
|
|
|
|
# GPIO 내부 풀저항 설정
|
|
# 반드시 False (PUD_OFF) 유지!
|
|
# 내부 풀다운(50kΩ)을 켜면 누수 시에도 HIGH가 되지 않아 감지 실패함.
|
|
# → 외부 4.7MΩ 풀다운 저항만 사용
|
|
USE_INTERNAL_PULL = False # True = PUD_DOWN, False = PUD_OFF
|
|
|
|
# ─────────────────────────────────────────────
|
|
# 주기 설정
|
|
# ─────────────────────────────────────────────
|
|
REPORT_INTERVAL = 600 # 정상 상태 주기 보고 간격 (초) = 10분
|
|
# 서버 HEARTBEAT_TIMEOUT_SEC(기본 1200초)보다 짧아야
|
|
# 오프라인 오탐이 없다 (600초 = 2회 누락 시 오프라인 판정).
|
|
LEAK_REPORT_INTERVAL = 10 # 누수 감지 시 재보고 간격 (초)
|
|
DEBOUNCE_MS = 500 # 바운싱 방지 시간 (ms) — 고임피던스라 넉넉히 설정
|
|
|
|
# ─────────────────────────────────────────────
|
|
# 로그 설정
|
|
# ─────────────────────────────────────────────
|
|
LOG_FILE = "/var/log/leak_sensor.log"
|
|
LOG_MAX_BYTES = 5 * 1024 * 1024 # 5MB
|
|
LOG_BACKUP_COUNT = 3
|
|
|
|
# ─────────────────────────────────────────────
|
|
# 재시도 설정
|
|
# ─────────────────────────────────────────────
|
|
HTTP_TIMEOUT = 10 # HTTP 요청 타임아웃 (초)
|
|
HTTP_RETRY_COUNT = 3 # 재시도 횟수
|
|
HTTP_RETRY_DELAY = 5 # 재시도 대기 (초)
|
|
|
|
# ─────────────────────────────────────────────
|
|
# 카메라 설정
|
|
# Pi 4 = 15핀(1.0mm 피치) 표준 CSI, Zero 2 W = 22핀(0.5mm 피치) CSI →
|
|
# 리본/카메라 보드 호환 안 됨 (Pi 4용 15핀 리본 또는 15↔22 변환 어댑터 필요).
|
|
# 촬영은 rpicam-still(Bookworm) / libcamera-still(Bullseye) 사용.
|
|
# ─────────────────────────────────────────────
|
|
CAMERA_ENABLED = True # False로 설정하면 촬영/업로드 건너뜀
|
|
PHOTO_WIDTH = 640 # 촬영 해상도 (가로)
|
|
PHOTO_HEIGHT = 480 # 촬영 해상도 (세로)
|
|
PHOTO_QUALITY = 60 # JPEG 품질 (1-100, 60 ≈ 50-80KB)
|
|
|
|
# ─────────────────────────────────────────────
|
|
# 운영·비밀값 로드 (fail closed)
|
|
# config_local.py 가 없으면 placeholder/하드코딩 비밀로 잘못 기동하는 대신
|
|
# 명확한 오류로 즉시 중단한다.
|
|
# ─────────────────────────────────────────────
|
|
try:
|
|
from config_local import ( # noqa: F401 (leak_sensor.py 가 config 에서 재수입)
|
|
API_URL,
|
|
API_KEY,
|
|
PHOTO_UPLOAD_URL,
|
|
DEVICE_ID,
|
|
DEVICE_LOCATION,
|
|
)
|
|
except ImportError as e:
|
|
raise RuntimeError(
|
|
"config_local.py 를 찾을 수 없습니다. "
|
|
"`cp config_local.example.py config_local.py` 후 실제 서버 URL / API_KEY / "
|
|
"기기 정보를 채우세요. (비밀값은 절대 커밋하지 마세요.)"
|
|
) from e
|
|
|
|
# ─────────────────────────────────────────────
|
|
# 선택 오버라이드 — 기기별 값을 config_local.py 에서 덮어쓸 수 있다.
|
|
# 추적 파일(config.py)을 기기마다 수정하면 배포 드리프트가 생기므로,
|
|
# 신규 지점 노드(예: sensor_id 3)는 config_local.py 에 SENSORS 를 정의한다.
|
|
# ─────────────────────────────────────────────
|
|
try:
|
|
from config_local import SENSORS # noqa: F811 — 기기별 센서 목록 오버라이드
|
|
except ImportError:
|
|
pass # config_local 에 SENSORS 가 없으면 위 기본값 사용
|
|
|
|
# SENSORS 형식 검증 (fail closed) — 오버라이드 오타/형식 오류로 잘못된 sensor_id
|
|
# (예: 기본값 1)로 조용히 기동해 다른 지점 상태를 덮어쓰는 사고를 막는다.
|
|
if not isinstance(SENSORS, list) or not SENSORS:
|
|
raise RuntimeError("SENSORS 는 비어있지 않은 list 여야 합니다 (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 항목 형식 오류: {_s!r} — id(int 1..255)/gpio(int)/name(str) 필수."
|
|
)
|
|
if _s["id"] in _ids or _s["gpio"] in _gpios:
|
|
raise RuntimeError(f"SENSORS id/gpio 중복: {_s!r}")
|
|
_ids.add(_s["id"])
|
|
_gpios.add(_s["gpio"])
|
|
del _ids, _gpios
|