|
| 1 | +#! /usr/bin/python3 |
| 2 | +##--------------------------------------------------------------------\ |
| 3 | +# hackrfpy 'examples/power_meter.py' |
| 4 | +# Live signal-strength meter at one frequency, using capture_callback. |
| 5 | +# Demonstrates the callback API (the binder-style inverted loop) on a real |
| 6 | +# stream: register a function, get each decoded complex64 block as it |
| 7 | +# arrives, print a rolling power reading. Read-only / receive-only. |
| 8 | +# |
| 9 | +# Usage: |
| 10 | +# uv run python examples/power_meter.py --freq 100e6 |
| 11 | +# uv run python examples/power_meter.py --freq 433.92e6 --rate 2e6 |
| 12 | +##--------------------------------------------------------------------\ |
| 13 | +import argparse |
| 14 | +import sys |
| 15 | +import numpy as np |
| 16 | +from hackrfpy import HackRF |
| 17 | + |
| 18 | + |
| 19 | +def main(): |
| 20 | + p = argparse.ArgumentParser(description="Live power meter (read-only).") |
| 21 | + p.add_argument("--freq", default="100e6", help="center frequency Hz") |
| 22 | + p.add_argument("--rate", default="2e6", help="sample rate sps") |
| 23 | + p.add_argument("--tools-dir", default=None) |
| 24 | + args = p.parse_args() |
| 25 | + freq, rate = float(args.freq), float(args.rate) |
| 26 | + |
| 27 | + h = HackRF(tools_dir=args.tools_dir) |
| 28 | + det = h.detect() |
| 29 | + if not det["ready"]: |
| 30 | + print(f"no usable HackRF: {det['problem']}", file=sys.stderr) |
| 31 | + return 1 |
| 32 | + |
| 33 | + print(f"[*] power meter @ {freq/1e6:g} MHz (Ctrl-C to stop)\n") |
| 34 | + |
| 35 | + # bar-graph scale in dBFS: roughly noise floor (-90) to full-scale (0) |
| 36 | + LO, HI, WIDTH = -90.0, 0.0, 40 |
| 37 | + |
| 38 | + def on_block(iq, total): |
| 39 | + if len(iq) == 0: |
| 40 | + return |
| 41 | + # raw dBFS, plus the gain-normalized relative reading (Level 1): the |
| 42 | + # relative value is comparable across gain settings; pass offset_db |
| 43 | + # from a calibration run (see examples/calibrate.py) to approximate dBm. |
| 44 | + db = h.power_dbfs(iq) |
| 45 | + rel = h.relative_power_db(db, lna=16, vga=20, amp=False) |
| 46 | + frac = max(0.0, min(1.0, (db - LO) / (HI - LO))) |
| 47 | + bar = "#" * int(frac * WIDTH) |
| 48 | + sys.stdout.write(f"\r {db:7.1f} dBFS (rel {rel:7.1f} dB) " |
| 49 | + f"|{bar:<{WIDTH}}|") |
| 50 | + sys.stdout.flush() |
| 51 | + |
| 52 | + try: |
| 53 | + # callback fires per block; we never reach a sample cap, so it runs |
| 54 | + # until Ctrl-C, and the context manager inside reaps the child. |
| 55 | + h.capture_callback(freq, rate, on_block=on_block) |
| 56 | + except KeyboardInterrupt: |
| 57 | + pass |
| 58 | + print("\n[*] stopped") |
| 59 | + return 0 |
| 60 | + |
| 61 | + |
| 62 | +if __name__ == "__main__": |
| 63 | + sys.exit(main()) |
0 commit comments