# ============================================================================= # 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