POSA_LEAKSMS/docs/superpowers/plans/2026-07-15-leak-node-sht30-integration.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

21 KiB
Raw Permalink Blame History

누수 노드 SHT30 온습도 통합 구현 계획

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 기존 누수 노드(Pi-A=id1, Pi-C=id3)에 SHT30 온습도 센서를 물리적으로 추가하고, 온습도를 같은 sensor_id(1,3)로 서버에 보고 — 대시보드·경보·설치 패키지 전부 반영.

Architecture: 노드마다 기존 누수 서비스는 무변경, sht30_sensor.py를 두 번째 systemd 서비스로 얹는다(별도 디렉터리 /home/pi/sht30). sht30 코드는 그대로 재사용하고 config_local로 SENSOR_ID만 1/3로 지정. 서버는 코드 변경 없이 config.local 값(METRIC_SENSOR_IDS·heartbeat override)만 조정 — 대시보드/보고서는 이미 METRIC_SENSOR_IDS 동적 순회.

Tech Stack: Python 3.11(라즈베리 파이 4, Bookworm), smbus2(I2C), PHP(cafe24 서버), systemd, bash 설치 스크립트(폐쇄망 오프라인 번들).

Global Constraints

  • 폐쇄망(air-gapped): 모든 설치 산출물은 자립형. 네트워크 미사용, 번들 없으면 명확히 FLAG. wheel/deb는 build 머신에서 vendor.sh로 사전 다운로드. (기존 install.sh/vendor.sh 패턴 유지)
  • 동일 sensor_id: 온습도는 새 id 없이 누수와 같은 id(Pi-A=1, Pi-C=3). Pi-B(id=2)는 절대 변경 없음.
  • 누수 코드 무변경: leak_sensor.py 및 누수 런타임 경로(/home/pi/raspberry_pi4) 손대지 않음.
  • 라벨 일치: 각 노드에서 sht30 config_local의 DEVICE_LOCATION은 같은 노드 누수 config_local의 DEVICE_LOCATION과 문자열 동일(같은 sensor_id upsert 라벨 플랩 방지).
  • HMAC 서명: sht30 config_local의 API_KEY는 서버 config.local.php의 API_KEY와 완전 동일.
  • 비밀값: config_local.py/config.local.php(실값)는 에이전트가 직접 수정/커밋하지 않음. 예시(*.example)와 문서만 반영, 실값 라인은 응답/문서에 제시.

Task 1: sht30 config.py — config_local로 SENSOR_ID/SENSOR_NAME 오버라이드 지원

목적: 누수 노드의 sht30가 tracked config.py(SENSOR_ID=2 기본) 편집 없이 config_local만으로 id=1/3 보고. 누수 config.py의 SENSORS 오버라이드 패턴과 동일. Pi-B는 config_local에 SENSOR_ID 미지정 → 기본 2 유지(하위호환).

Files:

  • Modify: raspberry_pi4_sht30/config.py (끝에 오버라이드 블록 추가)
  • Modify: deploy/03_pi_b_sht30_id2/config.py (동일 블록 — 소스와 동기화)

Interfaces:

  • Produces: config.SENSOR_ID / config.SENSOR_NAME 가 config_local 값으로 대체 가능(검증 포함). sht30_sensor.py가 from config import SENSOR_ID, SENSOR_NAME 로 소비(무변경).

  • Step 1: raspberry_pi4_sht30/config.py 끝(현재 마지막 줄 = config_local import 블록 이후)에 추가


# ─────────────────────────────────────────────
# Optional override — per-device values from config_local.py.
#   Editing the tracked config.py per device causes deployment drift, so a co-located
#   node (e.g. leak node reporting temp/humidity under the SAME sensor_id 1 or 3)
#   overrides SENSOR_ID / SENSOR_NAME here instead. If config_local has neither,
#   the defaults above (SENSOR_ID=2) are kept — backward compatible for Pi-B.
# ─────────────────────────────────────────────
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

# 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).")
  • Step 2: 같은 블록을 deploy/03_pi_b_sht30_id2/config.py 끝에도 추가 (소스와 동일 — 두 파일 내용 일치 확인)

  • Step 3: 오버라이드 동작 검증(수동 스모크 — 이 repo의 파이썬 테스트 관례는 수동 스크립트)

Run (임시 config_local 생성해 import 확인):

