"""Sens'it Discovery payload decoder — all 6 modes.""" MODES = { 0: "Standby / Button", 1: "Temperature", 2: "Light", 3: "Door", 4: "Vibration", 5: "Magnet", } def decode(payload_hex: str) -> dict: """ Decode a 4-byte Sens'it Discovery payload. Format (per Sens'it Discovery v3.1.0+ spec): Byte 0: bits 7-3 = battery level, bits 2-0 = reserved (0b110) Byte 1: bits 7-3 = mode, bits 2-1 = mode-specific / spare, bit 0 = button flag Byte 2-3: mode-specific data Battery formula: V = (raw × 0.05) + 2.7 """ payload_hex = payload_hex.strip().lower().replace("0x", "").replace(" ", "") if len(payload_hex) != 8: return {"error": f"payload must be 4 bytes (8 hex chars), got '{payload_hex}'"} try: b0, b1, b2, b3 = (int(payload_hex[i : i + 2], 16) for i in range(0, 8, 2)) except ValueError as e: return {"error": str(e)} # Common fields battery_raw = (b0 >> 3) & 0x1F battery_volts = round(battery_raw * 0.05 + 2.7, 2) reserved = b0 & 0x07 mode = (b1 >> 3) & 0x1F button_alert = bool(b1 & 0x01) result = { "raw": payload_hex, "battery": battery_volts, "battery_raw": battery_raw, "reserved_ok": reserved == 0b110, "mode": MODES.get(mode, f"Unknown ({mode})"), "mode_id": mode, "button_alert": button_alert, } # Mode-specific decoding if mode == 1: # Temperature temp_msb = (b1 >> 2) & 0x01 temp_raw = (temp_msb << 8) | b2 result["temperature_c"] = round((temp_raw - 200) / 8, 2) result["humidity_pct"] = round(b3 / 2, 1) elif mode == 2: # Light brightness_msb = (b1 >> 2) & 0x01 brightness_raw = (brightness_msb << 8) | b2 result["brightness_lux"] = round(brightness_raw / 96, 2) elif mode == 3: # Door door_status_bits = (b1 >> 1) & 0x03 door_map = {0: "closed", 1: "opened", 2: "opening_alert", 3: "closing_alert"} result["door_status"] = door_map.get(door_status_bits, "unknown") result["event_count"] = (b2 << 8) | b3 elif mode == 4: # Vibration vib_status_bits = (b1 >> 1) & 0x03 vib_map = {0: "no_vibration", 1: "vibration_detected"} result["vibration_status"] = vib_map.get(vib_status_bits, "unknown") result["event_count"] = (b2 << 8) | b3 elif mode == 5: # Magnet mag_status_bits = (b1 >> 1) & 0x03 mag_map = {0: "no_magnet", 1: "magnet_detected"} result["magnet_status"] = mag_map.get(mag_status_bits, "unknown") result["event_count"] = (b2 << 8) | b3 return result if __name__ == "__main__": # Sanity checks with the payloads we captured for h in ("b60dc86e", "a60dc271", "b6043081"): print(h, "→", decode(h))