#!/usr/bin/env python3 """RT-4D: РИД-ОНЛИ снятие калибровки DMR-модуля протоколом штатного тюнера. Реплика последовательности Radio Tuning Software (реверс, docs/tuning-software-protocol.md): одна сессия COM @115200, шлём кадры и ловим ответы. ТОЛЬКО ЧТЕНИЕ — ни одной write/set-команды калибровки (CmdWriteAll/Set* НЕ шлём). Рация в режиме «Обновление DMR чипа». Под WSL COM через powershell.exe. Порядок: CmdConnect(34 52 05 10 9b) -> [опц. CmdOpenCal 68 b0..01] -> Read* (68 b?..) -> CmdColseCal -> CmdEnd """ import argparse, subprocess, sys, json # --- команды (из реверса, research/tuning-re/command-templates.txt) --- CMDS = { "Ping": "68 84 01 01 00 00 00 01 00 10", "CmdConnect": "34 52 05 10 9b", "CmdOpenCal": "68 b0 01 01 00 00 00 01 01 10", "ReadDigVCCN": "68 b4 01 01 00 00 00 02 00 02 10", "ReadAnaVCCN": "68 b2 01 01 00 00 00 02 00 02 10", "ReadMod": "68 b6 01 01 00 00 00 03 00 01 00 10", "ReadPWR": "68 b8 01 01 00 00 00 04 00 01 01 00 10", "ReadSlot": "68 b7 01 01 00 00 00 01 00 10", "CmdColseCal": "68 b0 01 01 00 00 00 01 00 10", "CmdEnd": "34 52 05 ee 79", } def hx(name): return bytes.fromhex(CMDS[name].replace(" ", "")) # powershell: open port, run a sequence of (write, wait) steps, capture each response PS = r''' $ErrorActionPreference = "Stop" $p = New-Object System.IO.Ports.SerialPort "{port}",115200,"None",8,"one" $p.ReadTimeout = 60 try {{ $p.Open() }} catch {{ Write-Output "ERR:$($_.Exception.Message)"; exit }} $p.DiscardInBuffer(); $p.DiscardOutBuffer() $steps = New-Object System.Collections.ArrayList {steps} foreach ($s in $steps) {{ $name = $s[0]; $tx = [byte[]]$s[1] $p.DiscardInBuffer() $p.Write($tx,0,$tx.Length) Start-Sleep -Milliseconds {dwell} $buf = New-Object System.Collections.Generic.List[byte] $sw=[Diagnostics.Stopwatch]::StartNew(); $last=0 while ($sw.ElapsedMilliseconds -lt {tmo}) {{ try {{ $buf.Add($p.ReadByte()); $last=$sw.ElapsedMilliseconds }} catch {{}} if ($buf.Count -gt 0 -and ($sw.ElapsedMilliseconds-$last) -gt {idle}) {{ break }} }} $hex = ($buf | ForEach-Object {{ "{{0:x2}}" -f $_ }}) -join "" Write-Output ("RSP:"+$name+":"+$hex) }} $p.Close() Write-Output "DONE" ''' def find_port(): out = subprocess.run(["powershell.exe","-NoProfile","-Command", "[System.IO.Ports.SerialPort]::GetPortNames() -join ','"], capture_output=True, text=True).stdout.strip().replace("\r","") ports=[p for p in out.split(",") if p] if not ports: sys.exit("COM не найден.") return ports[0] def ps_arr(b): return "[byte[]]("+",".join(str(x) for x in b)+")" def run(port, seq, dwell=120, tmo=700, idle=200): steps = "\n".join(f"$null = $steps.Add(@('{n}',{ps_arr(hx(n))}))" for n in seq) script = PS.format(port=port, steps=steps, dwell=dwell, tmo=tmo, idle=idle) r = subprocess.run(["powershell.exe","-NoProfile","-Command",script], capture_output=True, text=True) out=r.stdout.replace("\r","") res=[] for line in out.split("\n"): if line.startswith("ERR:"): sys.exit(line) if line.startswith("RSP:"): _,name,hexs=line.split(":",2) res.append((name, bytes.fromhex(hexs) if hexs else b"")) return res def parse68(rx): if len(rx)<9 or rx[0]!=0x68: return None cmd=rx[1]; ln=(rx[6]<<8)|rx[7]; pl=rx[8:8+ln] return dict(cmd=cmd, status=rx[3], length=ln, payload=pl) def main(): ap=argparse.ArgumentParser() ap.add_argument("--port") ap.add_argument("--connect-only", action="store_true", help="только CmdConnect (проверка отклика)") ap.add_argument("--direct", action="store_true", help="без 34.. Connect: 0x68 напрямую в модуль (режим Обновление DMR)") ap.add_argument("--seq", help="произвольная последовательность имён через запятую") a=ap.parse_args() port=a.port or find_port() print(f"[порт {port} @115200] РИД-ОНЛИ снятие калибровки\n") if a.seq: seq=[s.strip() for s in a.seq.split(",")] elif a.connect_only: seq=["CmdConnect"] elif a.direct: seq=["Ping","CmdOpenCal","ReadDigVCCN","ReadAnaVCCN","ReadMod","ReadPWR", "ReadSlot","CmdColseCal"] else: seq=["CmdConnect","CmdOpenCal","ReadDigVCCN","ReadAnaVCCN","ReadMod","ReadPWR", "ReadSlot","CmdColseCal","CmdEnd"] res=run(port, seq) if not res: print("Нет ответов от рации (powershell не вернул RSP). Порт/режим?") return for name,rx in res: tag=f" {name:12s} TX={CMDS[name]}" if not rx: print(tag+" RX=(нет ответа)") else: p=parse68(rx) extra=f" [cmd=0x{p['cmd']:02X} st=0x{p['status']:02X} len={p['length']} pl={p['payload'].hex(' ')}]" if p else "" print(tag+f" RX={rx.hex(' ')}"+extra) if __name__=="__main__": main()