cd raspberry_pi4_sht30
python3 - <<'PY'
# 오버라이드 로직 단위 확인(실 config_local 없이): 값 대입+검증 규칙만.
ns = {'SENSOR_ID': 2, 'SENSOR_NAME': 'Sensor 2 (SHT30)'}
ns['SENSOR_ID'] = 1  # config_local override 흉내
assert isinstance(ns['SENSOR_ID'], int) and 1 <= ns['SENSOR_ID'] <= 255
assert isinstance(ns['SENSOR_NAME'], str) and ns['SENSOR_NAME']
print("override ok:", ns['SENSOR_ID'])
PY

Expected: override ok: 1

  • Step 4: Commit
git add raspberry_pi4_sht30/config.py deploy/03_pi_b_sht30_id2/config.py
git commit -m "feat(sht30): config_local 로 SENSOR_ID/SENSOR_NAME 오버라이드(동일-id 공용 노드 대비)"

Task 2: deploy/02 (Pi-A) 에 sht30 서브폴더 추가 — SENSOR_ID=1

Files:

  • Create dir: deploy/02_pi_a_leak_id1/sht30/
  • Create(복사): sht30/sht30_sensor.py, sht30/sht30_driver.py, sht30/wire.py, sht30/config.py, sht30/test_sensor.py, sht30/test_connection.py, sht30/requirements.txt, sht30/vendor.sh, sht30/install.sh, sht30/systemd/sht30-sensor.service — 전부 deploy/03_pi_b_sht30_id2/에서 verbatim 복사 (config.py는 Task 1 반영본)
  • Create: sht30/config_local.example.py, sht30/config_local.py

Interfaces:

  • Produces: /home/pi/sht30 로 설치될 자립형 sht30 installable. Task 4(install.sh 체이닝)가 소비.

  • Step 1: deploy/03 sht30 파일 verbatim 복사

cd deploy/02_pi_a_leak_id1
mkdir -p sht30/systemd
cp ../03_pi_b_sht30_id2/sht30_sensor.py     sht30/
cp ../03_pi_b_sht30_id2/sht30_driver.py     sht30/
cp ../03_pi_b_sht30_id2/wire.py             sht30/
cp ../03_pi_b_sht30_id2/config.py           sht30/
cp ../03_pi_b_sht30_id2/test_sensor.py      sht30/
cp ../03_pi_b_sht30_id2/test_connection.py  sht30/
cp ../03_pi_b_sht30_id2/requirements.txt    sht30/
cp ../03_pi_b_sht30_id2/vendor.sh           sht30/
cp ../03_pi_b_sht30_id2/install.sh          sht30/
cp ../03_pi_b_sht30_id2/systemd/sht30-sensor.service sht30/systemd/
  • Step 2: sht30/systemd/sht30-sensor.service 의 경로를 /home/pi/sht30 로 수정

WorkingDirectory 와 ExecStart 두 줄을 다음으로 변경:

WorkingDirectory=/home/pi/sht30
ExecStart=/home/pi/sht30/venv/bin/python sht30_sensor.py

(나머지 sandbox/그룹 설정은 그대로 — SupplementaryGroups=i2c 유지)

  • Step 3: sht30/config_local.example.py 작성
# =============================================================================
# config_local.example.py — 누수 노드(Pi-A, sensor_id=1)에 얹는 SHT30 온습도 서비스 설정
#   cp config_local.example.py config_local.py 후 실값 입력. config_local.py 는 커밋 금지.
#
# ★ 동일 sensor_id: 이 온습도는 같은 노드 누수 센서와 "같은" sensor_id(=1)로 보고한다.
# ★ 라벨 일치: DEVICE_LOCATION 은 같은 노드 누수 config_local.py 의 DEVICE_LOCATION 과
#     "문자열 동일"해야 한다(같은 id 를 두 서비스가 upsert → 라벨 플랩 방지).
# ★ API_KEY 는 서버 config.local.php 의 API_KEY 와 완전 동일(HMAC 서명).
# =============================================================================
API_URL = "https://YOUR_SERVER/raspi_leck_detecter/api/sensor_data.php"
API_KEY = "CHANGE_ME"

DEVICE_ID = "rpi4-a-sht30"
DEVICE_LOCATION = "Server Room"     # ★ 누수(id=1) DEVICE_LOCATION 과 동일하게

