# Sends an EVision per-key colour frame (same packets as SignalRGB's EVision plugin) # Usage: evision_led.ps1 -Leds "0=ff0000,21=00ff00" [-Fill 000000] [-Count 128] [-Seconds 0] [-SoftwareMode] param( [string]$Leds = '', [string]$Fill = '000000', [int]$Count = 128, [double]$Seconds = 0, [switch]$SoftwareMode, [string]$Match = 'VID_320F&PID_502A&MI_01&COL04' ) Add-Type @" using System; using System.Runtime.InteropServices; public static class EV { [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)] public static extern IntPtr CreateFile(string n, uint a, uint s, IntPtr sa, uint c, uint f, IntPtr t); [DllImport("kernel32.dll", SetLastError=true)] public static extern bool WriteFile(IntPtr h, byte[] b, int n, out int w, IntPtr o); [DllImport("kernel32.dll")] public static extern bool CloseHandle(IntPtr h); } "@ $dev = Get-PnpDevice -PresentOnly -Class HIDClass | Where-Object { $_.InstanceId -match [regex]::Escape($Match) } | Select-Object -First 1 if (-not $dev) { throw "RGB endpoint $Match not found" } $path = '\\?\' + ($dev.InstanceId -replace '\\','#') + '#{4d1e55b2-f16f-11cf-88cb-001111000030}' $h = [EV]::CreateFile($path, [uint32]3221225472, 3, [IntPtr]::Zero, 3, 0, [IntPtr]::Zero) if ($h -eq [IntPtr]-1) { throw "open failed, error $([Runtime.InteropServices.Marshal]::GetLastWin32Error())" } function Send([byte[]]$pkt) { $buf = New-Object byte[] 64; [Array]::Copy($pkt, $buf, [Math]::Min(64, $pkt.Length)) $w = 0 if (-not [EV]::WriteFile($h, $buf, 64, [ref]$w, [IntPtr]::Zero)) { throw "write failed, error $([Runtime.InteropServices.Marshal]::GetLastWin32Error())" } } function Rgb([string]$hex) { $hex = $hex.TrimStart('#'); ,([byte[]]@([Convert]::ToByte($hex.Substring(0,2),16), [Convert]::ToByte($hex.Substring(2,2),16), [Convert]::ToByte($hex.Substring(4,2),16))) } # Build the colour buffer $data = New-Object byte[] ($Count * 3) $f = Rgb $Fill for ($i = 0; $i -lt $Count; $i++) { $data[$i*3] = $f[0]; $data[$i*3+1] = $f[1]; $data[$i*3+2] = $f[2] } foreach ($e in ($Leds -split ',' | Where-Object { $_ })) { $idx, $col = $e.Split('=') if ($idx -match '^(\d+)-(\d+)$') { $range = [int]$Matches[1]..[int]$Matches[2] } else { $range = @([int]$idx) } $c = Rgb $col foreach ($i in $range) { if ($i -lt $Count) { $data[$i*3] = $c[0]; $data[$i*3+1] = $c[1]; $data[$i*3+2] = $c[2] } } } function SendFrame { $per = 56 for ($off = 0; $off -lt $data.Length; $off += $per) { $chunk = New-Object byte[] $per [Array]::Copy($data, $off, $chunk, 0, [Math]::Min($per, $data.Length - $off)) $pkt = [byte[]](@(0x04, 0, 0, 0x12, $per, ($off -band 0xFF), (($off -shr 8) -band 0xFF), 0) + $chunk) # checksum = 16-bit sum of bytes 3..63 (matches the plugin's formula) $sum = 0; for ($k = 3; $k -lt 64; $k++) { $sum += $pkt[$k] } $pkt[1] = $sum -band 0xFF; $pkt[2] = ($sum -shr 8) -band 0xFF Send $pkt Start-Sleep -Milliseconds 1 } } try { if ($SoftwareMode) { Send ([byte[]]@(0x04, 0x8c, 0x00, 0x0b, 0x30, 0x50, 0x01)); Start-Sleep -Milliseconds 20 } $end = (Get-Date).AddSeconds($Seconds); $n = 0 do { SendFrame; $n++; if ($Seconds -gt 0) { Start-Sleep -Milliseconds 100 } } while ((Get-Date) -lt $end) "sent $n frame(s), $([Math]::Ceiling($data.Length / 56)) packets each" } finally { [void][EV]::CloseHandle($h) }