POSA_LEAKSMS/raspberry_pi4_sht30
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
..
systemd i18n(pi): Raspberry Pi 번들 소스 한글 → 영어 (콘솔 mojibake 해소) 2026-07-10 10:44:13 +09:00
.gitignore i18n(pi): Raspberry Pi 번들 소스 한글 → 영어 (콘솔 mojibake 해소) 2026-07-10 10:44:13 +09:00
config.py feat(sht30): 누수 노드에 온습도 동일-id 통합 (소스·서버예시·테스트·문서) 2026-07-15 10:16:49 +09:00
config_local.example.py i18n(pi): Raspberry Pi 번들 소스 한글 → 영어 (콘솔 mojibake 해소) 2026-07-10 10:44:13 +09:00
install.sh i18n(pi): Raspberry Pi 번들 소스 한글 → 영어 (콘솔 mojibake 해소) 2026-07-10 10:44:13 +09:00
README.md i18n(pi): Raspberry Pi 번들 소스 한글 → 영어 (콘솔 mojibake 해소) 2026-07-10 10:44:13 +09:00
requirements.txt i18n(pi): Raspberry Pi 번들 소스 한글 → 영어 (콘솔 mojibake 해소) 2026-07-10 10:44:13 +09:00
sht30_driver.py feat(sht30): 벤더 예제(P4422-2) 기반 단발 측정 명령 0x2400 전환 2026-07-10 10:42:10 +09:00
sht30_sensor.py i18n(pi): Raspberry Pi 번들 소스 한글 → 영어 (콘솔 mojibake 해소) 2026-07-10 10:44:13 +09:00
test_connection.py i18n(pi): Raspberry Pi 번들 소스 한글 → 영어 (콘솔 mojibake 해소) 2026-07-10 10:44:13 +09:00
test_sensor.py i18n(pi): Raspberry Pi 번들 소스 한글 → 영어 (콘솔 mojibake 해소) 2026-07-10 10:44:13 +09:00
vendor.sh i18n(pi): Raspberry Pi 번들 소스 한글 → 영어 (콘솔 mojibake 해소) 2026-07-10 10:44:13 +09:00
wire.py i18n(pi): Raspberry Pi 번들 소스 한글 → 영어 (콘솔 mojibake 해소) 2026-07-10 10:44:13 +09:00
WIRING.html i18n(pi): Raspberry Pi 번들 소스 한글 → 영어 (콘솔 mojibake 해소) 2026-07-10 10:44:13 +09:00

Temperature/Humidity (SHT30) Sensor Module — Raspberry Pi 4 Model B only (Module 2)

A module that reads a Sensirion SHT30 (Hanjin Data P4422-3, I2C) temperature/humidity sensor over the Raspberry Pi 4 Model B I2C bus and periodically reports to the cafe24 server (php/api/sensor_data.php). The server stores the measurements and performs high/low temperature and high/low humidity threshold evaluation, SMS, and dashboard display.

This folder is a port of the STM32F407 firmware (sht30_fw) behavior to Raspberry Pi 4 + Python, the "Module 2". The wire format (body/signature), CRC, and conversion formulas are byte-for-byte identical to the firmware. "Module 1" (leak + camera) is in the ../raspberry_pi4/ folder. For the full setup, see SYSTEM_OVERVIEW.md in the root.


1. Files

File Role
sht30_sensor.py Main — periodic measurement → body build/sign → POST (with retry), startup→periodic
sht30_driver.py SHT30 I2C measurement + CRC-8 verification + physical-value conversion (smbus2, simulation fallback)
wire.py Body JSON + raw-body HMAC signature (byte-identical to firmware jsonbody.c/sig.c)
config.py Non-secret default settings (sensor id/interval/range/I2C). Loads secrets from config_local
config_local.example.py Operational/secret-value template → copy to config_local.py for use (not committed)
requirements.txt Python dependencies (requests, smbus2, pinned)
install.sh Pi 4 install (air-gapped offline + online fallback, includes enabling I2C)
vendor.sh Pre-download dependencies for air-gapped use (run on the build machine)
test_connection.py Server connectivity/signed-POST check
test_sensor.py CRC/format self-check + one measurement (format check passes even without hardware)
systemd/sht30-sensor.service Service unit for auto-start on boot

