#!/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 : <1024 Б> -> 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()