POSA_LEAKSMS/docs/superpowers/specs/2026-07-15-leak-node-sht30-integration-design.md
cw2007 0b1e86c84a feat(sht30): 누수 노드에 온습도 동일-id 통합 (소스·서버예시·테스트·문서)
누수 노드(Pi-A=sensor_id 1, Pi-C=sensor_id 3)에 SHT30 온습도를 "같은
sensor_id"로 보고하는 구성을 반영. 노드는 기존 sht30_sensor.py를 별도
systemd 서비스로 얹고(불안정한 누수 코드 무변경), config_local로 SENSOR_ID만 지정.

- raspberry_pi4_sht30/config.py: config_local로 SENSOR_ID/SENSOR_NAME 오버라이드
  지원(미지정 시 기본 2 유지 — Pi-B 하위호환)
- php/config.local.example.php: METRIC_SENSOR_IDS=[1,2,3] 예시 +
  SENSOR_HEARTBEAT_TIMEOUT_SEC[1,3]=1800(1·3이 온습도 목록에 들어가도 누수
  오프라인 감지 속도 유지)
- tests/heartbeat_timeout_test.php: 동일-id 통합 시 1·3 누수 타임아웃 유지 검증
- docs/superpowers/{specs,plans}: 설계 문서 + 구현 계획

