enet-validator/scripts/session/analyze_cold.ps1

241 lines
12 KiB
PowerShell
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[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分析完成。"