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>
87 lines
3.5 KiB
Python
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)
|