#!/usr/bin/env python3 """Sensor/format self-check — Pi 4 SHT30 (Sensor 2) module. 1) Unit-check that CRC-8 / fixed2 / body format match the firmware and server spec 2) Take one real (or simulated) SHT30 measurement -> print converted values Step 1) must always pass even without hardware (core guarantee of signature compatibility). """ import sys import wire from sht30_driver import crc8, parse_frame, SHT30, SMBUS_AVAILABLE # The format/CRC self-check must work even without secrets (config_local.py). # Since config.py fails closed and aborts on import when config_local.py is missing, # fall back to defaults for dev-PC checks (server connection is handled by 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 missing -> checking with I2C defaults (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 self-check (Sensor 2 module)") print("=" * 50) passed = True # ── 1) CRC-8 check (Sensirion standard vector 0xBEEF → 0x92) ── print("\n[1] CRC-8 (poly 0x31, init 0xFF):") passed &= check("crc8(0xBEEF)", crc8(b"\xBE\xEF"), 0x92) # ── 2) fixed2 check (round-half-up, same as reference.py/jb_fixed2) ── print("\n[2] fixed2 (2 decimal places, 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) Frame parsing check (synthetic frame including CRC) ── print("\n[3] parse_frame (synthetic frame conversion):") raw_t, raw_rh = 0x6400, 0x8000 # arbitrary raw values 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) Body format check (field order/type) ── print("\n[4] Body format (fixed field order):") 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" Signature(key=test): {wire.sign_raw('test', body)[:16]}...") # ── 5) One real/simulated measurement ── print("\n[5] One measurement:") sim = FORCE_SIMULATION or not SMBUS_AVAILABLE print(f" Mode: {'simulation' if sim else f'real 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" Reading: T={t:.2f}C RH={rh:.2f}%") except Exception as e: print(f" Measurement failed: {e}") if not sim: print(" (check 0x44 response with i2cdetect -y 1 / check wiring, pull-ups, I2C enabled)") print("\n" + "=" * 50) print(f" Result: {'ALL PASSED ✅' if passed else 'SOME FAILED ❌'}") print("=" * 50) sys.exit(0 if passed else 1)