#!/usr/bin/env python3 """ NL-42 / NL-52 bench CLI — talk to the meter over USB (or RS-232C), no deps. Setup on the meter first: MENU -> I/O -> Communication Interface -> "USB" (set BEFORE plugging in) Connect a generic USB-A -> mini-B cable directly (no hub). Find the port: ls -l /dev/ttyUSB* (and `dmesg | tail` after plugging in) Examples: python3 -m nl52.cli probe python3 -m nl52.cli --port /dev/ttyUSB0 status python3 -m nl52.cli start python3 -m nl52.cli stop python3 -m nl52.cli monitor --seconds 10 # DRD stream (needs NX-42EX) python3 -m nl52.cli poll --seconds 10 # DOD polling fallback (~1/s) python3 -m nl52.cli raw "System Version?NL" Run from the slmm/ directory (so the nl52 package is importable), or just run this file directly — it adds slmm/ to sys.path automatically. """ import argparse import os import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from nl52.client import NL52Client # noqa: E402 from nl52.protocol import DODSnapshot, DRDSample, ResultError # noqa: E402 def _fmt(v): return "--" if v is None else (f"{v:.1f}" if isinstance(v, float) else str(v)) def _print_dod(s: DODSnapshot): print(f" Lp = {_fmt(s.lp)} dB Leq = {_fmt(s.leq)} dB LE = {_fmt(s.le)} dB") print(f" Lmax = {_fmt(s.lmax)} dB Lmin = {_fmt(s.lmin)} dB") print(f" Ly = {_fmt(s.ly)} sub Lp = {_fmt(s.sub_lp)} dB") print(f" LN1..5 = {_fmt(s.ln1)} / {_fmt(s.ln2)} / {_fmt(s.ln3)} / {_fmt(s.ln4)} / {_fmt(s.ln5)}") print(f" overload={_fmt(s.overload)} underrange={_fmt(s.underrange)}") def cmd_probe(m: NL52Client, args): print("[*] Probing meter (read-only)...") print(f" System Version : {m.system_version()}") print(f" Clock : {m.get_clock()}") print(f" Measure state : {m.measure_state()}") try: print(f" Battery type : {m.battery_type()}") except Exception as e: print(f" Battery type : (n/a: {e})") try: print(f" SD free (MB?) : {m.sd_free_size()}") except Exception as e: print(f" SD free : (n/a: {e})") print("[OK] Two-way communication confirmed.") def cmd_status(m: NL52Client, args): state = m.measure_state() print(f"[*] Measure state: {state}") print("[*] DOD snapshot:") _print_dod(m.request_dod()) def cmd_start(m: NL52Client, args): print(f"[*] Measure,Start -> {m.measure_start()}") print(f" state now: {m.measure_state()}") def cmd_stop(m: NL52Client, args): print(f"[*] Measure,Stop -> {m.measure_stop()}") print(f" state now: {m.measure_state()}") def cmd_monitor(m: NL52Client, args): print(f"[*] DRD stream for {args.seconds}s (requires NX-42EX). Ctrl+C to stop.") def on_sample(s: DRDSample): print(f" #{_fmt(s.counter):>4} Lp={_fmt(s.lp)} Leq={_fmt(s.leq)} " f"Lmax={_fmt(s.lmax)} Lmin={_fmt(s.lmin)} sub={_fmt(s.sub_lp)}" f"{' OVERLOAD' if s.overload else ''}{' UNDER' if s.underrange else ''}") try: m.stream_drd(on_sample, duration=args.seconds) except ResultError as e: print(f"[!] DRD not available ({e}). The meter likely lacks the NX-42EX " f"option — use `poll` instead.") def cmd_poll(m: NL52Client, args): import time print(f"[*] Polling DOD ~1/s for {args.seconds}s. Ctrl+C to stop.") end = time.time() + args.seconds while time.time() < end: s = m.request_dod() print(f" Lp={_fmt(s.lp)} Leq={_fmt(s.leq)} Lmax={_fmt(s.lmax)} " f"Lmin={_fmt(s.lmin)} sub={_fmt(s.sub_lp)}" f"{' OVERLOAD' if s.overload else ''}{' UNDER' if s.underrange else ''}") def cmd_raw(m: NL52Client, args): resp = m.send(args.command) print(f" > {args.command}") print(f" < {resp}") def main(): ap = argparse.ArgumentParser(description="RION NL-42/NL-52 bench CLI") ap.add_argument("--port", default="/dev/ttyUSB0") ap.add_argument("--baud", type=int, default=115200) ap.add_argument("--timeout", type=float, default=3.0) ap.add_argument("--echo-on", action="store_true", help="Don't send Echo,Off on connect") sub = ap.add_subparsers(dest="cmd", required=True) sub.add_parser("probe", help="read-only sanity check") sub.add_parser("status", help="measure state + DOD snapshot") sub.add_parser("start", help="Measure,Start") sub.add_parser("stop", help="Measure,Stop") mon = sub.add_parser("monitor", help="DRD stream (needs NX-42EX)") mon.add_argument("--seconds", type=float, default=10) pol = sub.add_parser("poll", help="DOD polling fallback (~1/s)") pol.add_argument("--seconds", type=float, default=10) raw = sub.add_parser("raw", help="send an arbitrary command") raw.add_argument("command") args = ap.parse_args() if not os.path.exists(args.port): print(f"[!] {args.port} not found. Plug in the meter (Comm Interface=USB) and check:") print(" ls -l /dev/ttyUSB* ; dmesg | tail -20") return 2 handlers = { "probe": cmd_probe, "status": cmd_status, "start": cmd_start, "stop": cmd_stop, "monitor": cmd_monitor, "poll": cmd_poll, "raw": cmd_raw, } try: with NL52Client(args.port, baud=args.baud, timeout=args.timeout, disable_echo=not args.echo_on) as m: handlers[args.cmd](m, args) except PermissionError: print(f"[!] Permission denied on {args.port}. Add yourself to dialout:") print(" sudo usermod -aG dialout $USER (then log out/in)") return 2 except KeyboardInterrupt: print("\n[*] Interrupted.") except (TimeoutError, ResultError) as e: print(f"[!] {type(e).__name__}: {e}") print(" Check: Comm Interface=USB, ECO/Sleep OFF, correct port/baud.") return 1 return 0 if __name__ == "__main__": sys.exit(main())