#!/usr/bin/env python3
"""Подбор RXOffset хотспота под смещённую TX-частоту рации (без прибора).

Идея: TX рации смещена на постоянный X Гц (дефолтная калибровка). Двигаем приёмное
смещение хотспота (RXOffset) и при передаче рации (PTT) ищем в логе MMDVMHost
«received RF» — значит offset поймал передачу. По BER выбираем лучший.

Запуск на самом хотспоте (или по SSH). Владелец УДЕРЖИВАЕТ PTT на DMR-канале во время
каждого шага (окно ~6 с). Меняет RXOffset в MMDVM.ini, рестартит MMDVMHost, читает лог.

    python3 hotspot_rxoffset_sweep.py --range 1000 --step 100 --dwell 7

Требует sudo (запись MMDVM.ini + restart). Значения offset в Гц.
"""
import argparse, subprocess, re, time, sys

INI = "/opt/MMDVMHost/MMDVM.ini"
SVC = "radiostar-mmdvmhost"


def set_rxoffset(val):
    # заменить RXOffset= в секции [Modem], сохранив регистр
    sub = subprocess.run(
        ["sudo","sed","-i",f"s/^RXOffset=.*/RXOffset={val}/",INI],
        capture_output=True, text=True)
    return sub.returncode == 0


def restart():
    subprocess.run(["sudo","systemctl","restart",SVC],capture_output=True)


def watch_rf(dwell):
    """слушать журнал dwell секунд, вернуть (got_rf, ber)"""
    t0 = time.time()
    got = False; ber = None
    proc = subprocess.Popen(
        ["sudo","journalctl","-u",SVC,"-f","--since","now","-o","cat"],
        stdout=subprocess.PIPE, text=True)
    try:
        while time.time() - t0 < dwell:
            line = proc.stdout.readline()
            if not line: continue
            if "received RF" in line or "RF voice" in line or "RF late entry" in line:
                got = True
                m = re.search(r"BER:\s*([\d.]+)", line)
                if m: ber = float(m.group(1))
            if "BER:" in line and "RF" in line:
                m = re.search(r"BER:\s*([\d.]+)", line)
                if m: ber = float(m.group(1))
    finally:
        proc.terminate()
    return got, ber


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--range", type=int, default=1000, help="± Гц")
    ap.add_argument("--step", type=int, default=100)
    ap.add_argument("--dwell", type=int, default=7)
    a = ap.parse_args()

    offsets = list(range(-a.range, a.range+1, a.step))
    # порядок: от центра наружу
    offsets.sort(key=lambda x: abs(x))
    print(f"перебор RXOffset {offsets[0]}..{offsets[-1]} Гц, шаг {a.step}, окно {a.dwell}с")
    print("УДЕРЖИВАЙ PTT на DMR-канале рации во время КАЖДОГО шага!\n")
    best = None
    for off in offsets:
        set_rxoffset(off); restart(); time.sleep(2)
        print(f"  RXOffset={off:+5d} Гц … жми PTT", flush=True)
        got, ber = watch_rf(a.dwell)
        if got:
            print(f"    ✅ ПОЙМАНО RF! BER={ber}")
            if best is None or (ber is not None and ber < best[1]):
                best = (off, ber if ber is not None else 99)
            if ber is not None and ber < 3:
                print(f"    отличный BER — offset {off} рабочий, стоп")
                break
    if best:
        set_rxoffset(best[0]); restart()
        print(f"\nЛУЧШИЙ RXOffset={best[0]} Гц (BER {best[1]}) — записан и применён")
    else:
        print("\nне поймано — TX смещена сильнее диапазона или проблема в девиации, не частоте")


if __name__ == "__main__":
    main()