SENSOR_ID = 1                        # ★ 누수와 동일 id 로 온습도 보고
SENSOR_NAME = "Sensor 1 (SHT30)"
  • Step 4: sht30/config_local.py 작성(실배포 기본값 — 실값은 현장에서 교체, chmod 600은 install.sh가)

Step 3 내용과 동일하게 config_local.py 생성. .gitignore 가 config_local.py 를 제외하는지 확인 — 제외되면 예시만 커밋 대상(실값 파일 미커밋).

  • Step 5: Commit
git add deploy/02_pi_a_leak_id1/sht30
git commit -m "feat(pi-a): 누수 노드에 SHT30 온습도 서비스 서브폴더 추가(sensor_id=1)"

Task 3: deploy/04 (Pi-C) 에 sht30 서브폴더 추가 — SENSOR_ID=3

Task 2와 동일 절차, deploy/04_pi_c_leak_id3/sht30/ 로. 차이는 config_local(.example).py:

  • Step 1: 복사 — Task 2 Step 1을 cd deploy/04_pi_c_leak_id3 로 동일 수행.
  • Step 2: service 경로 — Task 2 Step 2와 동일(/home/pi/sht30).
  • Step 3+4: config_local(.example).py — Task 2와 동일하되:
DEVICE_ID = "rpi4-c-sht30"
DEVICE_LOCATION = "New Site"          # ★ 누수(id=3) DEVICE_LOCATION 과 동일하게
SENSOR_ID = 3                          # ★ 누수와 동일 id
SENSOR_NAME = "Sensor 3 (SHT30)"

(API_URL/API_KEY는 Task 2와 동일 형식)

  • Step 5: Commit
git add deploy/04_pi_c_leak_id3/sht30
git commit -m "feat(pi-c): 누수 노드에 SHT30 온습도 서비스 서브폴더 추가(sensor_id=3)"

Task 4: deploy/02 install.sh — sht30 서비스 설치 체이닝 + 서비스 enable

Files:

  • Modify: deploy/02_pi_a_leak_id1/install.sh (마지막 완료 메시지 직전에 단계 추가)

Interfaces:

  • Consumes: Task 2의 sht30/ (자립형 install.sh 포함).

  • Produces: install.sh 1회 실행으로 누수 + 온습도 두 서비스 설치·기동.

  • Step 1: deploy/02_pi_a_leak_id1/install.sh 의 최종 echo "==... Installation complete" 블록 바로 앞에 삽입

# 5) SHT30 온습도 co-센서 설치 (동일 sensor_id=1, /home/pi/sht30 로 설치)
if [ -d sht30 ]; then
  echo "[5/5] Installing SHT30 temp/humidity co-sensor (sensor_id 1)..."
  SHT30_DEST="/home/pi/sht30"
  sudo mkdir -p "${SHT30_DEST}"
  # 코드/번들 배치(설치 대상 디렉터리로 복사 — 폐쇄망: 네트워크 미사용)
  sudo cp -r sht30/. "${SHT30_DEST}"/
  sudo chown -R "${SERVICE_USER}:${SERVICE_USER}" "${SHT30_DEST}"
  # sht30 자체 install.sh 로 위임(venv + I2C 활성화 + 로그 + 서비스 설치). OFFLINE 전파.
  ( cd "${SHT30_DEST}" && SERVICE_USER="${SERVICE_USER}" OFFLINE="${OFFLINE}" bash install.sh )
  # systemd 서비스 등록/기동
  sudo cp "${SHT30_DEST}/systemd/sht30-sensor.service" /etc/systemd/system/
  sudo systemctl daemon-reload
  sudo systemctl enable sht30-sensor
  sudo systemctl restart sht30-sensor
  echo "  sht30-sensor 서비스 등록/기동 완료"
else
  echo "[5/5] sht30/ 폴더 없음 — 온습도 co-센서 설치 건너뜀"
fi
  • Step 2: 문법 검증

Run: bash -n deploy/02_pi_a_leak_id1/install.sh Expected: (출력 없음 = 문법 OK)

  • Step 3: Commit
git add deploy/02_pi_a_leak_id1/install.sh
git commit -m "feat(pi-a): install.sh 에 SHT30 온습도 서비스 설치 체이닝(누수+온습도 1회 설치)"

Task 5: deploy/04 install.sh — 동일 체이닝 (sensor_id 3)

