#!/usr/bin/env python3
"""Восстановление калибровки рации (SPI region 0x40, 4 КБ) из заводского эталона.

Пишет 4 КБ калибровки CPS-протоколом (USART6) из WSL через powershell.exe.
Источник — radio-spi-dump.bin[0:0x1000] (снят ДО экспериментов).

Протокол записи (CPS):
  notify : 34 00 00 10 44                       -> ACK 06
  write  : <region> <blkHi> <blkLo> <1024 Б> <cksum>  -> ACK 06   (region=0x40 калибровка)
  cksum  : sum & 0xFF, seed 0

    python3 tools/spi_write_cal.py            # запись region 0x40 из эталона (с подтверждением)
    python3 tools/spi_write_cal.py --yes      # без подтверждения
"""
import argparse, subprocess, sys, os, shutil

WIN_TMP_WSL = "/mnt/c/Users/vikto/AppData/Local/Temp"
WIN_TMP_WIN = r"C:\Users\vikto\AppData\Local\Temp"
REF = "radio-spi-dump.bin"
REGION = 0x40          # calibration
SIZE   = 0x1000        # 4 КБ

PS = r'''
$ErrorActionPreference='Stop'
$data=[System.IO.File]::ReadAllBytes("{datawin}")
$p=New-Object System.IO.Ports.SerialPort "{port}",115200,"None",8,"one"
$p.ReadTimeout=1500; $p.WriteTimeout=3000
try {{ $p.Open() }} catch {{ Write-Output "ERR:openfail"; exit 1 }}
function ck([byte[]]$a,$n){{ $s=0; for($i=0;$i -lt $n;$i++){{ $s=($s+$a[$i]) -band 0xFF }}; return [byte]$s }}
$p.DiscardInBuffer()
$nf=[byte[]](0x34,0x00,0x00,0x10,0x44); $p.Write($nf,0,5)
$ok=$false; $sw=[Diagnostics.Stopwatch]::StartNew()
while($sw.ElapsedMilliseconds -lt 3000){{ try {{ if($p.ReadByte() -eq 6){{ $ok=$true; break }} }} catch {{}} }}
if(-not $ok){{ Write-Output "ERR:notify"; $p.Close(); exit 1 }}
Write-Output "INFO:notify ok"
$region={region}; $size={size}
for($i=0; $i -lt $size; $i+=1024){{
  $cmd=New-Object byte[] 1028
  $cmd[0]=$region
  $blk=[int]($i/1024)
  $cmd[1]=(($blk -shr 8) -band 0xFF); $cmd[2]=($blk -band 0xFF)
  [Array]::Copy($data,$i,$cmd,3,1024)
  $cmd[1027]=ck $cmd 1027
  $p.DiscardInBuffer(); $p.Write($cmd,0,1028)
  $ok=$false; $sw2=[Diagnostics.Stopwatch]::StartNew()
  while($sw2.ElapsedMilliseconds -lt 3000){{ try {{ if($p.ReadByte() -eq 6){{ $ok=$true; break }} }} catch {{}} }}
  if(-not $ok){{ Write-Output ("ERR:write@"+$blk); $p.Close(); exit 1 }}
  Write-Output ("PROG:"+$blk)
}}
$cl=[byte[]](0x34,0x52,0x05,0xEE,0x79); $p.Write($cl,0,5)
$p.Close()
Write-Output "OK"
'''


def find_port():
    out = subprocess.run(["powershell.exe","-NoProfile","-Command",
        "[System.IO.Ports.SerialPort]::GetPortNames() -join ','"],
        capture_output=True, text=True).stdout
    ports=[p for p in out.strip().replace("\r","").split(",") if p]
    if not ports: sys.exit("нет COM-порта")
    return ports[0]


def main():
    ap=argparse.ArgumentParser()
    ap.add_argument("--yes", action="store_true")
    ap.add_argument("--port")
    a=ap.parse_args()

    ref=open(REF,"rb").read()
    cal=ref[:SIZE]
    print(f"калибровка из эталона: {SIZE} Б, region 0x{REGION:02X}")
    print(f"  начало: {cal[:16].hex(' ')}")
    if not a.yes:
        print("\nЗАПИСЬ в SPI-калибровку рации. Рация должна быть в ОБЫЧНОМ режиме.")
        if input("продолжить? [y/N] ").strip().lower() != "y":
            print("отменено"); return

    name="rt4d_cal.bin"
    open(os.path.join(WIN_TMP_WSL,name),"wb").write(cal)
    datawin=os.path.join(WIN_TMP_WIN,name)
    port=a.port or find_port()
    script=PS.format(datawin=datawin.replace("\\","\\\\"),port=port,region=REGION,size=SIZE)
    proc=subprocess.Popen(["powershell.exe","-NoProfile","-Command",script],
        stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True,encoding="utf-8",errors="replace")
    rc=0
    for line in proc.stdout:
        line=line.strip()
        if line.startswith("PROG:"): print(f"  записан блок {line[5:]}")
        elif line.startswith("INFO:"): print(f"  {line[5:]}")
        elif line.startswith("ERR:"): print(f"  ОШИБКА: {line[4:]}"); rc=1
        elif line=="OK": print("  ✅ калибровка восстановлена")
    proc.wait()
    sys.exit(rc)


if __name__=="__main__":
    main()
