[Console]::OutputEncoding = [Text.Encoding]::UTF8 $ErrorActionPreference = "Stop" 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 UidStr($b, $o) { if ($b.Length -lt $o+6) { return "?" }; return ($b[$o..($o+5)] | ForEach-Object { $_.ToString("x2") }) -join "" } function Analyze2($name, $tsv, $devIp, $hostIp) { $lines = [IO.File]::ReadAllLines($tsv) Write-Host "`n############ $name ($devIp) ############" # 真实 RDM 查询: 0x20-0x21 == 0200 (事件B帧), flag 0x30==0 $rdmCmd = @{}; $rdmPid = @{}; $rdmPidUid = @{} $falsePos = 0 $bFrames = New-Object System.Collections.ArrayList # 所有事件B帧 (时间, 0x22序号, 0x3C, 0x3D) foreach ($l in $lines) { $p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue } if ($p[1] -ne $hostIp -or $p[2] -ne $devIp) { continue } $b = H2B $p[4]; if ($b.Length -lt 0x52 -or $b.Length -gt 2000) { continue } if ([int]$b[4] -ne 4) { continue } $t = [double]$p[0] $isB = ($b[0x20] -eq 2 -and $b[0x21] -eq 0) $isA = ($b[0x20] -eq 1 -and $b[0x21] -eq 1) if ($isB -and $b.Length -gt 0x44) { [void]$bFrames.Add(@{ t=$t; seq=([int]$b[0x22]+[int]$b[0x23]*256); c3c=$b[0x3C]; c3d=$b[0x3D]; flag=$b[0x30] }) } # RDM 判定: 必须 0x3C==01 且 cmd 匹配 if ($b.Length -gt 0x44 -and $b[0x3C] -eq 1 -and ($b[0x3D] -eq 0x24 -or $b[0x3D] -eq 0x18 -or $b[0x3D] -eq 0x19 -or $b[0x3D] -eq 0x1a)) { if ($b[0x30] -ne 0) { continue } if ($isA) { $falsePos++; continue } # DMX-A 帧通道数据巧合 = 假阳性 $cmd = $b[0x3D] $ck = "cmd={0:x2}" -f $cmd if (-not $rdmCmd.ContainsKey($ck)) { $rdmCmd[$ck] = 0 }; $rdmCmd[$ck]++ if ($cmd -ne 0x24 -and $b.Length -gt 0x51) { $pidKey = "{0:x2}{1:x2}" -f $b[0x50], $b[0x51] $uid = UidStr $b 0x3E if (-not $rdmPid.ContainsKey($pidKey)) { $rdmPid[$pidKey] = 0 }; $rdmPid[$pidKey]++ $k2 = "$pidKey->$uid"; if (-not $rdmPidUid.ContainsKey($k2)) { $rdmPidUid[$k2] = 0 }; $rdmPidUid[$k2]++ } } } Write-Host ("--- 真实 RDM 查询 (排除 DMX-A 假阳性 {0} 条) ---" -f $falsePos) $rdmCmd.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | ForEach-Object { Write-Host (" {0} : {1}" -f $_.Key, $_.Value) } Write-Host "--- PID 分布 ---" $rdmPid.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | Select-Object -First 12 | ForEach-Object { Write-Host (" PID={0} : {1}" -f $_.Key, $_.Value) } Write-Host "--- PID->UID 细分 (0002/0003/0060/00f0/0201) ---" $rdmPidUid.GetEnumerator() | Where-Object { $_.Name -match "^(0002|0003|0060|00f0|0201)" } | Sort-Object Name | ForEach-Object { Write-Host (" {0} : {1}" -f $_.Name, $_.Value) } # 42B ACK 相关性: 每个 42B 帧之前 200ms 内最近的 B 帧特征 $acks = 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 $devIp -or $p[2] -ne $hostIp) { continue } $b = H2B $p[4]; if ($b.Length -ne 42 -or [int]$b[4] -ne 4) { continue } [void]$acks.Add(@{ t=([double]$p[0]); seq=([int]$b[0x20]+[int]$b[0x21]*256) }) } $ackByB = @{} $ackNoB = 0 foreach ($a in $acks) { # 找 seq == ack.seq+1 的 B 帧 (§4.3: ACK seq = 事件 seq - 1) $found = $null foreach ($bf in $bFrames) { if ($bf.seq -eq $a.seq + 1) { $found = $bf; break } } if ($found) { $k = "0x3C={0:x2} 0x3D={1:x2} flag={2:x2}" -f $found.c3c, $found.c3d, $found.flag if (-not $ackByB.ContainsKey($k)) { $ackByB[$k] = 0 }; $ackByB[$k]++ } else { $ackNoB++ } } Write-Host ("--- 42B ACK ({0}个) 对应的 B 帧特征 ---" -f $acks.Count) $ackByB.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | Select-Object -First 8 | ForEach-Object { Write-Host (" {0} : {1}" -f $_.Key, $_.Value) } Write-Host (" (无配对 B 帧: {0})" -f $ackNoB) } Analyze2 "真机 .1" "$env:TEMP\opencode\ss123.tsv" "192.168.123.1" "192.168.123.113" Analyze2 "虚拟 .7 新代码" "$env:TEMP\opencode\fix2.tsv" "192.168.58.7" "192.168.58.107" Write-Host "`n分析完成。"