Task 4와 동일. 삽입 스니펫에서 주석 "sensor_id 1"→"sensor_id 3"만 변경.

  • Step 1: deploy/04_pi_c_leak_id3/install.sh 에 Task 4 Step 1 스니펫 삽입(주석 id 3).
  • Step 2: bash -n deploy/04_pi_c_leak_id3/install.sh → 문법 OK.
  • Step 3: Commit
git add deploy/04_pi_c_leak_id3/install.sh
git commit -m "feat(pi-c): install.sh 에 SHT30 온습도 서비스 설치 체이닝(sensor_id=3)"

Task 6: deploy/02 배선/문서 — WIRING.html + SETUP.md + README.md

Files:

  • Modify: deploy/02_pi_a_leak_id1/WIRING.html (SHT30 I2C 배선 섹션 추가)

  • Modify: deploy/02_pi_a_leak_id1/SETUP.md (이중 서비스 설치·확인 절차)

  • Modify: deploy/02_pi_a_leak_id1/README.md (개요에 온습도 추가)

  • Step 1: WIRING.html — 누수 배선 다음에 SHT30 I2C 섹션 삽입

</body> 직전(또는 마지막 섹션 뒤)에 삽입:

<section>
  <h2>SHT30 온습도 (동일 노드, sensor_id 1)</h2>
  <p>누수 센서와 <b>같은 Pi</b>에 SHT30(P4422-3)을 I2C로 추가. 온습도는 누수와 <b>같은 sensor_id(1)</b>로 보고한다. 누수 GPIO17(핀11)과 <b>핀 충돌 없음</b>.</p>
  <table>
    <thead><tr><th>SHT30</th><th>Pi 40핀</th><th>비고</th></tr></thead>
    <tbody>
      <tr><td>VCC</td><td>3.3V (핀 1)</td><td>★ 반드시 3.3V. 5V(핀 2·4) 금지 — I2C 라인 5V로 끌려 Pi 손상</td></tr>
      <tr><td>SDA</td><td>GPIO2 / SDA1 (핀 3)</td><td>온보드 풀업 없으면 4.7kΩ→3.3V</td></tr>
      <tr><td>SCL</td><td>GPIO3 / SCL1 (핀 5)</td><td>온보드 풀업 없으면 4.7kΩ→3.3V</td></tr>
      <tr><td>GND</td><td>GND (핀 6 또는 9)</td><td></td></tr>
      <tr><td>ADDR</td><td>GND</td><td>0x44 (config.py I2C_ADDR 와 일치)</td></tr>
    </tbody>
  </table>
</section>
  • Step 2: SETUP.md — 온습도 설치/확인 절차 추가

다음 섹션을 문서 말미에 추가:

## SHT30 온습도 co-센서 (동일 sensor_id 1)

이 노드는 누수 + 온습도 두 서비스를 함께 돌린다.

1. 배선: WIRING.html 의 "SHT30 온습도" 표대로(VCC=3.3V, SDA=핀3, SCL=핀5, GND=핀6, ADDR=GND).
2. 온습도 설정: `sht30/config_local.py` 에서 API_URL/API_KEY/DEVICE_LOCATION 을 실값으로.
   - ★ `DEVICE_LOCATION` 은 누수 `config_local.py` 의 값과 문자열 동일.
   - `SENSOR_ID=1` 은 그대로(누수와 동일 id).
3. 설치: `bash install.sh` 1회 → 누수 + 온습도 서비스 모두 설치·기동(SHT30는 /home/pi/sht30).
4. 확인:
   - `i2cdetect -y 1` → 0x44 표시
   - `sudo systemctl status leak-sensor sht30-sensor` → 둘 다 active
   - 서버 대시보드 1번 카드에 누수 상태 + 온습도 표시
5. I2C 활성화 직후면 `sudo reboot` 필요할 수 있음(install.sh 안내).
  • Step 3: README.md — 개요 문단에 "이 노드는 누수(id 1) + SHT30 온습도(같은 id 1)를 함께 보고한다" 한 줄 추가.

  • Step 4: Commit

git add deploy/02_pi_a_leak_id1/WIRING.html deploy/02_pi_a_leak_id1/SETUP.md deploy/02_pi_a_leak_id1/README.md
git commit -m "docs(pi-a): SHT30 온습도 배선/설치 문서 반영"

Task 7: deploy/04 배선/문서 — 동일 (sensor_id 3)

