Главное: - Опознан DMR-чип: FM100B = Sicomm SCT3258TD. Датащит Packet Interface (126 стр) в docs/. Открывает калибровку/AMBE/вокодер по документации. Родня: UV-4R, китайские клоны. Реализовано: - Приём Talker Alias (cmd 0x60) — позывной крупно на экране (build_ta.py) - Интерактивный агент — управление FM100B по COM без перешивки (build_agent.py + agent.py) - Полная карта команд FM100B с живой рации (fm100b-commands.md) - Работа с SPI: чтение/запись/восстановление codeplug и калибровки (spi_read/spi_wr/spi_restore) - Конструктор дисплея + редактор прошивки (tools/display-editor) Документация: - docs/SCT3258_packet_interface.txt/.pdf — спека DMR-чипа - docs/fm100b-commands.md — карта команд - docs/dmr-tx-recovery.md — диагноз/восстановление DMR-передачи Инструменты: flash_ps.py (прошивка через WSL/powershell), sniff.py (снифер кадров), hotspot_rxoffset_sweep.py, apply_patch.py, build_* сборщики. Важно: DMR TX-калибровка сбита в экспериментах со SET-командами модулю; восстановление через offset-компенсацию / .updat Radtel / калибровку по прибору (docs/dmr-tx-recovery.md).
91 строка
3.9 KiB
Python
91 строка
3.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Чтение SPI-флеша рации через CPS-протокол (USART6) из WSL по powershell.exe.
|
|
|
|
Рация — в ОБЫЧНОМ режиме (не мост, не прошивка), кабель в COM. CPS-протокол:
|
|
notify : 34 00 00 10 44 -> ACK 06
|
|
read : 52 <off_hi> <off_lo> <cksum> -> 1028 Б (3 hdr + 1024 data + 1 cksum), offset в КБ
|
|
cksum : (sum) & 0xFF, seed 0
|
|
|
|
python3 tools/spi_read.py 0 16 --out cur_cal.bin # блоки 0..15 (первые 16 КБ)
|
|
python3 tools/spi_read.py 0 4096 --out full.bin # весь 4 МБ (долго)
|
|
"""
|
|
import argparse, subprocess, sys, os
|
|
|
|
WIN_TMP_WSL = "/mnt/c/Users/vikto/AppData/Local/Temp"
|
|
WIN_TMP_WIN = r"C:\Users\vikto\AppData\Local\Temp"
|
|
|
|
PS = r'''
|
|
$ErrorActionPreference='Stop'
|
|
$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 }}
|
|
# notify
|
|
$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 }}
|
|
$fs=[System.IO.File]::Open("{outwin}",[System.IO.FileMode]::Create)
|
|
for($off={first}; $off -lt {last}; $off++){{
|
|
$cmd=[byte[]](0x52,(($off -shr 8) -band 0xFF),($off -band 0xFF),0)
|
|
$cmd[3]=ck $cmd 3
|
|
$p.DiscardInBuffer(); $p.Write($cmd,0,4)
|
|
$buf=New-Object byte[] 1028; $got=0; $sw2=[Diagnostics.Stopwatch]::StartNew()
|
|
while($got -lt 1028 -and $sw2.ElapsedMilliseconds -lt 2500){{
|
|
try {{ $b=$p.ReadByte(); $buf[$got]=[byte]$b; $got++ }} catch {{}}
|
|
}}
|
|
if($got -lt 1028){{ Write-Output ("ERR:read@"+$off); $fs.Close(); $p.Close(); exit 1 }}
|
|
if($buf[0] -eq 0xFF){{ Write-Output ("ERR:noblk@"+$off); $fs.Close(); $p.Close(); exit 1 }}
|
|
$fs.Write($buf,3,1024)
|
|
if(($off % 64) -eq 0){{ Write-Output ("PROG:"+$off) }}
|
|
}}
|
|
$fs.Close()
|
|
$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("first", type=lambda x:int(x,0))
|
|
ap.add_argument("last", type=lambda x:int(x,0))
|
|
ap.add_argument("--out", default="spi_read.bin")
|
|
ap.add_argument("--port")
|
|
a=ap.parse_args()
|
|
port=a.port or find_port()
|
|
name="rt4d_spiread.bin"
|
|
outwin=os.path.join(WIN_TMP_WIN,name)
|
|
script=PS.format(port=port,outwin=outwin.replace("\\","\\\\"),first=a.first,last=a.last)
|
|
print(f"[чтение SPI блоков {a.first}..{a.last-1} ({(a.last-a.first)} КБ), порт {port}]")
|
|
proc=subprocess.Popen(["powershell.exe","-NoProfile","-Command",script],
|
|
stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True,encoding="utf-8",errors="replace")
|
|
for line in proc.stdout:
|
|
line=line.strip()
|
|
if line.startswith("PROG:"): print(f"\r блок {line[5:]}",end="",flush=True)
|
|
elif line.startswith("ERR:"): print("\n"+line);
|
|
elif line=="OK": print("\n чтение завершено")
|
|
proc.wait()
|
|
# забрать файл из Windows temp
|
|
src=os.path.join(WIN_TMP_WSL,name)
|
|
if os.path.exists(src):
|
|
import shutil; shutil.copy(src,a.out)
|
|
print(f"сохранено: {a.out} ({os.path.getsize(a.out)} Б)")
|
|
else:
|
|
print("файл не создан — чтение не удалось")
|
|
|
|
|
|
if __name__=="__main__":
|
|
main()
|