센서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 커밋 차단
160 lines
6.5 KiB
Bash
160 lines
6.5 KiB
Bash
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# Particulate-matter (PMS7003) module v2 (Raspberry Pi 4 Model B) install script
|
|
# Target OS: Raspberry Pi OS Bookworm (64-bit, aarch64)
|
|
#
|
|
# Air-gapped support:
|
|
# - If debs/ or wheelhouse/ is present, automatically switch to "offline mode".
|
|
# - In offline mode, never access the network. If the bundle is only partial,
|
|
# do not silently fall back to online; stop with a clear error (air-gapped rule). Pre-
|
|
# download is done on the build machine via vendor.sh.
|
|
# - Can be forced via the OFFLINE=1/0 environment variable.
|
|
# - Log file owner: SERVICE_USER (default pi).
|
|
#
|
|
# This script also enables the UART (enable_uart=1) and DISABLES the serial login console
|
|
# (so the PMS7003 has exclusive use of /dev/serial0), and adds the user to the dialout group.
|
|
# =============================================================================
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")"
|
|
|
|
SERVICE_USER="${SERVICE_USER:-pi}"
|
|
|
|
echo "=========================================="
|
|
echo " Particulate-matter (PMS7003) module v2 (Pi 4) install"
|
|
echo "=========================================="
|
|
|
|
# 0) Board model notice (not enforced)
|
|
if [ -r /proc/device-tree/model ]; then
|
|
MODEL="$(tr -d '\0' < /proc/device-tree/model)"
|
|
echo "[INFO] Board: ${MODEL}"
|
|
case "${MODEL}" in
|
|
*"Raspberry Pi 4"*) : ;;
|
|
*) echo "[WARN] This folder is for Raspberry Pi 4 Model B only (you may continue anyway)." ;;
|
|
esac
|
|
fi
|
|
|
|
# Offline mode detection
|
|
OFFLINE="${OFFLINE:-}"
|
|
if [ -z "${OFFLINE}" ]; then
|
|
if compgen -G "debs/*.deb" >/dev/null || [ -d wheelhouse ]; then
|
|
OFFLINE=1
|
|
echo "[INFO] Offline bundle detected -> air-gapped offline install mode (no network)"
|
|
else
|
|
OFFLINE=0
|
|
echo "[INFO] No offline bundle -> online install mode (internet required)"
|
|
fi
|
|
fi
|
|
|
|
# 1) System packages: requests + venv
|
|
echo "[1/4] Installing system packages..."
|
|
if [ "${OFFLINE}" = "1" ]; then
|
|
if ! compgen -G "debs/*.deb" >/dev/null; then
|
|
echo "[ERROR] Offline mode but debs/ is empty. Place the vendor.sh output (debs/) in this folder and re-run." >&2
|
|
exit 1
|
|
fi
|
|
echo " Offline .deb install (apt-get --no-download - dependency ordering/resolution automatic, no network)"
|
|
# --no-install-recommends: vendor.sh does not bundle Recommends (--no-recommends), so match that.
|
|
sudo apt-get install -y --no-install-recommends --no-download ./debs/*.deb
|
|
else
|
|
echo " Online apt install (internet required)"
|
|
sudo apt-get update
|
|
sudo apt-get install -y python3-requests python3-venv
|
|
fi
|
|
|
|
# 2) Enable UART + disable the serial login console
|
|
echo "[2/4] Enabling UART (and disabling the serial console)..."
|
|
# Boot config path: Bookworm=/boot/firmware/config.txt, older=/boot/config.txt
|
|
BOOT_CFG=""
|
|
for c in /boot/firmware/config.txt /boot/config.txt; do
|
|
if [ -f "$c" ]; then BOOT_CFG="$c"; break; fi
|
|
done
|
|
if [ -n "${BOOT_CFG}" ]; then
|
|
if ! grep -q "^enable_uart=1" "${BOOT_CFG}"; then
|
|
echo " Adding enable_uart=1 to ${BOOT_CFG} (applied after reboot)"
|
|
echo "enable_uart=1" | sudo tee -a "${BOOT_CFG}" >/dev/null
|
|
NEED_REBOOT=1
|
|
else
|
|
echo " enable_uart=1 already set"
|
|
fi
|
|
else
|
|
echo "[WARN] config.txt not found - enable UART manually via raspi-config"
|
|
fi
|
|
# Disable the serial login console so /dev/serial0 is free for the sensor.
|
|
# Bookworm=/boot/firmware/cmdline.txt, older=/boot/cmdline.txt
|
|
CMDLINE=""
|
|
for c in /boot/firmware/cmdline.txt /boot/cmdline.txt; do
|
|
if [ -f "$c" ]; then CMDLINE="$c"; break; fi
|
|
done
|
|
if [ -n "${CMDLINE}" ] && grep -q "console=serial0" "${CMDLINE}"; then
|
|
echo " Removing 'console=serial0,115200' from ${CMDLINE} (frees the UART for PMS7003)"
|
|
sudo sed -i 's/console=serial0,[0-9]* //' "${CMDLINE}"
|
|
NEED_REBOOT=1
|
|
fi
|
|
# Disable the getty on the primary UART (harmless if already masked).
|
|
sudo systemctl disable --now serial-getty@ttyS0.service 2>/dev/null || true
|
|
sudo systemctl disable --now serial-getty@ttyAMA0.service 2>/dev/null || true
|
|
# Add the service user to the dialout group (/dev/serial0 access permission)
|
|
if getent group dialout >/dev/null 2>&1; then
|
|
sudo usermod -aG dialout "${SERVICE_USER}" || echo "[WARN] usermod failed - check SERVICE_USER=${SERVICE_USER}"
|
|
fi
|
|
|
|
# 3) Python venv + requests + pyserial
|
|
echo "[3/4] Setting up Python virtual environment..."
|
|
if [ ! -d venv ]; then
|
|
python3 -m venv --system-site-packages venv
|
|
echo " venv created"
|
|
else
|
|
echo " venv already exists - skipping"
|
|
fi
|
|
# shellcheck disable=SC1091
|
|
source venv/bin/activate
|
|
if [ "${OFFLINE}" = "1" ]; then
|
|
if [ ! -d wheelhouse ]; then
|
|
echo "[ERROR] Offline mode but wheelhouse/ is missing. Place the vendor.sh output (wheelhouse/) and re-run." >&2
|
|
exit 1
|
|
fi
|
|
echo " Offline wheel install (--no-index - no network)"
|
|
pip install --no-index --find-links=wheelhouse -r requirements.txt
|
|
else
|
|
echo " Online pip install (internet required)"
|
|
pip install --upgrade pip
|
|
pip install -r requirements.txt
|
|
fi
|
|
echo " Packages installed"
|
|
|
|
# 4) Log file (least privilege: owner-only write)
|
|
echo "[4/4] Creating log file..."
|
|
sudo mkdir -p /var/log
|
|
sudo touch /var/log/pms7003_sensor.log
|
|
if ! sudo chown "${SERVICE_USER}:${SERVICE_USER}" /var/log/pms7003_sensor.log; then
|
|
echo "[WARN] chown failed - check whether SERVICE_USER=${SERVICE_USER} exists"
|
|
fi
|
|
sudo chmod 640 /var/log/pms7003_sensor.log
|
|
|
|
# Lock config_local.py immediately (contains API_KEY - prevent world-readable until harden)
|
|
# Hardening (harden_pi.sh) tightens it again later, but to avoid exposure even in
|
|
# that window, lock it to 600 right at install time. Protected regardless of harden.
|
|
if [ -f config_local.py ]; then
|
|
chmod 600 config_local.py
|
|
sudo chown "${SERVICE_USER}:${SERVICE_USER}" config_local.py 2>/dev/null || true
|
|
echo " config_local.py set to permission 600 (owner ${SERVICE_USER})"
|
|
fi
|
|
|
|
# config_local notice (fail closed - if missing, execution stops immediately)
|
|
if [ ! -f config_local.py ]; then
|
|
echo ""
|
|
echo "[!] config_local.py is missing. Run the following, then fill in real values:"
|
|
echo " cp config_local.example.py config_local.py"
|
|
fi
|
|
|
|
echo ""
|
|
echo "=========================================="
|
|
echo " Install complete! (OFFLINE=${OFFLINE})"
|
|
echo "=========================================="
|
|
if [ "${NEED_REBOOT:-0}" = "1" ]; then
|
|
echo " [!] UART was just enabled / serial console disabled -> reboot to apply: 'sudo reboot'."
|
|
fi
|
|
echo " Port check: ls -l /dev/serial0 (should symlink to ttyS0 or ttyAMA0)"
|
|
echo " Run: source venv/bin/activate && python3 pms7003_sensor.py"
|
|
echo " Service: see the systemd section in README.md"
|
|
echo ""
|