Task 6과 동일하되 모든 "sensor_id 1"→"3", DEVICE_LOCATION 안내는 누수(id=3) 값 기준.

  • Step 1~3: Task 6 Step 1~3을 deploy/04_pi_c_leak_id3/ 파일에 id=3으로 반영.
  • Step 4: Commit
git add deploy/04_pi_c_leak_id3/WIRING.html deploy/04_pi_c_leak_id3/SETUP.md deploy/04_pi_c_leak_id3/README.md
git commit -m "docs(pi-c): SHT30 온습도 배선/설치 문서 반영(sensor_id=3)"

Task 8: 서버 — config.local.example.php + deploy/01 SETUP 반영

Files:

  • Modify: php/config.local.example.php
  • Modify: deploy/01_server_cafe24/asmdkwqdmqwertn/config.local.php(시크릿 — 에이전트 미수정, 값만 SETUP·응답에 제시)
  • Modify: deploy/01_server_cafe24/SETUP.md

Interfaces:

  • Produces: 대시보드가 1·3을 온습도 카드로도 표시(METRIC_SENSOR_IDS), 누수 오프라인 감지 속도 유지(heartbeat override).

  • Step 1: php/config.local.example.php 의 LEAK_SENSOR_IDS 아래 METRIC_SENSOR_IDS 예시 추가 + heartbeat override 주석 갱신

'LEAK_SENSOR_IDS' => [1], 다음에 삽입:

    // 온습도(SHT30) sensor_id 목록 — 대시보드 온습도 카드 표시 대상 (기본 [2]).
    // 누수 노드(1·3)에 SHT30 을 얹어 "같은 sensor_id" 로 온습도를 보고하는 구성에서는
    // 1·3 도 포함한다: [1, 2, 3]. (누수 카드 + 온습도 카드가 같은 지점에 함께 표시됨)
    'METRIC_SENSOR_IDS' => [1, 2, 3],

그리고 기존 SENSOR_HEARTBEAT_TIMEOUT_SEC 주석 예시를 실효 오버라이드로 교체:

    // 센서별 오프라인 판정 타임아웃(무수신 초). ★ 1·3 은 온습도 목록에도 들어가지만
    // 누수 노드이므로 metric 기본(7500s)으로 느슨해지면 안 된다 → 누수 타임아웃 유지 위해
    // 명시 오버라이드한다(30분). (미지정 시 온습도로 간주되어 오프라인 감지가 늦어짐)
    'SENSOR_HEARTBEAT_TIMEOUT_SEC' => [1 => 1800, 3 => 1800],
  • Step 2: deploy/01_server_cafe24/SETUP.md 에 온서버 config.local.php 변경 절차 추가
## 누수 노드 온습도(SHT30) 통합 반영 (동일 sensor_id)

누수 노드(1·3)에 SHT30 를 얹어 같은 sensor_id 로 온습도를 보고하면, 서버 config.local.php 에
아래를 반영한다(코드 변경 없음 — 설정만):

    'METRIC_SENSOR_IDS' => [1, 2, 3],
    'SENSOR_HEARTBEAT_TIMEOUT_SEC' => [1 => 1800, 3 => 1800],

- METRIC_SENSOR_IDS: 1·3 을 온습도 카드 대상에 포함(대시보드/월간보고 자동 반영).
- SENSOR_HEARTBEAT_TIMEOUT_SEC: 1·3 을 누수 타임아웃(30분)으로 고정(온습도 완화 방지).
- 온습도 임계 경보 수신자는 기존 SENSOR_RECIPIENTS[1]/[3] 그대로 사용.
반영 후 대시보드에서 1·3 카드에 누수 + 온습도가 함께 뜨는지 확인.
  • Step 3: Commit
git add php/config.local.example.php deploy/01_server_cafe24/SETUP.md
git commit -m "docs(server): 누수 노드 동일-id 온습도 통합 — METRIC_SENSOR_IDS/heartbeat 예시·SETUP 반영"

Task 9: 서버 테스트 — heartbeat override(1·3) 케이스 추가

Files:

  • Modify: tests/heartbeat_timeout_test.php

Interfaces:

  • Consumes: resolve_heartbeat_timeout($sid, $overrides, $metricIds, $metricDefault, $globalDefault) (config.php 순수 함수).

  • Step 1: 기존 테스트 파일 문법 확인

