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