2. Hardware (Pi 4 Model B baseline)

  • Raspberry Pi 4 Model B (BCM2711) + Raspberry Pi OS Bookworm 64-bit
  • SHT30 temperature/humidity sensor module (I2C, Hanjin Data P4422-3)
  • I2C pull-up resistors 4.7kΩ × 2 (omit if built into the module)
  • Power: USB-C 5V/3A, network: Pi 4 Gigabit Ethernet (wired) or WiFi

2.1 Pinout (I2C bus 1)

Signal Pi 4 pin Note
SHT30 VCC 3.3V (physical 1) No 5V
SHT30 GND GND (physical 6/9/14/…)
SHT30 SDA GPIO2 / SDA1 (physical 3) + 4.7kΩ pull-up → 3.3V
SHT30 SCL GPIO3 / SCL1 (physical 5) + 4.7kΩ pull-up → 3.3V
SHT30 ADDR GND → address 0x44 0x45 if tied to VDD (config.py I2C_ADDR)

3. Server compatibility (must verify)

This module reports to the current repo's php/ server (= the cafe24 SHT30 dashboard server). The server requires a raw-body HMAC signature.

X-Signature = lowercase_hex( HMAC-SHA256( key=API_KEY, msg=request body bytes ) )
  • API_KEY in config_local.py must be exactly identical to API_KEY in the server's config.local.php.
  • The server blocks replays via timestamp freshness (±600 seconds), so the Pi clock must be NTP-synchronized (enabled by default on Raspberry Pi OS).
  • It reports with sensor_id=2, and the server dashboard (dashboard.php) shows sensor 2 as the default chart.
  • Threshold alerts (high/low temperature, high/low humidity), SMS, and recovery evaluation are all performed by the server. This module only reports measurements.

For the server-side schema/deployment, see ../sql/schema_sht30.sql, ../docs/INSTALL_PI_SERVER.md, ../docs/SHT30_SENSOR2_SETUP.md.


4. Installation

4.1 Prepare configuration values

cp config_local.example.py config_local.py
# Open config_local.py and fill in API_URL / API_KEY / DEVICE_ID / DEVICE_LOCATION

If config_local.py is missing, execution stops immediately with a clear error (fail closed). Never commit config_local.py or API_KEY (handled by .gitignore).

4.2 Online install (internet-capable environment)

chmod +x install.sh
./install.sh
sudo reboot      # reboot once if I2C was newly enabled

4.3 Air-gapped offline install

On an internet-connected machine of the same architecture (aarch64, ideally the same Pi 4 + Bookworm 64-bit), download the dependencies first to build a bundle, then move it to the target Pi 4 and install.

# (build machine, internet YES) generate wheelhouse/ + debs/
./vendor.sh

# after copying wheelhouse/ and debs/ into this folder on the target Pi 4:
# (target Pi 4, internet NO)
./install.sh        # if debs/ · wheelhouse/ exist, it installs offline automatically

smbus2 is pure Python (arch-independent), so it can be downloaded on an x86 build machine. Other wheels/debs such as requests must be the aarch64 builds (running vendor.sh on the target arch is recommended).


5. Run / check

# is the sensor visible (0x44)
i2cdetect -y 1

# format/CRC self-check + one measurement (simulation if no hardware)
source venv/bin/activate
python3 test_sensor.py

# server connectivity/signature check
python3 test_connection.py

# actual run
python3 sht30_sensor.py

In environments without smbus2 installed or with I2C disabled (dev PC, etc.), it automatically runs in simulation mode.


6. Auto-start on boot (systemd)

# assumes the folder is placed at /home/pi/raspberry_pi4_sht30 (otherwise edit the path in the .service)
sudo cp systemd/sht30-sensor.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable sht30-sensor
sudo systemctl start sht30-sensor
sudo systemctl status sht30-sensor
journalctl -u sht30-sensor -f

SupplementaryGroups=i2c is required for /dev/i2c-1 access.


7. Security notes

  • Secrets (API_KEY), server URL, and device info live only in config_local.py (not committed). Committed files contain only placeholders.
  • The report body is protected against tampering by an HMAC-SHA256(API_KEY, raw_body) signature (X-Signature header) (immune to length-extension attacks). The legacy body-field sha256 fallback is disabled by default on the server.
  • The signed bytes and the transmitted bytes are always identical (wire.py builds the body directly, signs those bytes, then raw POSTs). This matches firmware sig.c / reference.py / server verify_signature_raw byte-for-byte.
  • If config_local.py is exposed, immediately rotate API_KEY (change it on the server at the same time).