using System; using System.Collections.Generic; using System.Diagnostics; using System.IO.Ports; using System.Runtime.InteropServices; using System.Text; using System.Threading; using LibreHardwareMonitor.Interop.PowerMonitor; namespace LibreHardwareMonitor.Hardware.Gpu.PowerMonitor; /// /// Thermal Grizzly WireView Pro II power monitor. /// public sealed class WireViewPro2 : Hardware, IPowerMonitor { public const string WelcomeMessage = "Thermal Grizzly WireView Pro II"; private const byte VendorID = 0xEF; private const byte ProductID = 0x05; private readonly int _baudRate; private readonly string _portName; private readonly List _sensors = []; private SharedSerialPort _serialPort; public WireViewPro2(string portName, ISettings settings, int baud = 115200) : base("WireView Pro II", new Identifier("gpu-powermonitor", portName), settings) { _portName = portName; _baudRate = baud; Connect(); if (IsConnected) { CreateSensors(); } } public override HardwareType HardwareType => HardwareType.GpuPowerMonitor; public bool IsConnected { get; private set; } public string UniqueID { get; private set; } public VendorDataStruct? VendorData { get; private set; } public int ConfigVersion => VendorData?.FwVersion > 2 ? 1 : 0; public static WireViewPro2 TryFindDevice(ISettings settings) { if (!Software.OperatingSystem.IsWindows8OrGreater) { return null; //No Linux implementation yet } List matches = Stm32PortFinder.FindMatchingComPorts(0x0483, 0x5740); WireViewPro2 wireViewPro2 = null; foreach (string port in matches) { try { wireViewPro2 = new WireViewPro2(port, settings); if (wireViewPro2.IsConnected) { break; } wireViewPro2.Close(); wireViewPro2 = null; } catch { wireViewPro2?.Close(); wireViewPro2 = null; } } return wireViewPro2; } public override string GetReport() { var sb = new StringBuilder(); sb.AppendLine(Name); foreach (WireViewPro2Sensor sensor in _sensors) { sb.AppendLine($" Sensor: {sensor.Name} = {sensor.Value}"); } return sb.ToString(); } public override void Close() { Disconnect(); base.Close(); } public override void Update() { SensorStruct? sensorValues = ReadSensorValues(); if (sensorValues.HasValue) { DeviceData deviceData = MapSensorStructure(sensorValues.Value); _sensors.ForEach(wvps => wvps.Update(deviceData)); } } public DeviceConfigStructV2? ReadConfig() { if (!IsConnected) { return null; } int size = 0; try { switch (ConfigVersion) { case 0: size = Marshal.SizeOf(); break; case 1: size = Marshal.SizeOf(); break; default: return null; } var buf = SendCmd(UsbCmd.CMD_READ_CONFIG, size); if (buf == null) { return null; } switch (ConfigVersion) { case 0: var s = BytesToStructure(buf); return StructureConversion.ConvertConfigV1ToV2(s); case 1: return BytesToStructure(buf); default: return null; } } finally { _serialPort.Close(); } } public void WriteConfig(DeviceConfigStructV2 config) { if (!IsConnected) { return; } byte[] payload = []; switch (ConfigVersion) { case 0: var s = StructureConversion.ConvertConfigV2ToV1(config); payload = StructureToBytes(s); break; case 1: payload = StructureToBytes(config); break; default: return; } byte[] frame = new byte[64]; frame[0] = (byte)UsbCmd.CMD_WRITE_CONFIG; try { _serialPort.Open(); _serialPort.DiscardInBuffer(); const int maxPayloadPerFrame = 62; for (byte offset = 0; offset < payload.Length; offset += maxPayloadPerFrame) { int bytesToWrite = Math.Min(maxPayloadPerFrame, payload.Length - offset); frame[1] = offset; Buffer.BlockCopy(payload, offset, frame, 2, bytesToWrite); _serialPort.Write(frame, 0, bytesToWrite + 2); } } finally { _serialPort.Close(); } } public void NonVolatileMemoryCommand(NVM_CMD cmd) { if (!IsConnected) { return; } SendData( [ (byte)UsbCmd.CMD_NVM_CONFIG, 0x55, //Magic 0xAA, //Magic 0x55, //Magic 0xAA, //Magic (byte)cmd ]); } public void ScreenCmd(SCREEN_CMD cmd) { if (!IsConnected) { return; } SendData([(byte)UsbCmd.CMD_SCREEN_CHANGE, (byte)cmd]); } public void ClearFaults(int faultStatusMask = 0xFFFF, int faultLogMask = 0xFFFF) { if (!IsConnected) { return; } SendData( [ (byte)UsbCmd.CMD_CLEAR_FAULTS, (byte)(faultStatusMask & 0xFF), (byte)((faultStatusMask >> 8) & 0xFF), (byte)(faultLogMask & 0xFF), (byte)((faultLogMask >> 8) & 0xFF) ]); } private void CreateSensors() { //Onboard temperature sensors AddSensor("Onboard Temperature In", 0, SensorType.Temperature, dd => (float)dd.OnboardTempInC); AddSensor("Onboard Temperature Out", 1, SensorType.Temperature, dd => (float)dd.OnboardTempOutC); //External temperature sensors, requires shipped temperature sensors to be connected AddSensor("External Temperature 1", 2, SensorType.Temperature, dd => (float)dd.ExternalTemp1C); AddSensor("External Temperature 2", 3, SensorType.Temperature, dd => (float)dd.ExternalTemp2C); //Pin voltages AddSensor("Pin 1 Voltage", 10, SensorType.Voltage, dd => (float)dd.PinVoltage[0]); AddSensor("Pin 2 Voltage", 11, SensorType.Voltage, dd => (float)dd.PinVoltage[1]); AddSensor("Pin 3 Voltage", 12, SensorType.Voltage, dd => (float)dd.PinVoltage[2]); AddSensor("Pin 4 Voltage", 13, SensorType.Voltage, dd => (float)dd.PinVoltage[3]); AddSensor("Pin 5 Voltage", 14, SensorType.Voltage, dd => (float)dd.PinVoltage[4]); AddSensor("Pin 6 Voltage", 15, SensorType.Voltage, dd => (float)dd.PinVoltage[5]); //Pin currents AddSensor("Total Current", 20, SensorType.Current, dd => (float)dd.SumCurrentA); AddSensor("Pin 1 Current", 21, SensorType.Current, dd => (float)dd.PinCurrent[0]); AddSensor("Pin 2 Current", 22, SensorType.Current, dd => (float)dd.PinCurrent[1]); AddSensor("Pin 3 Current", 23, SensorType.Current, dd => (float)dd.PinCurrent[2]); AddSensor("Pin 4 Current", 24, SensorType.Current, dd => (float)dd.PinCurrent[3]); AddSensor("Pin 5 Current", 25, SensorType.Current, dd => (float)dd.PinCurrent[4]); AddSensor("Pin 6 Current", 26, SensorType.Current, dd => (float)dd.PinCurrent[5]); //Power AddSensor("Total Power", 30, SensorType.Power, dd => (float)dd.SumPowerW); } private void AddSensor(string name, int index, SensorType sensorType, GetWireViewPro2SensorValue getValue) { var sensor = new WireViewPro2Sensor(name, index, sensorType, this, _settings, getValue); _sensors.Add(sensor); ActivateSensor(sensor); } private void Connect() { if (IsConnected) { return; } try { _serialPort = new SharedSerialPort(_portName, _baudRate, Parity.None, 8, StopBits.One); _serialPort.ReadTimeout = 1000; _serialPort.WriteTimeout = 1000; if (!ReadWelcomeMessage()) { IsConnected = false; return; } VendorDataStruct? vendorData = ReadVendorData(); if (vendorData.HasValue && vendorData.Value.VendorId == VendorID && vendorData.Value.ProductId == ProductID) { VendorData = vendorData.Value; UniqueID = ReadUniqueID(); IsConnected = true; } } catch { if (_serialPort != null) { try { if (_serialPort.IsOpen) { _serialPort.Close(); } } catch { //Ignore exceptions during cleanup } finally { _serialPort.Dispose(); _serialPort = null; } } IsConnected = false; } } private void Disconnect() { if (!IsConnected) { return; } if (_serialPort != null) { _serialPort.Close(); _serialPort.Dispose(); _serialPort = null; } IsConnected = false; VendorData = null; UniqueID = null; } private bool ReadWelcomeMessage(bool sendCmd = false) { int size = WelcomeMessage.Length + 1; var bytes = SendData([(byte)UsbCmd.CMD_WELCOME], size, true); return bytes == null ? false : Encoding.ASCII.GetString(bytes, 0, size).TrimEnd('\0').CompareTo(WelcomeMessage) == 0; } private VendorDataStruct? ReadVendorData() { var bytes = SendCmd(UsbCmd.CMD_READ_VENDOR_DATA, Marshal.SizeOf()); return bytes == null ? null : BytesToStructure(bytes); } private string ReadUniqueID() { const int UIDBytes = 12; var bytes = SendCmd(UsbCmd.CMD_READ_UID, UIDBytes); return bytes == null ? null : BitConverter.ToString(bytes).Replace("-", string.Empty); } private SensorStruct? ReadSensorValues() { if (!IsConnected) { return null; } var bytes = SendCmd(UsbCmd.CMD_READ_SENSOR_VALUES, Marshal.SizeOf()); return bytes == null ? null : BytesToStructure(bytes); } private DeviceData MapSensorStructure(SensorStruct sensorStruct) { var deviceData = new DeviceData { Connected = true, HardwareRevision = $"{VendorData?.VendorId}{VendorData?.ProductId}", FirmwareVersion = $"{VendorData?.FwVersion}", OnboardTempInC = sensorStruct.Ts[(int)SensorTs.SENSOR_TS_IN] / 10.0, OnboardTempOutC = sensorStruct.Ts[(int)SensorTs.SENSOR_TS_OUT] / 10.0, ExternalTemp1C = sensorStruct.Ts[(int)SensorTs.SENSOR_TS3] / 10.0, ExternalTemp2C = sensorStruct.Ts[(int)SensorTs.SENSOR_TS4] / 10.0, FaultStatus = sensorStruct.FaultStatus, FaultLog = sensorStruct.FaultLog, }; switch (sensorStruct.HpwrCapability) { case HpwrCapability.PSU_CAP_600W: deviceData.PsuCapabilityW = 600; break; case HpwrCapability.PSU_CAP_450W: deviceData.PsuCapabilityW = 450; break; case HpwrCapability.PSU_CAP_300W: deviceData.PsuCapabilityW = 300; break; case HpwrCapability.PSU_CAP_150W: deviceData.PsuCapabilityW = 150; break; } for (int i = 0; i < 6; ++i) { deviceData.PinVoltage[i] = sensorStruct.PowerReadings[i].Voltage / 1000.0; deviceData.PinCurrent[i] = sensorStruct.PowerReadings[i].Current / 1000.0; } return deviceData; } private byte[] SendCmd(UsbCmd cmd, int responseSize = 0, bool rts = false) { return SendData(new[] { (byte)cmd }, responseSize, rts); } private byte[] SendData(byte[] data, int responseSize = 0, bool rts = false) { if (_serialPort == null) { return null; } byte[] buf = null; try { lock (_serialPort) { _serialPort.Open(); _serialPort.DiscardInBuffer(); if (rts) { _serialPort.RtsEnable = true; Thread.Sleep(10); } _serialPort.Write(data, 0, data.Length); if (responseSize > 0) { buf = ReadExact(responseSize); } if (rts) { Thread.Sleep(10); _serialPort.RtsEnable = false; } } } finally { _serialPort.Close(); } return buf; } private byte[] ReadExact(int size) { byte[] buffer = new byte[size]; int offset = 0; var sw = Stopwatch.StartNew(); while (offset < size && sw.ElapsedMilliseconds < _serialPort.ReadTimeout) { try { if (_serialPort.BytesToRead > 0) { offset += _serialPort.Read(buffer, offset, size - offset); } } catch (TimeoutException) { //Ignore timeout exceptions and continue reading } } return offset != size ? null : buffer; } private T BytesToStructure(byte[] bytes) where T : struct { var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); try { return Marshal.PtrToStructure(handle.AddrOfPinnedObject()); } finally { handle.Free(); } } private byte[] StructureToBytes(T value) where T : struct { int size = Marshal.SizeOf(); byte[] bytes = new byte[size]; IntPtr ptr = Marshal.AllocHGlobal(size); try { Marshal.StructureToPtr(value, ptr, false); Marshal.Copy(ptr, bytes, 0, size); return bytes; } finally { Marshal.FreeHGlobal(ptr); } } }