설치 패키지(deploy/* — gitignored)는 온디스크 산출물로 별도 반영(sht30 서브폴더,
install.sh 체이닝, WIRING/SETUP/README, 서버 SETUP 절차).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nd7QvbgEWCjSQtUYnGNvvV
2026-07-15 10:16:49 +09:00

8.7 KiB
Raw Permalink Blame History

설계: 누수 노드에 SHT30 온습도 통합 (동일 sensor_id)

날짜: 2026-07-15 브랜치: feat/pi-c-sensor3-leak-node 상태: 설계 승인됨 (사용자 확인 완료)

목표

기존 누수 노드(Pi-A=sensor_id 1, Pi-C=sensor_id 3)에 SHT30(P4422-3) 온습도 센서를 물리적으로 추가하고, 온습도를 같은 sensor_id(1, 3) 로 서버에 보고한다. 대시보드·경보· 월간보고·설치 패키지까지 전부 반영한다. Pi-B(온습도 전용, sensor_id 2)는 건드리지 않는다.

핵심 결정 (확정)

  1. 동일 sensor_id. 온습도는 새 id를 만들지 않고 누수와 같은 id(1, 3)로 보고한다.
    • leak_sensor_status(sensor_id 키)와 leak_sensor_metric(sensor_id+측정시각 키)는 테이블이 분리돼 있어, 한 sensor_id가 누수 상태 + 온습도 측정을 동시에 가질 수 있다.
    • 새 id/재번호 없음. Pi-B(2) 유지.
  2. 노드: 별도 서비스. 불안정한 누수 코드(leak_sensor.py)는 건드리지 않는다. 기존 sht30_sensor.py를 두 번째 systemd 서비스로 얹는다. sht30 코드는 무변경 — config의 SENSOR_ID만 노드별(1/3)로 설정.
  3. 라벨: 서버 SENSOR_LABELS 오버라이드 없이, 노드가 보고하는 device_location/ sensor_name을 그대로 쓴다. 단, 같은 sensor_id를 두 서비스가 upsert하므로 라벨이 흔들리지 않게 두 서비스의 device_location을 노드별로 동일하게 맞춘다.
  4. 경보/수신자: 전역 METRIC_* 임계로 서버가 고온/고습 등 판정 → SMS. 수신자는 기존 SENSOR_RECIPIENTS의 1·3 그룹을 그대로 사용(추가 매핑 불필요).

아키텍처 / 데이터 흐름

Pi-A (한 대)
 ├ leak-sensor.service   → leak_sensor.py   → POST (sensor_id=1) leak_alert/recovery/periodic (누수, 온습도 없음)
 └ sht30-sensor.service  → sht30_sensor.py  → POST (sensor_id=1) periodic (metric_type=sht30, 온·습도)
Pi-C (한 대): 위와 동일, sensor_id=3
Pi-B: 변경 없음 (sensor_id=2)

서버 sensor_data.php:
 - sensor_id=1 상태 upsert: 누수 이벤트만 is_leak 갱신, sht30 periodic은 is_leak 보존(기존 로직)
 - sht30 payload → leak_sensor_metric(sensor_id=1) 저장 + 임계 판정 → 온습도 SMS(SENSOR_RECIPIENTS[1])
 - last_seen 은 두 서비스가 모두 갱신
대시보드: sensor_id=1 → 누수 카드(LEAK_SENSOR_IDS) + 온습도 카드(METRIC_SENSOR_IDS)

변경 범위

A. 서버 (코드 변경 없음 — 설정/문서/예시만)

서버 코드는 이미 동적(대시보드/추이/월간보고 전부 METRIC_SENSOR_IDS 순회, 스키마는 임의 sensor_id 수용)이라 코드 수정 불필요. 설정과 예시/문서만 반영.

  1. php/config.local.example.php
    • METRIC_SENSOR_IDS 예시를 [1, 2, 3] 로(누수 노드 1·3도 온습도 카드 표시).
    • SENSOR_HEARTBEAT_TIMEOUT_SEC 예시에 [1 => 1800, 3 => 1800] 추가 — 1·3을 온습도 목록에 넣으면 오프라인 타임아웃이 metric 기본(7500s)으로 느슨해지므로, 누수 노드는 빠른 오프라인 감지를 유지하도록 명시 오버라이드.
    • 동일 sensor_id에 누수+온습도를 함께 두는 구성 설명 주석 추가.
  2. deploy/01_server_cafe24/asmdkwqdmqwertn/config.local.php(온서버 시크릿 — 접근 불가)
    • 실제 반영 값을 사용자에게 제시: METRIC_SENSOR_IDS => [1,2,3], SENSOR_HEARTBEAT_TIMEOUT_SEC => [1=>1800, 3=>1800].
    • 파일이 시크릿이라 에이전트가 직접 수정하지 않고, 추가할 라인을 SETUP 문서/응답에 명시.
  3. deploy/01_server_cafe24/SETUP.md: 위 config.local 변경 절차 문서화.

검증 항목(구현 중 확인): php/dashboard_data.php의 카드 맵(≈208–223행)이 sensor_id를 LEAK/METRIC 양쪽에 두어도(중복 집합) 정상 렌더되는지, monthly_report*/forecast.php가 동일 id를 누수+온습도로 다뤄도 깨지지 않는지 확인. 문제 시 최소 수정.

B. 노드 — 각 누수 노드에 sht30 서비스 추가 (deploy/02, deploy/04)

각 노드 배포 폴더에 sht30/ 하위 폴더를 추가한다(기존 누수 파일/경로는 불변 = /home/pi/raspberry_pi4 그대로). sht30 파일은 raspberry_pi4_sht30/에서 복사(코드 무변경).

노드별 sht30/ 구성 파일:

  • sht30_sensor.py, sht30_driver.py, wire.py — 소스에서 복사(무변경)
  • config.py — 소스에서 복사(무변경; SENSOR_ID 기본값은 config_local에서 오버라이드)
  • config_local.example.py — 템플릿
  • config_local.py — 노드별 실값:
    • Pi-A(02): SENSOR_ID=1, SENSOR_NAME=누수와 동일, DEVICE_ID="rpi4-a-sht30", DEVICE_LOCATION=Pi-A 누수 노드와 동일 문자열, API_URL/API_KEY=서버와 동일
    • Pi-C(04): SENSOR_ID=3, 나머지 동일 패턴(DEVICE_ID="rpi4-c-sht30")
  • requirements.txt — smbus2 포함(소스와 동일)
  • systemd/sht30-sensor.service — WorkingDirectory=/home/pi/sht30, ExecStart=/home/pi/sht30/venv/bin/python sht30_sensor.py, SupplementaryGroups=i2c
  • test_sensor.py, test_connection.py — 소스에서 복사
  • vendor.sh — 오프라인 번들(smbus2 휠 + i2c-tools deb)

각 누수 노드 폴더의 설치/문서 갱신:

  • install.sh — 기존 누수 설치에 더해 sht30 서비스 설치 단계 추가:
    • sht30/를 /home/pi/sht30로 설치(venv 생성, requirements 설치, 로그파일 /var/log/sht30_sensor.log 생성, config_local 권한 600)
    • I2C 활성화: sudo raspi-config nonint do_i2c 0 (오프라인 가능). 실패 시 /boot/firmware/config.txt(Bookworm) 또는 /boot/config.txt에 dtparam=i2c_arm=on 보장. i2c 그룹에 SERVICE_USER 추가.
    • sht30-sensor.service 설치 + enable + start
    • 폐쇄망 규칙 준수: 네트워크 미사용, 번들 없으면 명확히 FLAG(기존 install.sh 패턴 유지)
  • WIRING.html — 기존 누수 배선에 SHT30 I2C 배선 추가: VCC→3.3V(핀1), SDA→GPIO2(핀3), SCL→GPIO3(핀5), GND→핀6/9, ADDR→GND=0x44. 누수 GPIO17(핀11)과 무충돌 명시. "반드시 3.3V, 5V 금지" 경고 포함.
  • SETUP.md / README.md — 이중 서비스(누수+온습도) 설치·확인 절차, 동일 sensor_id 설명.

C. 소스 트리 (참고 — 정합성)

raspberry_pi4_sht30/(sht30 소스)와 raspberry_pi4/(누수 소스)는 코드 변경 없음. php/config.local.example.php만 소스에서 갱신(위 A-1). deploy가 1차 산출물이므로 deploy 폴더를 정본으로 반영하고, 소스 예시 파일은 동기화한다.

에러 처리 / 엣지

  • 라벨 플랩: 두 서비스가 같은 sensor_id로 sensor_name/device_location upsert → 마지막 쓰기가 이김. 노드별로 두 config의 device_location을 동일 문자열로 맞춰 방지. (sensor_name은 서버 표시에 영향 최소지만 역시 일치 권장.)
  • 오프라인 타임아웃: A-1/A-2의 SENSOR_HEARTBEAT_TIMEOUT_SEC 오버라이드로 1·3의 누수 오프라인 감지 속도 유지(온습도 목록 편입에 의한 완화 방지).
  • is_leak 보존: sht30 periodic(비-누수 이벤트)은 서버 upsert에서 is_leak 보존(기존 IF(:is_leak_event, VALUES(is_leak), is_leak) 로직) → 온습도 보고가 누수 상태를 덮지 않음.
  • SHT30 통신 실패: sht30_sensor.py가 크래시 없이 재시도(기존 동작). 누수 서비스와 독립 프로세스라 상호 영향 없음.
  • I2C 미활성: smbus2/I2C 불가 시 sht30는 시뮬레이션 폴백(기존). install.sh가 I2C를 활성화하고, 미활성 시 로그로 안내.

테스트

  • 서버(PHP 단위/통합):
    • recipients: sensor_id 1·3의 온습도 경보가 SENSOR_RECIPIENTS[1]/[3]로 발송되는지.
    • heartbeat: heartbeat_timeout_for(1)/(3)이 오버라이드로 누수 타임아웃(1800s)을 반환(metric 7500s 아님)하는지 — resolve_heartbeat_timeout 단위 테스트 케이스 추가.
    • 통합: sensor_id=1 metric POST(periodic) → leak_sensor_metric 저장 + is_leak 보존 + 임계 초과 시 SMS 경로.
    • 대시보드: LEAK∩METRIC(=1,3)에서 카드 렌더 정상(중복 집합) 스모크.
  • 노드:
    • sht30/test_sensor.py: I2C 판독(또는 시뮬).
    • sht30/test_connection.py: SENSOR_ID=1/3로 서버 POST 200 + 서명 검증.
  • 오프라인 설치: 네트워크 차단 상태에서 install.sh가 두 서비스 + I2C 활성화까지 무network 완료되는지(폐쇄망 검증).

비목표 (YAGNI)

  • Pi-B(sensor_id 2) 재번호/변경 없음.
  • 누수 라인 100nF 콘덴서/하드웨어 필터 — 별개 이슈(이번 범위 밖).
  • 센서별 경보 on/off 토글 등 신규 서버 기능 없음(전역 임계 그대로).
  • 누수 4~10분 복구 지연(리포트 굶주림) 수정 — 별개 이슈(이번 범위 밖).