POSA_LEAKSMS/raspberry_pi4_pms7003/config.py
cw2007 ec78be0bda feat(pms7003): 누수 노드에 미세먼지(PM2.5/PM10) 통합
센서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 커밋 차단
2026-07-19 10:46:33 +09:00

124 lines
7.7 KiB
Python

# =============================================================================
# config.py - Particulate-matter (PMS7003) monitoring module (Pi 4 Model B) config — non-secret defaults
#
# Operational/secret values such as server URL / API_KEY / device identifiers (DEVICE_ID/LOCATION)
# 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 the real values
#
# This module integrates a PMS7003 PM sensor onto an EXISTING leak node (co-located, same
# sensor_id as that node's leak sensor, distinguished by metric_type="pms7003" — exactly like
# the SHT30 co-location). It attaches to BOTH leak nodes: Pi-C (sensor_id=3, the default here)
# and the id=1 node (override SENSOR_ID=1 in config_local.py). Threshold/SMS is server-side.
# =============================================================================
# ─────────────────────────────────────────────
# Sensor identity (for server dashboard display; not secret)
# SENSOR_ID is the key in the server's leak_sensor_metric / leak_sensor_status. The PMS row
# shares the co-located leak sensor's id (1 or 3), separated by metric_type. Default = Pi-C (3).
# ─────────────────────────────────────────────
SENSOR_ID = 3
SENSOR_NAME = "Sensor 3 (PMS7003)"
# ─────────────────────────────────────────────
# UART config (Pi 4 40-pin header primary UART)
# PMS7003 TXD → Pi RXD = GPIO15 (physical pin 10). 3.3V logic, 9600 8N1.
# Port: /dev/serial0 (alias to the primary UART). Also usable: /dev/ttyS0 / /dev/ttyAMA0.
# Requires: enable UART + DISABLE the serial login console (raspi-config / enable_uart=1).
# ─────────────────────────────────────────────
UART_PORT = "/dev/serial0"
UART_BAUD = 9600
UART_TIMEOUT = 2.0 # pyserial per-read timeout (seconds)
FRAME_WAIT_TIMEOUT = 5.0 # max time to wait for a valid frame in one measurement (seconds)
# Force simulation. If True, runs with dummy measurements without hardware (for server integration checks).
# Even if False, the driver auto-falls back to simulation when pyserial is not installed / UART is unavailable.
FORCE_SIMULATION = False
# ─────────────────────────────────────────────
# Measurement/report intervals
# ─────────────────────────────────────────────
REPORT_INTERVAL = 600 # Normal measure/report interval (seconds) = 10 minutes
# Matches the co-located leak node's SHT30 cadence (600s).
# Server offline detection (METRIC_HEARTBEAT_TIMEOUT_SEC) must exceed this.
# Override per-node in config_local.py if a different cadence is needed.
MEASURE_RETRY_DELAY = 5 # Wait before retry after a failed measurement (seconds)
# ─────────────────────────────────────────────
# Physical plausibility range for PM values (µg/m³, atmospheric concentration)
# Values outside this range are reported as metric_status="out_of_range".
# * Operational threshold judgments (high PM2.5 / PM10) and SMS are all handled by the
# server (config.php METRIC_PM*). This module only faithfully reports measurements.
# ─────────────────────────────────────────────
PM_VALUE_MIN = 0
PM_VALUE_MAX = 1000
# Module identity version (server app_version field; the server only records it, does not validate)
APP_VERSION = "v2606-pms7003-rpi4"
# ─────────────────────────────────────────────
# Logging config
# ─────────────────────────────────────────────
LOG_FILE = "/var/log/pms7003_sensor.log"
LOG_MAX_BYTES = 5 * 1024 * 1024 # 5MB
LOG_BACKUP_COUNT = 3
# ─────────────────────────────────────────────
# HTTP retry config
# ─────────────────────────────────────────────
HTTP_TIMEOUT = 10 # Single request timeout (seconds)
HTTP_RETRY_COUNT = 3 # Number of retries
HTTP_RETRY_DELAY = 5 # Wait between retries (seconds)
# ─────────────────────────────────────────────
# Load operational/secret values (fail closed)
# If config_local.py is missing, abort immediately with a clear error instead of
# starting incorrectly with placeholder/hardcoded secrets.
# ─────────────────────────────────────────────
try:
from config_local import ( # noqa: F401 (pms7003_sensor.py re-imports from config)
API_URL,
API_KEY,
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 from config_local.py.
# Editing the tracked config.py per device causes deployment drift, so each leak node
# overrides SENSOR_ID / SENSOR_NAME here instead (Pi-C keeps id 3; the id=1 node sets
# SENSOR_ID=1). If config_local defines neither, the defaults above (SENSOR_ID=3) are kept.
# ─────────────────────────────────────────────
try:
from config_local import SENSOR_ID # noqa: F811 — per-device sensor_id override
except ImportError:
pass
try:
from config_local import SENSOR_NAME # noqa: F811 — per-device display name override
except ImportError:
pass
try:
# Per-device report cadence. Defaults to 600s (10 min) to match the co-located leak
# node's SHT30 cadence; override here if a node needs a different interval.
from config_local import REPORT_INTERVAL # noqa: F811 — per-device report interval override
except ImportError:
pass
try:
from config_local import UART_PORT # noqa: F811 — per-device UART device override
except ImportError:
pass
# Validate (fail closed) — a typo must not silently start with a wrong/plausible id.
if not (isinstance(SENSOR_ID, int) and 1 <= SENSOR_ID <= 255):
raise RuntimeError(f"SENSOR_ID must be int 1..255 (check config_local.py): {SENSOR_ID!r}")
if not (isinstance(SENSOR_NAME, str) and SENSOR_NAME):
raise RuntimeError("SENSOR_NAME must be a non-empty string (check config_local.py).")
if not (isinstance(REPORT_INTERVAL, int) and REPORT_INTERVAL > 0):
raise RuntimeError(f"REPORT_INTERVAL must be a positive int (seconds) (check config_local.py): {REPORT_INTERVAL!r}")
if not (isinstance(UART_PORT, str) and UART_PORT):
raise RuntimeError("UART_PORT must be a non-empty string (check config_local.py).")