[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 Analyze($name, $tsv, $devIp, $hostIp) { $lines = [IO.File]::ReadAllLines($tsv) $t0 = 1e9; $t1 = 0 foreach ($l in $lines) { $p = $l -split "`t"; if ($p.Count -lt 1 -or -not $p[0]) { continue }; $t = [double]$p[0]; if ($t -lt $t0) { $t0 = $t }; if ($t -gt $t1) { $t1 = $t } } $dur = $t1 - $t0 $h2d = @{} # host->dev: 帧型 $d2h = @{} # dev->host: 帧型 $rdmCmd = @{} # host->dev RDM cmd 直方图 (flag=00) $rdmPid = @{} $queries = New-Object System.Collections.ArrayList # RDM 查询 (flag=00, cmd 18/19/1a/24) $answers = New-Object System.Collections.ArrayList # dev->host RDM 短帧 $dmxA = 0; $evtB = 0; $ack42 = 0 $devUnsolicited = New-Object System.Collections.ArrayList # dev->host 42B 帧时间 foreach ($l in $lines) { $p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue } $b = H2B $p[4]; if ($b.Length -lt 0x1A) { continue } $t = [double]$p[0] $typ = [int]$b[4] $isH2D = ($p[1] -eq $hostIp -and $p[2] -eq $devIp) $isD2H = ($p[1] -eq $devIp -and $p[2] -eq $hostIp) if (-not $isH2D -and -not $isD2H) { continue } $len = $b.Length if ($isH2D) { $k = "t$typ|$len" if (-not $h2d.ContainsKey($k)) { $h2d[$k] = 0 }; $h2d[$k]++ if ($typ -eq 4 -and $len -gt 1000) { if ($b[0x20] -eq 1 -and $b[0x21] -eq 1) { $dmxA++ } else { $evtB++ } } if ($typ -eq 4 -and $len -lt 2000 -and $len -gt 0x44 -and $b[0x30] -eq 0) { $cmd = $b[0x3D] if ($cmd -eq 0x24 -or $cmd -eq 0x18 -or $cmd -eq 0x19 -or $cmd -eq 0x1a) { $ck = "cmd={0:x2}" -f $cmd if (-not $rdmCmd.ContainsKey($ck)) { $rdmCmd[$ck] = 0 }; $rdmCmd[$ck]++ $pidKey = "" if ($cmd -ne 0x24 -and $b.Length -gt 0x51) { $pidKey = "{0:x2}{1:x2}" -f $b[0x50], $b[0x51] if (-not $rdmPid.ContainsKey($pidKey)) { $rdmPid[$pidKey] = 0 }; $rdmPid[$pidKey]++ } [void]$queries.Add(@{ t=$t; cmd=$cmd; pid=$pidKey; uid=(UidStr $b 0x3E); seq=([int]$b[0x22] + [int]$b[0x23]*256); tn=$b[0x4A]; len=$len }) } } } else { $k = "t$typ|$len" if (-not $d2h.ContainsKey($k)) { $d2h[$k] = 0 }; $d2h[$k]++ if ($typ -eq 4 -and $len -eq 42) { $ack42++; [void]$devUnsolicited.Add($t) } if ($typ -eq 4 -and $len -lt 200 -and $len -gt 0x2C -and ($b[0x2A] -eq 0xCC -or $b[0x2A] -eq 0xAA)) { [void]$answers.Add(@{ t=$t; b=$b; len=$len; seq=([int]$b[0x20] + [int]$b[0x21]*256); pulse=($b[0x2A] -eq 0xAA) }) } } } Write-Host "`n############ $name ($devIp) 时长 $([int]$dur)s ############" Write-Host "--- host→dev 帧型 (Top12) ---" $h2d.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | Select-Object -First 12 | ForEach-Object { Write-Host (" {0} : {1} ({2:F1}/s)" -f $_.Key, $_.Value, ($_.Value / $dur)) } Write-Host "--- dev→host 帧型 (Top12) ---" $d2h.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | Select-Object -First 12 | ForEach-Object { Write-Host (" {0} : {1} ({2:F1}/s)" -f $_.Key, $_.Value, ($_.Value / $dur)) } Write-Host ("--- DMX-A={0} ({1:F1}/s) 事件B={2} ({3:F1}/s) 42B回帧={4} ({5:F1}/s) ACK/事件B比={6:F2}" -f $dmxA, ($dmxA/$dur), $evtB, ($evtB/$dur), $ack42, ($ack42/$dur), $(if ($evtB -gt 0) { $ack42/$evtB } else { 0 })) Write-Host "--- RDM 查询 cmd (flag=00) ---" $rdmCmd.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | ForEach-Object { Write-Host (" {0} : {1}" -f $_.Key, $_.Value) } Write-Host "--- RDM 查询 PID (cmd!=24) ---" $rdmPid.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | Select-Object -First 15 | ForEach-Object { Write-Host (" PID={0} : {1}" -f $_.Key, $_.Value) } # 应答时延: cmd=18/19/1a 查询 → 200ms 内 seq 回显应答 $lat = New-Object System.Collections.ArrayList $paired = 0; $unpaired = 0 foreach ($q in $queries) { if ($q.cmd -eq 0x24) { continue } $best = $null foreach ($a in $answers) { if ($a.pulse) { continue } if ($a.t -lt $q.t -or $a.t -gt $q.t + 0.2) { continue } if ($a.seq -eq $q.seq) { $best = $a; break } } if ($best) { $paired++; [void]$lat.Add([int](($best.t - $q.t) * 1000)) } else { $unpaired++ } } if ($lat.Count -gt 0) { $ls = $lat | Sort-Object Write-Host ("--- 参数应答时延: 配对={0} 未配对={1} min={2}ms 中位={3}ms max={4}ms" -f $paired, $unpaired, $ls[0], $ls[[int]($ls.Count/2)], $ls[-1]) } else { Write-Host ("--- 参数应答时延: 配对={0} 未配对={1} (无样本)" -f $paired, $unpaired) } # 脉冲时延 $plat = New-Object System.Collections.ArrayList $discQs = @($queries | Where-Object { $_.cmd -eq 0x24 }) $pulses = @($answers | Where-Object { $_.pulse }) foreach ($pu in $pulses) { $best = $null for ($i = $discQs.Count - 1; $i -ge 0; $i--) { if ($discQs[$i].t -le $pu.t) { $best = $discQs[$i]; break } } if ($best -and ($pu.t - $best.t) -lt 0.5) { [void]$plat.Add([int](($pu.t - $best.t) * 1000)) } } if ($plat.Count -gt 0) { $ps = $plat | Sort-Object Write-Host ("--- 脉冲: {0} 个 (应答率 {1:F0}% of cmd24) 时延 min={2}ms 中位={3}ms max={4}ms" -f $pulses.Count, $(if ($discQs.Count -gt 0) { 100.0*$pulses.Count/$discQs.Count } else { 0 }), $ps[0], $ps[[int]($ps.Count/2)], $ps[-1]) } return @{ queries=$queries; answers=$answers } } $r1 = Analyze "真机 .1" "$env:TEMP\opencode\ss123.tsv" "192.168.123.1" "192.168.123.113" $r7 = Analyze "虚拟 .7" "$env:TEMP\opencode\ss58.tsv" "192.168.58.7" "192.168.58.107" # ============ cmd=19/1a 深挖: 布局解析 (用 .1 的样本) ============ Write-Host "`n############ cmd=19/1a 布局深挖 (.1 样本) ############" $cnt19 = 0; $cnt1a = 0 foreach ($q in $r1.queries) { if ($q.cmd -eq 0x19 -and $cnt19 -lt 2) { Write-Host (" cmd=19 t={0:F3} UID={1} PID={2} TN={3:x2} len={4}" -f $q.t, $q.uid, $q.pid, $q.tn, $q.len) $cnt19++ } if ($q.cmd -eq 0x1a -and $cnt1a -lt 2) { Write-Host (" cmd=1a t={0:F3} UID={1} PID={2} TN={3:x2} len={4}" -f $q.t, $q.uid, $q.pid, $q.tn, $q.len) $cnt1a++ } } # cmd=19/1a 的 PID 分布 $p19 = @{}; $p1a = @{} foreach ($q in $r1.queries) { if ($q.cmd -eq 0x19) { if (-not $p19.ContainsKey($q.pid)) { $p19[$q.pid] = 0 }; $p19[$q.pid]++ } if ($q.cmd -eq 0x1a) { if (-not $p1a.ContainsKey($q.pid)) { $p1a[$q.pid] = 0 }; $p1a[$q.pid]++ } } Write-Host "--- cmd=19 PID 分布 (.1) ---" $p19.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | Select-Object -First 10 | ForEach-Object { Write-Host (" PID={0} : {1}" -f $_.Key, $_.Value) } Write-Host "--- cmd=1a PID 分布 (.1) ---" $p1a.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | Select-Object -First 10 | ForEach-Object { Write-Host (" PID={0} : {1}" -f $_.Key, $_.Value) } # cmd=19 查询的完整 hex 样本 (从原始 tsv 找一条) Write-Host "--- cmd=19 查询完整 hex (1 条) ---" $lines = [IO.File]::ReadAllLines("$env:TEMP\opencode\ss123.tsv") $done = 0 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 0x60 -or $b.Length -gt 2000) { continue } if ($b[0x30] -ne 0 -or $b[0x3D] -ne 0x19) { continue } Write-Host (" len={0} hex[0x20..0x60]={1}" -f $b.Length, (($b[0x20..([Math]::Min($b.Length-1, 0x60))] | ForEach-Object { $_.ToString("x2") }) -join "")) # 找其应答 $seq = [int]$b[0x22] + [int]$b[0x23]*256 foreach ($a in $r1.answers) { if (-not $a.pulse -and $a.seq -eq $seq -and $a.t -ge [double]$p[0] -and $a.t -le [double]$p[0] + 0.2) { Write-Host (" 应答 len={0}: {1}" -f $a.len, (($a.b | ForEach-Object { $_.ToString("x2") }) -join "")) break } } $done++; if ($done -ge 2) { break } } Write-Host "`n分析完成。"