#!/usr/bin/env python3
"""Зонд чтения флеша Beken-загрузчика модуля: catch link-check -> один read4k(0) -> сырой ответ.

Фрейминг (из DMR Upgrade Tool SendEraseFlashReq): 01 E0 FC FF F4 <len_lo> <len_hi> <CMD> <addr:4 LE> ...
Erase=0x0F. Read4K пробуем CMD 0x09: 01 E0 FC FF F4 05 00 09 <addr:4 LE>. ТОЛЬКО ЧТЕНИЕ.

Процедура: запусти, затем зажми MENU и включи рацию. Тул поймает загрузчик и прочитает 1 сектор
по адресу 0 — покажет формат ответа (по нему построим полный дамп).
"""
import argparse, subprocess

PS = r'''
$p=New-Object System.IO.Ports.SerialPort "{port}",115200,"None",8,"one"
$p.ReadTimeout=1; $p.WriteTimeout=50; $p.ReadBufferSize=16384
try{{$p.Open()}}catch{{Write-Output "ERR:$($_.Exception.Message)";exit}}
$lc=[byte[]]@(1,224,252,1,0)
# read4k addr=0: 01 E0 FC FF F4 05 00 09 00 00 00 00
$rd=[byte[]]@(1,224,252,255,244,5,0,{cmd},0,0,0,0)
$sw=[Diagnostics.Stopwatch]::StartNew(); $state="catch"; $roll=""
while($sw.Elapsed.TotalSeconds -lt {sec}){{
  if($state -eq "catch"){{
    try{{$p.Write($lc,0,$lc.Length)}}catch{{}}
    $n=$p.BytesToRead
    if($n -gt 0){{
      $b=New-Object byte[] $n;$p.Read($b,0,$n)|Out-Null
      $hex=($b|ForEach-Object{{"{{0:x2}}"-f $_}}) -join ""
      $roll=($roll+$hex); if($roll.Length -gt 400){{$roll=$roll.Substring($roll.Length-400)}}
      if($hex -match "484b4c46"){{ Write-Output "BANNER" }}
      if($roll -match "040e05"){{
        Write-Output "CATCH"
        # слить бэклог link-check ответов до тишины (~250мс без байт)
        $q=[Diagnostics.Stopwatch]::StartNew(); $last=0
        while($q.ElapsedMilliseconds -lt 400){{
          $m=$p.BytesToRead
          if($m -gt 0){{ $tb=New-Object byte[] $m;$p.Read($tb,0,$m)|Out-Null; $last=$q.ElapsedMilliseconds }}
        }}
        $p.DiscardInBuffer()
        $p.Write($rd,0,$rd.Length)   # read4k
        $state="read"; $rsw=[Diagnostics.Stopwatch]::StartNew()
        $acc=New-Object System.Collections.Generic.List[byte]
      }}
    }}
  }} else {{
    $n=$p.BytesToRead
    if($n -gt 0){{ $b=New-Object byte[] $n;$p.Read($b,0,$n)|Out-Null; foreach($x in $b){{$acc.Add($x)}} }}
    if($rsw.Elapsed.TotalMilliseconds -gt 2500){{
      $hx=($acc.ToArray()|ForEach-Object{{"{{0:x2}}"-f $_}}) -join ""
      Write-Output ("RDLEN:"+$acc.Count)
      Write-Output ("RDHEAD:"+$hx.Substring(0,[Math]::Min(96,$hx.Length)))
      if($hx.Length -gt 96){{ Write-Output ("RDTAIL:"+$hx.Substring($hx.Length-32)) }}
      break
    }}
  }}
}}
$p.Close(); Write-Output "END"
'''

def main():
    ap=argparse.ArgumentParser()
    ap.add_argument("--port",default="COM4"); ap.add_argument("--sec",type=int,default=30)
    ap.add_argument("--cmd",default="9",help="опкод чтения (9=read4k, 3=readsector)")
    a=ap.parse_args()
    print(f"[зонд чтения, CMD 0x{int(a.cmd):02x}, {a.port}, {a.sec}с] — ЗАЖМИ MENU и включи рацию\n")
    r=subprocess.run(["powershell.exe","-NoProfile","-Command",
                      PS.format(port=a.port,sec=a.sec,cmd=int(a.cmd))],capture_output=True,text=True)
    for line in r.stdout.replace("\r","").split("\n"):
        line=line.strip()
        if line.startswith(("ERR:","CATCH","RDLEN:","RDHEAD:","RDTAIL:")): print("  "+line)
    print("\n(RDLEN ~4096+ и RDHEAD с данными = read4k работает. RDLEN 0/мало = опкод неверный, пробуй --cmd 3)")

if __name__=="__main__":
    main()
