누수 노드(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
21 KiB
누수 노드 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.sh1회 실행으로 누수 + 온습도 두 서비스 설치·기동. -
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 는 예시·문서만, 실값 라인은 제시(에이전트 미커밋). ✓