#!/usr/bin/env python3
"""RT-4D DMR-модуль (HF6853 / AUCTUS A6) — починка частотного смещения TX.

Модуль RT-4D — это одночиповый Auctus A6 (RDA8809), у которого частотная
калибровка TCXO хранится в NVRAM и правится СКРЫТЫМ ATE-интерфейсом:
    AT+GETFREQERR         -> прочитать N (в десятках Гц)
    AT+DMOFREQERR=<N>     -> записать N;  offset[Гц] = -2500 + 10*N,  N=0..500
После записи СМЕНИТЬ КАНАЛ на рации, чтобы применилось.

Наш случай ([[rt4d-dmr-tx-broken]]): TX смещена ~1100 Гц (в пределах ±2500),
поэтому один AT+DMOFREQERR должен вернуть декодирование хотспотом.

Реверс/библиотека — jhart99/a6tools (копия в research/a6tools). См.
docs/hf6853-auctus-a6.md.

БЕЗОПАСНОСТЬ:
  * По умолчанию РЕЖИМ probe/read (--probe / --read). Запись только с --write И --yes.
  * Точное значение требует ИЗМЕРЕНИЯ реальной TX по SDR (RTL-SDR).
  * Рация боевая — вслепую не писать.

Транспорт: два варианта COM-доступа —
  --port /dev/ttyUSBx      прямой pyserial (если модуль виден напрямую)
  --bridge                 через powershell-мост WSL (как tools/fm100b.py) — TODO,
                           заготовка ниже; сперва проверить probe напрямую.

Скорость Auctus по умолчанию 921600 8N1 XON/XOFF. Режим рации: «Обновление DMR
чипа» (вкл. с зажатой MENH/MENU) — прозрачный мост PC<->модуль.

Примеры:
    python3 a6_freqfix.py --port /dev/ttyUSB0 --probe
    python3 a6_freqfix.py --port /dev/ttyUSB0 --read
    # измерили: рация реально шлёт 438.799100 вместо 438.800000 (=-900 Гц)
    python3 a6_freqfix.py --port /dev/ttyUSB0 --measured 438799100 --nominal 438800000 --write --yes
"""
import sys, os, time, argparse

# использовать вендоренную библиотеку a6tools
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(HERE, "..", "research", "a6tools"))


def load_a6(port, baud, verbosity):
    from a6.serialio import (SerialIO, send_ate_command, atecps_resp_read,
                             send_uart_setup, get_freq_err, set_freq_err)
    from a6.a6commands import set_uart_to_host
    from a6.serialio import write_flush_pause
    SerialIO(port, baud, verbosity)          # singleton init
    return dict(send_ate=send_ate_command, resp=atecps_resp_read,
                setup=send_uart_setup, geterr=get_freq_err, seterr=set_freq_err,
                to_host=set_uart_to_host, wfp=write_flush_pause)


def probe(a6):
    """Проверить, отвечает ли модуль на RDA-debug (knock). content==0x80 => ок."""
    print("[probe] knock RDA-debug (read reg 0x03)…")
    ok = a6["setup"]()
    print("  ответ:", "МОДУЛЬ ОТВЕЧАЕТ (debug активен)" if ok else "нет ответа")
    if not ok:
        print("  -> модуль не в RDA-debug. Проверь режим рации / скорость (пробуй --baud 115200).")
    return ok


def read_err(a6):
    a6["send_ate"]("AT+DMOCONNECT")
    n = a6["geterr"]()
    print(f"[read] GETFREQERR N (в получаемом виде) -> offset={n} Гц")
    return n


def main():
    ap = argparse.ArgumentParser(description="HF6853/A6 TX freq-offset fix")
    ap.add_argument("--port", required=True, help="COM/tty (напр. /dev/ttyUSB0)")
    ap.add_argument("--baud", type=int, default=921600)
    ap.add_argument("-v", "--verbose", action="count", default=1)
    ap.add_argument("--probe", action="store_true", help="только проверить связь")
    ap.add_argument("--read", action="store_true", help="прочитать текущий freqerr")
    ap.add_argument("--measured", type=int, help="реально измеренная TX-частота, Гц (по SDR)")
    ap.add_argument("--nominal", type=int, help="номинальная (запрограммированная) частота, Гц")
    ap.add_argument("--write", action="store_true", help="применить новое значение")
    ap.add_argument("--yes", action="store_true", help="подтвердить запись (обязателен с --write)")
    a = ap.parse_args()

    a6 = load_a6(a.port, a.baud, a.verbose)

    if not probe(a6):
        if a.probe:
            return
        print("Прерываю: без связи писать нельзя.")
        return
    if a.probe:
        return

    cur = read_err(a6)
    if a.read and not a.write:
        return

    if a.write:
        if a.measured is None or a.nominal is None:
            print("Для --write нужны --measured и --nominal (по SDR).")
            return
        delta = a.nominal - a.measured           # сколько добавить к текущему offset
        target = cur + delta
        if abs(target) > 2500:
            print(f"Целевой offset {target} Гц вне ±2500 — так фиксить нельзя (нужна другая калибровка).")
            return
        N = int((target + 2500) / 10)
        print(f"[plan] cur={cur} Гц, delta={delta} Гц, target={target} Гц -> N={N}")
        if not a.yes:
            print("Добавь --yes чтобы записать. (ничего не записано)")
            return
        a6["send_ate"]("AT+DMOCONNECT")
        a6["seterr"](N)
        print(f"[write] AT+DMOFREQERR={N} отправлено. ТЕПЕРЬ СМЕНИ КАНАЛ на рации и проверь BER.")


if __name__ == "__main__":
    main()
