From e2e0d1c9ad1f7c969c47f081b5ea732e0c17a662 Mon Sep 17 00:00:00 2001 From: Blacktempel <6657796+Blacktempel@users.noreply.github.com> Date: Fri, 13 Feb 2026 18:15:24 +0100 Subject: [PATCH] Updates for TG WVP2 (#2215) * Implement Fan control for TG WVP2. Calculate approximate fan speed. Catch two exceptions. Remove leftover close in ReadConfig. * Support for multi device detection * Add more data to report * Add group for power monitors and remove from GenericGpu. * Move files. Add option for Power Monitor in Computer + UI. --- .../Resources/powermonitor.png | Bin 0 -> 894 bytes LibreHardwareMonitor/UI/HardwareTypeImage.cs | 5 +- LibreHardwareMonitor/UI/MainForm.Designer.cs | 9 + LibreHardwareMonitor/UI/MainForm.cs | 4 + LibreHardwareMonitor/Utilities/HttpServer.cs | 3 + LibreHardwareMonitorLib/Hardware/Computer.cs | 23 ++ .../Hardware/Gpu/GenericGpu.cs | 19 - .../Hardware/HardwareType.cs | 2 +- LibreHardwareMonitorLib/Hardware/IComputer.cs | 6 + .../{Gpu => }/PowerMonitor/IPowerMonitor.cs | 2 +- .../PowerMonitor/PowerMonitorGroup.cs | 54 +++ .../PowerMonitor/SharedSerialPort.cs | 2 +- .../{Gpu => }/PowerMonitor/WireViewPro2.cs | 330 +++++++++++++++--- .../PowerMonitor/WireViewPro2Sensor.cs | 2 +- .../Interop/PowerMonitor/DeviceData.cs | 4 +- 15 files changed, 396 insertions(+), 69 deletions(-) create mode 100644 LibreHardwareMonitor/Resources/powermonitor.png rename LibreHardwareMonitorLib/Hardware/{Gpu => }/PowerMonitor/IPowerMonitor.cs (88%) create mode 100644 LibreHardwareMonitorLib/Hardware/PowerMonitor/PowerMonitorGroup.cs rename LibreHardwareMonitorLib/Hardware/{Gpu => }/PowerMonitor/SharedSerialPort.cs (97%) rename LibreHardwareMonitorLib/Hardware/{Gpu => }/PowerMonitor/WireViewPro2.cs (60%) rename LibreHardwareMonitorLib/Hardware/{Gpu => }/PowerMonitor/WireViewPro2Sensor.cs (94%) diff --git a/LibreHardwareMonitor/Resources/powermonitor.png b/LibreHardwareMonitor/Resources/powermonitor.png new file mode 100644 index 0000000000000000000000000000000000000000..ed99dd71aabfbbda64e3b4144d2bf0e300a5ab2c GIT binary patch literal 894 zcmV-^1A+XBP)YjL}KRjEV8HW~Cc-;l!m2ql}(_B*io3xDu47 z&fzy<@mu&fdg|IB%3{^(?duO{I`RiJ?*b6aUI4)yp_6Z3!nQ2_XiQ_W&tQ2HMcp-M zJ6B79zK9wfnZlFH^$08uK-s|0`D?w)A4aDj49(R~5C+^JC8qwH!NIN?+$*bsDDA0I ziJ?jKS2e=O^0NwsJ=cyS^Zz^|MIkK8%|`aJCHOe~kpSPzF~6p;zcq}O(*!sbt5GlF zz$!QmuiCIc&FVsGOmw3b&xobIhv!+m3j z*ESJ=MYHyj2VqFoUA*OYg!-eHo|wS+KmxD36G&SoQlDn9r~U%kL!3f+{H7Y}d5OO2 zM)-pP+=<7~+!aOs#`P%7-+|EZZS?-tg$-Lb!_jTr3q>*5+>652tGIvhhTFti^*ok9 zf20XU(9areq(0AL`-xrnY2^y!tzLudg+HKR-4>X$7JR{l-gpaIu2KO3O1i7qgGoG% zG{Ert*kl3QZPe!^PIiWIcH3bnBST;vKEIza8)h<4n|327Zy6_O(-*quf%KO-6tq#nIriXKXIQ;``48GluW;H-CX;gJ z$QDHIk2&~CHaTxW*Jx2iclcHbIEmxeGM1X^w<>M4~&xCq2p-X4CT6dbk~m{yWx%32n4v4 z(p?$+vj0~M-hBwM^YRfTEr<07pi!o~y0pEwedOf73-$fb;TBqH%Oi4^ZIYzl0PPj7 UR0Kx-wg3PC07*qoM6N<$g1}R+T>t<8 literal 0 HcmV?d00001 diff --git a/LibreHardwareMonitor/UI/HardwareTypeImage.cs b/LibreHardwareMonitor/UI/HardwareTypeImage.cs index 0f8b8fc..3d867b8 100644 --- a/LibreHardwareMonitor/UI/HardwareTypeImage.cs +++ b/LibreHardwareMonitor/UI/HardwareTypeImage.cs @@ -63,6 +63,9 @@ public class HardwareTypeImage case HardwareType.Battery: image = Utilities.EmbeddedResources.GetImage("battery.png"); break; + case HardwareType.PowerMonitor: + image = Utilities.EmbeddedResources.GetImage("powermonitor.png"); + break; default: image = new Bitmap(1, 1); break; @@ -70,4 +73,4 @@ public class HardwareTypeImage _images.Add(hardwareType, image); return image; } -} \ No newline at end of file +} diff --git a/LibreHardwareMonitor/UI/MainForm.Designer.cs b/LibreHardwareMonitor/UI/MainForm.Designer.cs index b9cca1d..0135057 100644 --- a/LibreHardwareMonitor/UI/MainForm.Designer.cs +++ b/LibreHardwareMonitor/UI/MainForm.Designer.cs @@ -56,6 +56,7 @@ namespace LibreHardwareMonitor.UI this.cpuMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.ramMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.gpuMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.powerMonitorMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.fanControllerMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.hddMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.nicMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -292,6 +293,7 @@ namespace LibreHardwareMonitor.UI this.cpuMenuItem, this.ramMenuItem, this.gpuMenuItem, + this.powerMonitorMenuItem, this.fanControllerMenuItem, this.hddMenuItem, this.nicMenuItem, @@ -325,6 +327,12 @@ namespace LibreHardwareMonitor.UI this.gpuMenuItem.Size = new System.Drawing.Size(180, 22); this.gpuMenuItem.Text = "GPU"; // + // powerMonitorMenuItem + // + this.powerMonitorMenuItem.Name = "powerMonitorMenuItem"; + this.powerMonitorMenuItem.Size = new System.Drawing.Size(180, 22); + this.powerMonitorMenuItem.Text = "Power Monitors"; + // // fanControllerMenuItem // this.fanControllerMenuItem.Name = "fanControllerMenuItem"; @@ -1176,6 +1184,7 @@ namespace LibreHardwareMonitor.UI private System.Windows.Forms.ToolStripMenuItem mainboardMenuItem; private System.Windows.Forms.ToolStripMenuItem cpuMenuItem; private System.Windows.Forms.ToolStripMenuItem gpuMenuItem; + private System.Windows.Forms.ToolStripMenuItem powerMonitorMenuItem; private System.Windows.Forms.ToolStripMenuItem fanControllerMenuItem; private System.Windows.Forms.ToolStripMenuItem ramMenuItem; private System.Windows.Forms.ToolStripMenuItem logSensorsMenuItem; diff --git a/LibreHardwareMonitor/UI/MainForm.cs b/LibreHardwareMonitor/UI/MainForm.cs index 72230a8..b98afb5 100644 --- a/LibreHardwareMonitor/UI/MainForm.cs +++ b/LibreHardwareMonitor/UI/MainForm.cs @@ -40,6 +40,7 @@ public sealed partial class MainForm : Form private readonly UserOption _readCpuSensors; private readonly UserOption _readFanControllersSensors; private readonly UserOption _readGpuSensors; + private readonly UserOption _readPowerMonitorSensors; private readonly UserOption _readHddSensors; private readonly UserOption _readMainboardSensors; private readonly UserOption _readNicSensors; @@ -265,6 +266,9 @@ public sealed partial class MainForm : Form _readGpuSensors = new UserOption("gpuMenuItem", true, gpuMenuItem, _settings); _readGpuSensors.Changed += delegate { _computer.IsGpuEnabled = _readGpuSensors.Value; }; + _readPowerMonitorSensors = new UserOption("powerMonitorMenuItem", true, powerMonitorMenuItem, _settings); + _readPowerMonitorSensors.Changed += delegate { _computer.IsPowerMonitorEnabled = _readPowerMonitorSensors.Value; }; + _readFanControllersSensors = new UserOption("fanControllerMenuItem", true, fanControllerMenuItem, _settings); _readFanControllersSensors.Changed += delegate { _computer.IsControllerEnabled = _readFanControllersSensors.Value; }; diff --git a/LibreHardwareMonitor/Utilities/HttpServer.cs b/LibreHardwareMonitor/Utilities/HttpServer.cs index 51b7c04..a21af16 100644 --- a/LibreHardwareMonitor/Utilities/HttpServer.cs +++ b/LibreHardwareMonitor/Utilities/HttpServer.cs @@ -883,6 +883,8 @@ public class HttpServer return "power-supply.png"; case HardwareType.Battery: return "battery.png"; + case HardwareType.PowerMonitor: + return "powermonitor.png"; default: return "cpu.png"; } @@ -896,6 +898,7 @@ public class HttpServer case SensorType.Current: return "voltage.png"; case SensorType.Clock: + case SensorType.Timing: return "clock.png"; case SensorType.Load: return "load.png"; diff --git a/LibreHardwareMonitorLib/Hardware/Computer.cs b/LibreHardwareMonitorLib/Hardware/Computer.cs index 3790b9d..bb87340 100644 --- a/LibreHardwareMonitorLib/Hardware/Computer.cs +++ b/LibreHardwareMonitorLib/Hardware/Computer.cs @@ -23,6 +23,7 @@ using LibreHardwareMonitor.Hardware.Gpu; using LibreHardwareMonitor.Hardware.Memory; using LibreHardwareMonitor.Hardware.Motherboard; using LibreHardwareMonitor.Hardware.Network; +using LibreHardwareMonitor.Hardware.PowerMonitor; using LibreHardwareMonitor.Hardware.Psu.Corsair; using LibreHardwareMonitor.Hardware.Psu.Msi; using LibreHardwareMonitor.Hardware.Storage; @@ -42,6 +43,7 @@ public class Computer : IComputer private bool _controllerEnabled; private bool _cpuEnabled; private bool _gpuEnabled; + private bool _powerMonitorEnabled; private bool _memoryEnabled; private bool _motherboardEnabled; private bool _networkEnabled; @@ -194,6 +196,24 @@ public class Computer : IComputer } } + /// + public bool IsPowerMonitorEnabled + { + get { return _powerMonitorEnabled; } + set + { + if (_open && value != _powerMonitorEnabled) + { + if (value) + Add(new PowerMonitorGroup(_settings)); + else + RemoveType(); + } + + _powerMonitorEnabled = value; + } + } + /// public bool IsMemoryEnabled { @@ -520,6 +540,9 @@ public class Computer : IComputer Add(new IntelGpuGroup(GetIntelCpus(), _settings)); } + if (_powerMonitorEnabled) + Add(new PowerMonitorGroup(_settings)); + if (_controllerEnabled) { Add(new TBalancerGroup(_settings)); diff --git a/LibreHardwareMonitorLib/Hardware/Gpu/GenericGpu.cs b/LibreHardwareMonitorLib/Hardware/Gpu/GenericGpu.cs index e70e246..8e9b479 100644 --- a/LibreHardwareMonitorLib/Hardware/Gpu/GenericGpu.cs +++ b/LibreHardwareMonitorLib/Hardware/Gpu/GenericGpu.cs @@ -3,15 +3,10 @@ // Copyright (C) LibreHardwareMonitor and Contributors. // All Rights Reserved. -using System.Collections.Generic; -using LibreHardwareMonitor.Hardware.Gpu.PowerMonitor; - namespace LibreHardwareMonitor.Hardware.Gpu; public abstract class GenericGpu : Hardware { - readonly List _subHardware = new(); - /// /// Initializes a new instance of the class. /// @@ -20,7 +15,6 @@ public abstract class GenericGpu : Hardware /// Additional settings passed by the . protected GenericGpu(string name, Identifier identifier, ISettings settings) : base(name, identifier, settings) { - TryAddSubHardware(); } /// @@ -28,21 +22,8 @@ public abstract class GenericGpu : Hardware /// public abstract string DeviceId { get; } - public override IHardware[] SubHardware => _subHardware.ToArray(); - public override void Close() { - _subHardware.ForEach(h => h.Close()); - base.Close(); } - - private void TryAddSubHardware() - { - var wireViewPro2 = WireViewPro2.TryFindDevice(_settings); - if (wireViewPro2 != null && wireViewPro2.IsConnected) - { - _subHardware.Add(wireViewPro2); - } - } } diff --git a/LibreHardwareMonitorLib/Hardware/HardwareType.cs b/LibreHardwareMonitorLib/Hardware/HardwareType.cs index e6fec96..e775271 100644 --- a/LibreHardwareMonitorLib/Hardware/HardwareType.cs +++ b/LibreHardwareMonitorLib/Hardware/HardwareType.cs @@ -24,5 +24,5 @@ public enum HardwareType EmbeddedController, Psu, Battery, - GpuPowerMonitor, + PowerMonitor, } diff --git a/LibreHardwareMonitorLib/Hardware/IComputer.cs b/LibreHardwareMonitorLib/Hardware/IComputer.cs index e3a0738..d8933d8 100644 --- a/LibreHardwareMonitorLib/Hardware/IComputer.cs +++ b/LibreHardwareMonitorLib/Hardware/IComputer.cs @@ -78,6 +78,12 @@ public interface IComputer : IElement /// if a given category of devices is already enabled. bool IsGpuEnabled { get; } + /// + /// Gets or sets a value indicating whether collecting information about devices should be enabled and updated. + /// + /// if a given category of devices is already enabled. + bool IsPowerMonitorEnabled { get; } + /// /// Gets or sets a value indicating whether collecting information about devices should be enabled and updated. /// diff --git a/LibreHardwareMonitorLib/Hardware/Gpu/PowerMonitor/IPowerMonitor.cs b/LibreHardwareMonitorLib/Hardware/PowerMonitor/IPowerMonitor.cs similarity index 88% rename from LibreHardwareMonitorLib/Hardware/Gpu/PowerMonitor/IPowerMonitor.cs rename to LibreHardwareMonitorLib/Hardware/PowerMonitor/IPowerMonitor.cs index 1f69105..a7356bb 100644 --- a/LibreHardwareMonitorLib/Hardware/Gpu/PowerMonitor/IPowerMonitor.cs +++ b/LibreHardwareMonitorLib/Hardware/PowerMonitor/IPowerMonitor.cs @@ -4,7 +4,7 @@ // Partial Copyright (C) Michael Möller and Contributors. // All Rights Reserved. -namespace LibreHardwareMonitor.Hardware.Gpu.PowerMonitor; +namespace LibreHardwareMonitor.Hardware.PowerMonitor; internal interface IPowerMonitor { diff --git a/LibreHardwareMonitorLib/Hardware/PowerMonitor/PowerMonitorGroup.cs b/LibreHardwareMonitorLib/Hardware/PowerMonitor/PowerMonitorGroup.cs new file mode 100644 index 0000000..9fc2fa2 --- /dev/null +++ b/LibreHardwareMonitorLib/Hardware/PowerMonitor/PowerMonitorGroup.cs @@ -0,0 +1,54 @@ +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// Copyright (C) Florian K. (Blacktempel) +// All Rights Reserved. + +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LibreHardwareMonitor.Hardware.PowerMonitor; + +internal class PowerMonitorGroup : IGroup +{ + private readonly List _hardware = new(); + private readonly StringBuilder _report = new(); + + public PowerMonitorGroup(ISettings settings) + { + _report.AppendLine("Power Monitors:"); + _report.AppendLine(); + + var devices = WireViewPro2.TryFindDevices(settings); + + devices.ForEach(wvp2 => + { + if (wvp2.IsConnected) + { + _report.AppendLine($"Power Monitor for '{wvp2.Name}' initialized successfully"); + + _hardware.Add(wvp2); + } + }); + } + + public IReadOnlyList Hardware => _hardware; + + public void Close() + { + foreach (Hardware hw in _hardware.OfType()) + { + hw.Close(); + } + } + + public string GetReport() + { + var sb = new StringBuilder(); + sb.AppendLine(_report.ToString()); + + _hardware.ForEach(hw => sb.AppendLine(hw.GetReport())); + + return sb.ToString(); + } +} diff --git a/LibreHardwareMonitorLib/Hardware/Gpu/PowerMonitor/SharedSerialPort.cs b/LibreHardwareMonitorLib/Hardware/PowerMonitor/SharedSerialPort.cs similarity index 97% rename from LibreHardwareMonitorLib/Hardware/Gpu/PowerMonitor/SharedSerialPort.cs rename to LibreHardwareMonitorLib/Hardware/PowerMonitor/SharedSerialPort.cs index 8bb3be6..84ea4e5 100644 --- a/LibreHardwareMonitorLib/Hardware/Gpu/PowerMonitor/SharedSerialPort.cs +++ b/LibreHardwareMonitorLib/Hardware/PowerMonitor/SharedSerialPort.cs @@ -1,6 +1,6 @@ using System.IO.Ports; -namespace LibreHardwareMonitor.Hardware.Gpu.PowerMonitor; +namespace LibreHardwareMonitor.Hardware.PowerMonitor; internal sealed class SharedSerialPort : SerialPort { diff --git a/LibreHardwareMonitorLib/Hardware/Gpu/PowerMonitor/WireViewPro2.cs b/LibreHardwareMonitorLib/Hardware/PowerMonitor/WireViewPro2.cs similarity index 60% rename from LibreHardwareMonitorLib/Hardware/Gpu/PowerMonitor/WireViewPro2.cs rename to LibreHardwareMonitorLib/Hardware/PowerMonitor/WireViewPro2.cs index 81c3825..b5da949 100644 --- a/LibreHardwareMonitorLib/Hardware/Gpu/PowerMonitor/WireViewPro2.cs +++ b/LibreHardwareMonitorLib/Hardware/PowerMonitor/WireViewPro2.cs @@ -1,13 +1,19 @@ -using System; +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// Copyright (C) Florian K. (Blacktempel) +// All Rights Reserved. + +using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.IO.Ports; using System.Runtime.InteropServices; using System.Text; using System.Threading; using LibreHardwareMonitor.Interop.PowerMonitor; -namespace LibreHardwareMonitor.Hardware.Gpu.PowerMonitor; +namespace LibreHardwareMonitor.Hardware.PowerMonitor; /// /// Thermal Grizzly WireView Pro II power monitor. @@ -16,9 +22,22 @@ public sealed class WireViewPro2 : Hardware, IPowerMonitor { public const string WelcomeMessage = "Thermal Grizzly WireView Pro II"; + /// + /// Max RPM according to the8auer. This is a custom made fan. + /// + private const int MaxFanRPM = 5000; + private const byte VendorID = 0xEF; private const byte ProductID = 0x05; + /// + /// Time the fan needs to ramp up by 10%. + /// + private static readonly TimeSpan FanRampupTime = TimeSpan.FromSeconds(3.5); + + private double _lastFanSpeedRpm; + private DateTime _lastFanUpdateTime = DateTime.MinValue; + private readonly int _baudRate; private readonly string _portName; private readonly List _sensors = []; @@ -39,7 +58,7 @@ public sealed class WireViewPro2 : Hardware, IPowerMonitor } } - public override HardwareType HardwareType => HardwareType.GpuPowerMonitor; + public override HardwareType HardwareType => HardwareType.PowerMonitor; public bool IsConnected { get; private set; } @@ -49,11 +68,13 @@ public sealed class WireViewPro2 : Hardware, IPowerMonitor public int ConfigVersion => VendorData?.FwVersion > 2 ? 1 : 0; - public static WireViewPro2 TryFindDevice(ISettings settings) + public static List TryFindDevices(ISettings settings) { + var devices = new List(); + if (!Software.OperatingSystem.IsWindows8OrGreater) { - return null; //No Linux implementation yet + return devices; //No Linux implementation yet } List matches = Stm32PortFinder.FindMatchingComPorts(0x0483, 0x5740); @@ -67,7 +88,8 @@ public sealed class WireViewPro2 : Hardware, IPowerMonitor wireViewPro2 = new WireViewPro2(port, settings); if (wireViewPro2.IsConnected) { - break; + devices.Add(wireViewPro2); + continue; } wireViewPro2.Close(); @@ -80,7 +102,7 @@ public sealed class WireViewPro2 : Hardware, IPowerMonitor } } - return wireViewPro2; + return devices; } public override string GetReport() @@ -93,6 +115,15 @@ public sealed class WireViewPro2 : Hardware, IPowerMonitor sb.AppendLine($" Sensor: {sensor.Name} = {sensor.Value}"); } + if (VendorData.HasValue) + { + sb.AppendLine($" {nameof(VendorData.Value.VendorId)} = {VendorData.Value.VendorId}"); + sb.AppendLine($" {nameof(VendorData.Value.ProductId)} = {VendorData.Value.ProductId}"); + sb.AppendLine($" {nameof(VendorData.Value.FwVersion)} = {VendorData.Value.FwVersion}"); + } + + sb.AppendLine($" {nameof(UniqueID)} = {UniqueID}"); + return sb.ToString(); } @@ -105,15 +136,43 @@ public sealed class WireViewPro2 : Hardware, IPowerMonitor public override void Update() { - SensorStruct? sensorValues = ReadSensorValues(); - if (sensorValues.HasValue) - { - DeviceData deviceData = MapSensorStructure(sensorValues.Value); + var deviceData = GetDeviceData(); + if (deviceData != null) + { _sensors.ForEach(wvps => wvps.Update(deviceData)); } } + public DeviceData GetDeviceData() + { + SensorStruct? sensorValues = null; + DeviceConfigStructV2? config = null; + + try + { + sensorValues = ReadSensorValues(); + + config = ReadConfig(); + } + catch (IOException) + { + //"A device attached to the system is not functioning." + //Can happen rarely + } + catch (InvalidOperationException) + { + //Can happen sometimes if the device is disconnecting while reading + } + + if (sensorValues.HasValue && config.HasValue) + { + return MapSensorStructure(sensorValues.Value, config.Value); + } + + return null; + } + public DeviceConfigStructV2? ReadConfig() { if (!IsConnected) @@ -123,41 +182,34 @@ public sealed class WireViewPro2 : Hardware, IPowerMonitor int size = 0; - try + switch (ConfigVersion) { - 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) - { + case 0: + size = Marshal.SizeOf(); + break; + case 1: + size = Marshal.SizeOf(); + break; + default: return null; - } - - switch (ConfigVersion) - { - case 0: - var s = BytesToStructure(buf); - return StructureConversion.ConvertConfigV1ToV2(s); - case 1: - return BytesToStructure(buf); - default: - return null; - } } - finally + + var buf = SendCmd(UsbCmd.CMD_READ_CONFIG, size); + + if (buf == null) { - _serialPort.Close(); + return null; + } + + switch (ConfigVersion) + { + case 0: + var s = BytesToStructure(buf); + return StructureConversion.ConvertConfigV1ToV2(s); + case 1: + return BytesToStructure(buf); + default: + return null; } } @@ -283,15 +335,204 @@ public sealed class WireViewPro2 : Hardware, IPowerMonitor //Power AddSensor("Total Power", 30, SensorType.Power, dd => (float)dd.SumPowerW); + + //Fan + var fan = AddSensor("Fan", 40, SensorType.Fan, dd => CalculateFanSpeed(dd)); + var fanControl = new Control(fan, _settings, 0, 100); + + fan.Control = fanControl; + fanControl.ControlModeChanged += OnFanControlModeChanged; + fanControl.SoftwareControlValueChanged += OnSoftwareControlValueChanged; } - private void AddSensor(string name, int index, SensorType sensorType, GetWireViewPro2SensorValue getValue) + private WireViewPro2Sensor AddSensor(string name, int index, SensorType sensorType, GetWireViewPro2SensorValue getValue) { var sensor = new WireViewPro2Sensor(name, index, sensorType, this, _settings, getValue); _sensors.Add(sensor); ActivateSensor(sensor); + + return sensor; + } + + private double FromTemp(short temp) + { + return temp == 0 ? 0 : temp / 10.0; + } + + private short ToTemp(double temp) + { + return (short)(temp * 10); + } + + private void OnFanControlModeChanged(Control control) + { + var deviceData = GetDeviceData(); + + if (deviceData == null) + { + return; + } + + switch (control.ControlMode) + { + case ControlMode.Software: + deviceData.Config.FanConfig.Mode = FanMode.FanModeFixed; + break; + case ControlMode.Default: + //Set default values of TG Software + deviceData.Config.FanConfig.Mode = FanMode.FanModeCurve; + deviceData.Config.FanConfig.TempSource = TempSource.TempSourceTmax; + + deviceData.Config.FanConfig.TempMin = ToTemp(50); + deviceData.Config.FanConfig.TempMax = ToTemp(80); + + deviceData.Config.FanConfig.DutyMin = 0; + deviceData.Config.FanConfig.DutyMax = 100; + break; + default: + break; + } + + WriteConfig(deviceData.Config); + } + + private void OnSoftwareControlValueChanged(Control control) + { + var deviceData = GetDeviceData(); + + if (deviceData == null) + { + return; + } + + byte value = (byte)control.SoftwareValue; + + if (value < 0) + { + value = 0; + } + else if (value > 100) + { + value = 100; + } + + deviceData.Config.FanConfig.TempMin = ToTemp(0); + deviceData.Config.FanConfig.TempMax = ToTemp(80); + + deviceData.Config.FanConfig.DutyMin = value; + deviceData.Config.FanConfig.DutyMax = value; + + WriteConfig(deviceData.Config); + } + + /// + /// Fan speed for this device is an approximation based on the curve configuration and current temperatures.
+ /// The device itself does not report actual fan speed. + ///
+ private float CalculateFanSpeed(DeviceData dd) + { + var fanConfig = dd.Config.FanConfig; + double targetRpm; + + switch (fanConfig.Mode) + { + case FanMode.FanModeCurve: + var tempMin = FromTemp(fanConfig.TempMin); + var tempMax = FromTemp(fanConfig.TempMax); + var currentTemperature = GetActiveTemperature(dd); + + if (tempMax <= tempMin || currentTemperature <= tempMin) + { + targetRpm = fanConfig.DutyMin; + } + else if (currentTemperature >= tempMax) + { + targetRpm = fanConfig.DutyMax; + } + else + { + var temp = (currentTemperature - tempMin) / (tempMax - tempMin); + var fanSpeedInPercent = fanConfig.DutyMin + temp * (fanConfig.DutyMax - fanConfig.DutyMin); + targetRpm = fanSpeedInPercent; + } + + targetRpm = targetRpm == 0 ? 0 : targetRpm / 100.0 * MaxFanRPM; + break; + case FanMode.FanModeFixed: + targetRpm = fanConfig.DutyMin == 0 ? 0 : fanConfig.DutyMin / 100.0 * MaxFanRPM; + break; + default: + return -1; + } + + return (float)ApplyFanRamp(targetRpm); + } + + private double ApplyFanRamp(double targetRpm) + { + var now = DateTime.UtcNow; + + if (_lastFanUpdateTime == DateTime.MinValue) + { + _lastFanSpeedRpm = targetRpm; + _lastFanUpdateTime = now; + return targetRpm; + } + + var elapsed = now - _lastFanUpdateTime; + + if (elapsed <= TimeSpan.Zero) + { + return _lastFanSpeedRpm; + } + + var maxPercentDeltaPerSecond = 0.1 / FanRampupTime.TotalSeconds; //10% per FanRampupTime + var allowedPercentDelta = elapsed.TotalSeconds * maxPercentDeltaPerSecond; + + var rpmDelta = targetRpm - _lastFanSpeedRpm; + var percentDelta = rpmDelta / MaxFanRPM; + + if (Math.Abs(percentDelta) > allowedPercentDelta) + { + rpmDelta = Math.Sign(percentDelta) * allowedPercentDelta * MaxFanRPM; + } + + _lastFanSpeedRpm += rpmDelta; + _lastFanUpdateTime = now; + + return _lastFanSpeedRpm; + } + + private double GetActiveTemperature(DeviceData dd) + { + double temperature = 0; + + switch (dd.Config.FanConfig.TempSource) + { + case TempSource.TempSourceTsIn: + temperature = dd.OnboardTempInC; + break; + case TempSource.TempSourceTsOut: + temperature = dd.OnboardTempOutC; + break; + case TempSource.TempSourceTs1: + temperature = dd.ExternalTemp1C; + break; + case TempSource.TempSourceTs2: + temperature = dd.ExternalTemp2C; + break; + case TempSource.TempSourceTmax: + temperature = Math.Max( + Math.Max(dd.OnboardTempInC, dd.OnboardTempOutC), + Math.Max(dd.ExternalTemp1C, dd.ExternalTemp2C)); + break; + default: + break; + } + + return temperature; } private void Connect() @@ -408,7 +649,7 @@ public sealed class WireViewPro2 : Hardware, IPowerMonitor return bytes == null ? null : BytesToStructure(bytes); } - private DeviceData MapSensorStructure(SensorStruct sensorStruct) + private DeviceData MapSensorStructure(SensorStruct sensorStruct, DeviceConfigStructV2 config) { var deviceData = new DeviceData { @@ -421,6 +662,7 @@ public sealed class WireViewPro2 : Hardware, IPowerMonitor ExternalTemp2C = sensorStruct.Ts[(int)SensorTs.SENSOR_TS4] / 10.0, FaultStatus = sensorStruct.FaultStatus, FaultLog = sensorStruct.FaultLog, + Config = config, }; switch (sensorStruct.HpwrCapability) diff --git a/LibreHardwareMonitorLib/Hardware/Gpu/PowerMonitor/WireViewPro2Sensor.cs b/LibreHardwareMonitorLib/Hardware/PowerMonitor/WireViewPro2Sensor.cs similarity index 94% rename from LibreHardwareMonitorLib/Hardware/Gpu/PowerMonitor/WireViewPro2Sensor.cs rename to LibreHardwareMonitorLib/Hardware/PowerMonitor/WireViewPro2Sensor.cs index e074a59..88d414a 100644 --- a/LibreHardwareMonitorLib/Hardware/Gpu/PowerMonitor/WireViewPro2Sensor.cs +++ b/LibreHardwareMonitorLib/Hardware/PowerMonitor/WireViewPro2Sensor.cs @@ -6,7 +6,7 @@ using LibreHardwareMonitor.Interop.PowerMonitor; -namespace LibreHardwareMonitor.Hardware.Gpu.PowerMonitor; +namespace LibreHardwareMonitor.Hardware.PowerMonitor; internal delegate float GetWireViewPro2SensorValue(DeviceData wvp); diff --git a/LibreHardwareMonitorLib/Interop/PowerMonitor/DeviceData.cs b/LibreHardwareMonitorLib/Interop/PowerMonitor/DeviceData.cs index 63d5d6e..4e97d21 100644 --- a/LibreHardwareMonitorLib/Interop/PowerMonitor/DeviceData.cs +++ b/LibreHardwareMonitorLib/Interop/PowerMonitor/DeviceData.cs @@ -9,7 +9,7 @@ using System.Linq; namespace LibreHardwareMonitor.Interop.PowerMonitor; -internal sealed class DeviceData +public sealed class DeviceData { public DateTime Timestamp { get; set; } = DateTime.UtcNow; @@ -40,4 +40,6 @@ internal sealed class DeviceData public ushort FaultStatus { get; set; } public ushort FaultLog { get; set; } + + public DeviceConfigStructV2 Config; }