POSA_LEAKSMS/raspberry_pi4_sht30/test_sensor.py
cw2007 73e49fd528 feat: RPi4 누수+온습도 2-모듈 단일 서버 통합 + 보안 하드닝
2번 모듈 신규(raspberry_pi4_sht30/): SHT30 온습도 → cafe24, raw-body X-Signature HMAC, 폐쇄망 오프라인 설치(vendor/install), systemd, CRC/포맷 자가점검. firmware sht30_fw wire 포맷과 byte-parity 검증.

1번 모듈 통합(raspberry_pi4/): 누수+카메라를 동일 서버로. 레거시 sha256(key+json) body-sig → X-Signature HMAC 전환, 사진 canonical=device_id|sensor_id|log_id|timestamp.

서버 통합(php/): api/sensor_data.php 누수 이벤트 수신, api/photo_upload.php 신규(HMAC·finfo·서버생성 파일명·nonce 멱등·sensor 결속), photo.php 인증 사진서빙, dashboard.php 누수 패널+사진, sms_send.php 누수 SMS, config.php 태그/요약.

DB: sql/migration_add_leak.sql(가산·멱등), schema_sht30.sql 통합 스키마.

보안 하드닝(외부 펜테스트 대비, 확정 CRITICAL/HIGH 0): cron_heartbeat api_key fail-closed, setup_mfa 부트스트랩 토큰 post-enrollment 잠금, var/ PHP 실행차단, HSTS.

문서/테스트: SYSTEM_OVERVIEW.md, security_hardening_test.php 갱신. 참고: 사용자 요청으로 세션 외 기존 미커밋 변경도 함께 포함.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:21:11 +09:00

87 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""센서/포맷 자가점검 — Pi 4 SHT30(2번) 모듈.
1) CRC-8 / fixed2 / 본문 포맷이 펌웨어·서버 규약과 일치하는지 단위 점검
2) 실제(또는 시뮬레이션) SHT30 측정 1회 → 변환값 출력
하드웨어 없이도 1)은 항상 통과해야 한다(서명 호환성의 핵심 보증).
"""
import sys
import wire
from sht30_driver import crc8, parse_frame, SHT30, SMBUS_AVAILABLE
# 포맷/CRC 자가점검은 비밀값(config_local.py) 없이도 가능해야 한다.
# config.py 는 config_local.py 가 없으면 import 시 fail-closed 로 중단하므로,
# 개발 PC 점검을 위해 기본값으로 폴백한다(서버 연결은 test_connection.py 담당).
try:
from config import I2C_BUS, I2C_ADDR, FORCE_SIMULATION
except RuntimeError:
I2C_BUS, I2C_ADDR, FORCE_SIMULATION = 1, 0x44, False
print(" [info] config_local.py 없음 → I2C 기본값(bus=1, 0x44)으로 점검")
def check(name, got, want):
ok = got == want
print(f" [{'OK ' if ok else 'FAIL'}] {name}: got={got!r} want={want!r}")
return ok
print("=" * 50)
print(" SHT30 자가점검 (2번 모듈)")
print("=" * 50)
passed = True
# ── 1) CRC-8 점검 (Sensirion 표준 벡터 0xBEEF → 0x92) ──
print("\n[1] CRC-8 (poly 0x31, init 0xFF):")
passed &= check("crc8(0xBEEF)", crc8(b"\xBE\xEF"), 0x92)
# ── 2) fixed2 점검 (round-half-up, reference.py/jb_fixed2 동일) ──
print("\n[2] fixed2 (소수 2자리 round-half-up):")
passed &= check("24.0", wire.fixed2(24.0), "24.00")
passed &= check("48.5", wire.fixed2(48.5), "48.50")
passed &= check("-3.245", wire.fixed2(-3.245), "-3.25")
passed &= check("0.0", wire.fixed2(0.0), "0.00")
# ── 3) 프레임 파싱 점검 (CRC 포함 합성 프레임) ──
print("\n[3] parse_frame (합성 프레임 변환):")
raw_t, raw_rh = 0x6400, 0x8000 # 임의 raw 값
frame = bytes([
raw_t >> 8, raw_t & 0xFF, crc8(bytes([raw_t >> 8, raw_t & 0xFF])),
raw_rh >> 8, raw_rh & 0xFF, crc8(bytes([raw_rh >> 8, raw_rh & 0xFF])),
])
t, rh = parse_frame(frame)
exp_t = -45.0 + 175.0 * (raw_t / 65535.0)
exp_rh = 100.0 * (raw_rh / 65535.0)
passed &= check("temp_c", round(t, 4), round(exp_t, 4))
passed &= check("rh", round(rh, 4), round(exp_rh, 4))
# ── 4) 본문 포맷 점검 (필드 순서/타입) ──
print("\n[4] 본문 포맷 (필드 순서 고정):")
body = wire.sht30_event_body("dev", "loc", 2, "s2", "periodic",
1700000000, 24.0, 48.0, "normal", "vX")
expected = ('{"device_id":"dev","device_location":"loc","sensor_id":2,'
'"sensor_name":"s2","event_type":"periodic","timestamp":1700000000,'
'"metric_type":"sht30","temperature_c":24.00,"humidity_percent":48.00,'
'"metric_status":"normal","app_version":"vX"}')
passed &= check("body", body, expected)
print(f" 서명(key=test): {wire.sign_raw('test', body)[:16]}...")
# ── 5) 실제/시뮬레이션 측정 1회 ──
print("\n[5] 측정 1회:")
sim = FORCE_SIMULATION or not SMBUS_AVAILABLE
print(f" 모드: {'시뮬레이션' if sim else f'실제 I2C(bus={I2C_BUS},0x{I2C_ADDR:02X})'}")
try:
with SHT30(bus_num=I2C_BUS, addr=I2C_ADDR, simulate=sim) as s:
t, rh = s.measure()
print(f" 측정값: T={t:.2f}C RH={rh:.2f}%")
except Exception as e:
print(f" 측정 실패: {e}")
if not sim:
print(" (i2cdetect -y 1 로 0x44 응답 확인 / 배선·풀업·I2C 활성화 점검)")
print("\n" + "=" * 50)
print(f" 결과: {'전체 통과 ✅' if passed else '실패 항목 있음 ❌'}")
print("=" * 50)
sys.exit(0 if passed else 1)