센서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 커밋 차단
124 lines
7.7 KiB
Python
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).")
|