Run: php -l tests/heartbeat_timeout_test.php Expected: No syntax errors detected

  • Step 2: 실패 테스트 추가 — 1·3 이 METRIC 목록에 있어도 override 로 누수 타임아웃 반환

tests/heartbeat_timeout_test.php 에 다음 검증 추가(파일의 assert 패턴에 맞춰 삽입):

// 동일-id 온습도 통합: 1·3 이 METRIC_SENSOR_IDS 에 포함돼도, 명시 오버라이드가
// metric 기본(7500)보다 우선하여 누수 타임아웃(1800)을 반환해야 한다.
$overrides   = [1 => 1800, 3 => 1800];
$metricIds   = [1, 2, 3];
assert(resolve_heartbeat_timeout(1, $overrides, $metricIds, 7500, 1200) === 1800); // 누수 유지
assert(resolve_heartbeat_timeout(3, $overrides, $metricIds, 7500, 1200) === 1800); // 누수 유지
assert(resolve_heartbeat_timeout(2, $overrides, $metricIds, 7500, 1200) === 7500); // 순수 온습도
echo "[OK] heartbeat override(1,3) with metric-membership\n";
  • Step 3: 실행 → 통과 확인

Run: php tests/heartbeat_timeout_test.php Expected: 기존 출력 + [OK] heartbeat override(1,3) with metric-membership, 종료코드 0

  • Step 4: Commit
git add tests/heartbeat_timeout_test.php
git commit -m "test(server): 동일-id 온습도 통합 시 1·3 누수 heartbeat 타임아웃 유지 검증"

Task 10: 통합 검증 (대시보드 중복집합 + 라벨/스키마) — 코드 수정은 발견 시만

Files:

  • Verify: php/dashboard_data.php(≈208–223, 503–506), php/monthly_report_print.php, php/forecast.php

  • Modify(발견 시만): 위 파일 중 동일 id가 LEAK∩METRIC 일 때 깨지는 부분

  • Step 1: 카드 맵 로직 확인 — sensor_id 가 LEAK·METRIC 양쪽에 있어도 정상

php/dashboard_data.php 208–230행을 읽고, LEAK_SENSOR_IDS와 METRIC_SENSOR_IDS 순회가 서로 독립적으로 카드를 만드는지(중복 집합 1·3에서 예외/누락 없는지) 확인. 누수 카드와 온습도 카드가 각각 생성되면 정상.

  • Step 2: 월간보고/예보에서 동일 id 취급 확인

php/monthly_report_print.php(LEAK_SENSOR_IDS·METRIC_SENSOR_IDS 사용부), php/forecast.php(METRIC 순회)에서 1·3이 온습도로도 집계될 때 누수 데이터와 충돌/중복 표기가 없는지 확인.

  • Step 3: 문제 발견 시에만 최소 수정 — 없으면 수정 없이 다음.

  • Step 4: 통합 스모크(로컬 서버 있으면) — sensor_id=1 metric POST → 저장·표시·임계 경로

가능하면 로컬/스테이징에서 sensor_id=1 로 sht30 periodic POST(서명 포함) → leak_sensor_metric에 1행 저장 + leak_sensor_status의 is_leak 보존 + 대시보드 1번 카드에 온습도 표시 확인. (운영 서버 직접 검증은 사용자 몫 — SETUP 절차로 안내.)

  • Step 5: Commit(수정 있었을 때만)
git add -A
git commit -m "fix(server): 동일-id LEAK∩METRIC 대시보드/보고 표기 정합(발견 시)"

Self-Review (작성자 체크)

  • Spec 커버리지: A(서버 설정/문서)=Task 8·9·10, B(노드 sht30 서비스)=Task 1·2·3·4·5, 배선/설치문서=Task 6·7. 스펙의 모든 절 대응 태스크 존재. ✓
  • Placeholder: "적절히 처리" 류 없음. 각 스텝 실제 코드/명령/기대출력 포함. ✓
  • 타입/이름 정합: resolve_heartbeat_timeout 시그니처, SENSOR_ID/SENSOR_NAME/DEVICE_LOCATION/METRIC_SENSOR_IDS/SENSOR_HEARTBEAT_TIMEOUT_SEC 명칭 전 태스크 일관. ✓
  • 비밀값: 실 config_local.py/config.local.php 는 예시·문서만, 실값 라인은 제시(에이전트 미커밋). ✓