enet-validator/scripts/session/analyze_cold.ps1

241 lines
12 KiB
PowerShell
Raw Permalink Normal View History

[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
$f = "$env:TEMP\opencode\cold2.tsv"
$lines = [IO.File]::ReadAllLines($f)
function H2B($hex) {
if (-not $hex) { return $null }
$n = $hex.Length / 2
$b = New-Object byte[] $n
for ($i = 0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring(2*$i, 2), 16) }
return $b
}
function G16($b, $o) { if ($b.Length -lt $o+2) { return -1 }; return $b[$o] -bor ($b[$o+1] -shl 8) }
function G32($b, $o) { if ($b.Length -lt $o+4) { return -1 }; return $b[$o] -bor ($b[$o+1] -shl 8) -bor ($b[$o+2] -shl 16) -bor ($b[$o+3] -shl 24) }
function UidStr($b, $o) { if ($b.Length -lt $o+6) { return "?" }; return ($b[$o..($o+5)] | ForEach-Object { $_.ToString("x2") }) -join "" }
# ============ 1) 冷启动时间线: 前 30 秒关键帧 ============
Write-Host "`n=========== [1] 冷启动前 30s 关键帧时间线 ==========="
$shown = 0
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
$t = [double]$p[0]; if ($t -gt 30) { break }
$b = H2B $p[4]; if ($b.Length -lt 0x1A) { continue }
if ($b[0] -ne 0x45 -or $b[1] -ne 0x4E) { continue } # "EN"
$type = G32 $b 4; $dir = "$($p[1])->$($p[2])"; $len = $b.Length
$desc = $null
if ($type -eq 1) { $desc = "发现请求" }
elseif ($type -eq 2) { $sub = G16 $b 0x18
if ($sub -eq 1) { $desc = "宣告sub=01" }
elseif ($sub -eq 2) { $desc = "注册sub=02(89B)" }
elseif ($sub -eq 3) { $desc = "心跳sub=03" }
elseif ($sub -eq 5) { $desc = "AECH质询sub=05" }
elseif ($sub -eq 6) { $desc = "AERE应答sub=06" }
else { $desc = "type2 sub=$sub" } }
elseif ($type -eq 4 -and $len -eq 259) { $desc = "259发现应答" }
elseif ($type -eq 4 -and $len -eq 84) { $desc = "84B状态" }
elseif ($type -eq 4 -and $len -gt 1000) { $desc = "DMX/RDM长帧" }
elseif ($type -eq 4) { $desc = "type4短帧" }
if ($desc -and $desc -ne "84B状态" -and $desc -ne "心跳sub=03" -and $shown -lt 60) {
Write-Host (" t={0,8:F3} {1,-26} len={2,-5} {3}" -f $t, $dir, $len, $desc); $shown++
}
}
# ============ 2) RDM 查询直方图 (主机->.1, type=4) ============
Write-Host "`n=========== [2] 主机 RDM 查询 cmd 直方图 (type=4, 0x3D) ==========="
$cmdHist = @{}; $pidHist = @{}; $queries = New-Object System.Collections.ArrayList
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne "192.168.123.113" -or $p[2] -ne "192.168.123.1") { continue }
$b = H2B $p[4]; if ($b.Length -lt 0x52) { continue }
if ((G32 $b 4) -ne 4) { continue }
if ($b.Length -gt 2000) { continue } # 纯DMX 1126B? 实际 1084=DMX
$t = [double]$p[0]
$len = $b.Length
if ($len -eq 1084 -or $len -eq 1126) { # DMX 帧或内嵌 RDM 副本
if ($b[0x3C] -ne 0x01) { continue }
}
$cmd = $b[0x3D]
if ($cmd -ne 0x24 -and $cmd -ne 0x18 -and $cmd -ne 0x19 -and $cmd -ne 0x1a) { continue }
$flag = $b[0x30]
$key = ("cmd=0x{0:x2} flag={1:x2}" -f $cmd, $flag)
if (-not $cmdHist.ContainsKey($key)) { $cmdHist[$key] = 0 }; $cmdHist[$key]++
$uid = UidStr $b 0x3E
$pid2 = ""
if ($cmd -eq 0x18 -and $b.Length -gt 0x51) { $pid2 = "{0:x2}{1:x2}" -f $b[0x50], $b[0x51]
$pk = "$pid2 flag=$("{0:x2}" -f $flag)"; if (-not $pidHist.ContainsKey($pk)) { $pidHist[$pk] = 0 }; $pidHist[$pk]++ }
[void]$queries.Add(@{ t=$t; cmd=$cmd; flag=$flag; uid=$uid; pid=$pid2; seq=((G16 $b 0x22)); seq16lo=(G16 $b 0x22) })
}
Write-Host "--- cmd 分布 ---"
$cmdHist.GetEnumerator() | Sort-Object Value -Descending | ForEach-Object { Write-Host (" {0} : {1}" -f $_.Key, $_.Value) }
Write-Host "--- cmd=18 的 PID 分布 ---"
$pidHist.GetEnumerator() | Sort-Object Value -Descending | ForEach-Object { Write-Host (" PID={0} : {1}" -f $_.Key, $_.Value) }
# ============ 3) .1 应答帧: 长度/内容直方图 + checksum 验证 ============
Write-Host "`n=========== [3] .1 应答帧分析 (.1->113, type=4 短帧) ==========="
$answers = New-Object System.Collections.ArrayList
$ansLenHist = @{}
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne "192.168.123.1" -or $p[2] -ne "192.168.123.113") { continue }
$b = H2B $p[4]; if ($b.Length -lt 0x2E) { continue }
if ((G32 $b 4) -ne 4) { continue }
if ($b.Length -gt 200) { continue } # 只看 RDM 短应答 (59/69/77/87/101/111/119B)
if ($b[0x2A] -ne 0xCC -and $b[0x2A] -ne 0xAA) { continue }
$t = [double]$p[0]
$len = $b.Length
if (-not $ansLenHist.ContainsKey($len)) { $ansLenHist[$len] = 0 }; $ansLenHist[$len]++
[void]$answers.Add(@{ t=$t; b=$b; len=$len })
}
Write-Host "--- 应答长度分布 ---"
$ansLenHist.GetEnumerator() | Sort-Object Name | ForEach-Object { Write-Host (" {0}B : {1}" -f $_.Key, $_.Value) }
# checksum 验证 (标准 RDM 帧: 0x2A=0xCC, msgLen@0x2C, checksum=前 msgLen 字节和, BE)
Write-Host "--- 标准 RDM 应答 checksum 验证 (抽样全部) ---"
$okC = 0; $badC = 0; $badSamples = New-Object System.Collections.ArrayList
foreach ($a in $answers) {
$b = $a.b
if ($b[0x2A] -ne 0xCC) { continue }
$msgLen = $b[0x2C]
if ($b.Length -lt 0x2A + $msgLen + 2) { continue }
$sum = 0; for ($i = 0x2A; $i -lt 0x2A + $msgLen; $i++) { $sum += $b[$i] }
$ck = ($b[0x2A + $msgLen] -shl 8) -bor $b[0x2A + $msgLen + 1]
if (($sum -band 0xFFFF) -eq $ck) { $okC++ } else { $badC++; if ($badSamples.Count -lt 3) { [void]$badSamples.Add(@{t=$a.t; len=$a.len; sum=($sum -band 0xFFFF); ck=$ck}) } }
}
Write-Host (" checksum OK={0} BAD={1}" -f $okC, $badC)
foreach ($s in $badSamples) { Write-Host (" BAD样本: t={0:F3} len={1} sum=0x{2:x4} ck=0x{3:x4}" -f $s.t, $s.len, $s.sum, $s.ck) }
# 脉冲帧 (0x2A=0xAA) 内容: 解码 UID
Write-Host "--- DISC 脉冲帧 (0x2A=0xAA) UID 解码 ---"
$pulseUids = @{}
foreach ($a in $answers) {
$b = $a.b
if ($b[0x2A] -ne 0xAA) { continue }
# 定位 0xAA 分隔符后的 16 字节编码 UID
$uid = ""
for ($i = 0; $i -lt 6; $i++) {
$b1 = $b[0x2B + 2*$i]; $b2 = $b[0x2C + 2*$i]
$byte = ($b1 -band 0x55) -bor ($b2 -band 0xAA)
$uid += $byte.ToString("x2")
}
if (-not $pulseUids.ContainsKey($uid)) { $pulseUids[$uid] = 0 }; $pulseUids[$uid]++
}
$pulseUids.GetEnumerator() | Sort-Object Value -Descending | ForEach-Object { Write-Host (" UID={0} : {1} 脉冲" -f $_.Key, $_.Value) }
# ============ 4) 查询-应答配对: cmd=18 按 PID, 时延统计 ============
Write-Host "`n=========== [4] cmd=18 查询→应答配对 (seq 0x22-0x23 ↔ 0x20-0x21) ==========="
$ansBySeq = @{}
foreach ($a in $answers) {
$b = $a.b
if ($b.Length -lt 0x22) { continue }
$s = G16 $b 0x20
if (-not $ansBySeq.ContainsKey($s)) { $ansBySeq[$s] = New-Object System.Collections.ArrayList }
[void]$ansBySeq[$s].Add($a)
}
$pairStats = @{}; $latByPid = @{}
foreach ($q in $queries) {
if ($q.cmd -ne 0x18 -or $q.flag -ne 0) { continue }
$pk = if ($q.pid) { $q.pid } else { "????" }
if (-not $pairStats.ContainsKey($pk)) { $pairStats[$pk] = @{ n=0; ans=0; lens=@{} } }
$pairStats[$pk].n++
# 找该查询之后 200ms 内 seq 回显的应答
$best = $null
foreach ($a in $answers) {
if ($a.t -lt $q.t -or $a.t -gt $q.t + 0.2) { continue }
$s = G16 $a.b 0x20
if ($s -eq $q.seq) { $best = $a; break }
}
if ($best) {
$pairStats[$pk].ans++
$al = $best.len
if (-not $pairStats[$pk].lens.ContainsKey($al)) { $pairStats[$pk].lens[$al] = 0 }
$pairStats[$pk].lens[$al]++
if (-not $latByPid.ContainsKey($pk)) { $latByPid[$pk] = New-Object System.Collections.ArrayList }
[void]$latByPid[$pk].Add([int](($best.t - $q.t) * 1000))
}
}
foreach ($k in ($pairStats.Keys | Sort-Object)) {
$st = $pairStats[$k]
$lensStr = ($st.lens.GetEnumerator() | ForEach-Object { "$($_.Key)B×$($_.Value)" }) -join " "
$latStr = ""
if ($latByPid.ContainsKey($k) -and $latByPid[$k].Count -gt 0) {
$la = $latByPid[$k] | Sort-Object
$latStr = (" 时延ms: min={0} 中位={1} max={2}" -f $la[0], $la[[int]($la.Count/2)], $la[-1])
}
Write-Host (" PID={0} 查询={1} 配对应答={2} 长度[{3}]{4}" -f $k, $st.n, $st.ans, $lensStr, $latStr)
}
# ============ 5) 0002/0003 应答逐字节样本 ============
Write-Host "`n=========== [5] PID=0002 / 0003 应答样本 (前2个) ==========="
foreach ($wantPid in @("0002", "0003")) {
$cnt = 0
foreach ($q in $queries) {
if ($q.cmd -ne 0x18 -or $q.flag -ne 0 -or $q.pid -ne $wantPid) { continue }
foreach ($a in $answers) {
if ($a.t -lt $q.t -or $a.t -gt $q.t + 0.2) { continue }
if ((G16 $a.b 0x20) -eq $q.seq) {
Write-Host (" PID={0} UID={1} t={2:F3} 查询len -> 应答{3}B: {4}" -f $wantPid, $q.uid, $q.t, $a.len, (($a.b | ForEach-Object { $_.ToString("x2") }) -join ""))
$cnt++; break
}
}
if ($cnt -ge 2) { break }
}
if ($cnt -eq 0) { Write-Host " PID=$wantPid 无配对样本" }
}
# ============ 6) 发现收敛: 全范围查询(24) 与脉冲随时间 ============
Write-Host "`n=========== [6] 发现收敛时间线 (10s 桶: 全范围查询数 / 脉冲应答数 / MUTE数) ==========="
$buckets = @{}
foreach ($q in $queries) {
if ($q.flag -ne 0) { continue }
$bk = [int]($q.t / 10) * 10
if (-not $buckets.ContainsKey($bk)) { $buckets[$bk] = @{ disc=0; get=0; pulse=0; mute=0; unmute=0 } }
if ($q.cmd -eq 0x24) { $buckets[$bk].disc++ }
if ($q.cmd -eq 0x18 -and $q.pid -eq "0003") { $buckets[$bk].mute++ }
if ($q.cmd -eq 0x18 -and $q.pid -eq "0002") { $buckets[$bk].unmute++ }
if ($q.cmd -eq 0x18) { $buckets[$bk].get++ }
}
# 脉冲数按桶
foreach ($a in $answers) {
if ($a.b[0x2A] -ne 0xAA) { continue }
$bk = [int]($a.t / 10) * 10
if (-not $buckets.ContainsKey($bk)) { $buckets[$bk] = @{ disc=0; get=0; pulse=0; mute=0; unmute=0 } }
$buckets[$bk].pulse++
}
foreach ($bk in ($buckets.Keys | Sort-Object)) {
$v = $buckets[$bk]
Write-Host (" t={0,4}-{1,-4}s 全范围查询={2,-5} GET查询={3,-5} 脉冲={4,-5} MUTE={5,-4} UNMUTE={6}" -f $bk, ($bk+10), $v.disc, $v.get, $v.pulse, $v.mute, $v.unmute)
}
# ============ 7) 84B 状态帧节奏 ============
Write-Host "`n=========== [7] 84B 状态帧节奏 (.1->113) ==========="
$statTimes = New-Object System.Collections.ArrayList
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne "192.168.123.1" -or $p[2] -ne "192.168.123.113") { continue }
$b = H2B $p[4]
if ($b.Length -eq 84 -and (G32 $b 4) -eq 4) { [void]$statTimes.Add([double]$p[0]) }
}
Write-Host (" 84B 总数={0}" -f $statTimes.Count)
if ($statTimes.Count -gt 2) {
$gaps = New-Object System.Collections.ArrayList
for ($i = 1; $i -lt $statTimes.Count; $i++) { [void]$gaps.Add([int](($statTimes[$i] - $statTimes[$i-1]) * 1000)) }
$gs = $gaps | Sort-Object
Write-Host (" 帧间隔ms: min={0} 中位={1} max={2}" -f $gs[0], $gs[[int]($gs.Count/2)], $gs[-1])
# 突发检测: 间隔>1000ms 为新突发
$bursts = 1; foreach ($g in $gaps) { if ($g -gt 1000) { $bursts++ } }
Write-Host (" 突发数={0} (平均每突发 {1:F1} 帧)" -f $bursts, ($statTimes.Count / $bursts))
}
# ============ 8) DEVICE_INFO(0060) 读取对象 UID ============
Write-Host "`n=========== [8] DEVICE_INFO(0060)/参数读取的 UID 分布 ==========="
$diUids = @{}
foreach ($q in $queries) {
if ($q.cmd -ne 0x18 -or $q.flag -ne 0) { continue }
if ($q.pid -eq "0060" -or $q.pid -eq "0080" -or $q.pid -eq "00f0" -or $q.pid -eq "0003") {
$k = "$($q.pid)->$($q.uid)"; if (-not $diUids.ContainsKey($k)) { $diUids[$k] = 0 }; $diUids[$k]++
}
}
$diUids.GetEnumerator() | Sort-Object Name | ForEach-Object { Write-Host (" {0} : {1}" -f $_.Key, $_.Value) }
Write-Host "`n分析完成。"