Add support for Corsair PSUs (#458)

Thanks to @zeule
This commit is contained in:
Eugene Shalygin
2021-06-22 10:13:54 +02:00
committed by GitHub
parent 1f7450019f
commit e5a852457f
25 changed files with 1016 additions and 176 deletions
@@ -11,6 +11,7 @@
<StartupObject>LibreHardwareMonitor.Program</StartupObject>
<UseWindowsForms>true</UseWindowsForms>
<OutputPath>..\bin\$(Configuration)\</OutputPath>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugType>full</DebugType>
Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B

+22
View File
@@ -8,6 +8,7 @@ using LibreHardwareMonitor.Hardware;
using LibreHardwareMonitor.Utilities;
using System;
using System.Collections.Generic;
using System.Text;
namespace LibreHardwareMonitor.UI
{
@@ -48,6 +49,27 @@ namespace LibreHardwareMonitor.UI
set { Hardware.Name = value; }
}
public override string ToolTip
{
get
{
IDictionary<string, string> properties = Hardware.Properties;
if (properties.Count > 0)
{
StringBuilder stringBuilder = new();
stringBuilder.AppendLine("Hardware properties:");
foreach (KeyValuePair<string, string> property in properties)
stringBuilder.AppendFormat(" • {0}: {1}\n", property.Key, property.Value);
return stringBuilder.ToString();
}
return null;
}
}
public IHardware Hardware { get; }
public bool Expanded
@@ -54,6 +54,9 @@ namespace LibreHardwareMonitor.UI
case HardwareType.Cooler:
image = Utilities.EmbeddedResources.GetImage("fan.png");
break;
case HardwareType.Psu:
image = Utilities.EmbeddedResources.GetImage("power-supply.png");
break;
default:
image = new Bitmap(1, 1);
break;
+12 -2
View File
@@ -125,6 +125,7 @@ namespace LibreHardwareMonitor.UI
this.timer = new System.Windows.Forms.Timer(this.components);
this.splitContainer = new LibreHardwareMonitor.UI.SplitContainerAdv();
this.treeView = new Aga.Controls.Tree.TreeViewAdv();
this.psuMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.mainMenu.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit();
this.splitContainer.Panel1.SuspendLayout();
@@ -263,7 +264,8 @@ namespace LibreHardwareMonitor.UI
this.gpuMenuItem,
this.fanControllerMenuItem,
this.hddMenuItem,
this.nicMenuItem});
this.nicMenuItem,
this.psuMenuItem});
this.menuItem5.Name = "menuItem5";
this.menuItem5.Size = new System.Drawing.Size(145, 22);
this.menuItem5.Text = "Hardware";
@@ -844,12 +846,19 @@ namespace LibreHardwareMonitor.UI
this.treeView.MouseMove += new System.Windows.Forms.MouseEventHandler(this.TreeView_MouseMove);
this.treeView.MouseUp += new System.Windows.Forms.MouseEventHandler(this.TreeView_MouseUp);
//
// psuMenuItem
//
this.psuMenuItem.Name = "psuMenuItem";
this.psuMenuItem.Size = new System.Drawing.Size(180, 22);
this.psuMenuItem.Text = "Power supplies";
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(418, 533);
this.Controls.Add(this.splitContainer);
this.Controls.Add(this.mainMenu);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MainMenuStrip = this.mainMenu;
this.Name = "MainForm";
@@ -860,11 +869,11 @@ namespace LibreHardwareMonitor.UI
this.Move += new System.EventHandler(this.MainForm_MoveOrResize);
this.mainMenu.ResumeLayout(false);
this.mainMenu.PerformLayout();
this.Controls.Add(mainMenu);
this.splitContainer.Panel1.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit();
this.splitContainer.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
@@ -959,6 +968,7 @@ namespace LibreHardwareMonitor.UI
private ToolStripRadioButtonMenuItem timeWindow12hMenuItem;
private ToolStripRadioButtonMenuItem timeWindow24hMenuItem;
private System.Windows.Forms.ToolStripMenuItem authWebServerMenuItem;
private System.Windows.Forms.ToolStripMenuItem psuMenuItem;
}
}
+11
View File
@@ -49,6 +49,7 @@ namespace LibreHardwareMonitor.UI
private readonly UserOption _readFanControllersSensors;
private readonly UserOption _readHddSensors;
private readonly UserOption _readNicSensors;
private readonly UserOption _readPsuSensors;
private readonly UserOption _showGadget;
private UserRadioGroup _plotLocation;
@@ -137,6 +138,10 @@ namespace LibreHardwareMonitor.UI
_wmiProvider = new WmiProvider(_computer);
}
treeView.ShowNodeToolTips = true;
NodeToolTipProvider tooltipProvider = new();
nodeTextBoxText.ToolTipProvider = tooltipProvider;
nodeTextBoxValue.ToolTipProvider = tooltipProvider;
_logger = new Logger(_computer);
_plotColorPalette = new Color[13];
@@ -250,6 +255,12 @@ namespace LibreHardwareMonitor.UI
_computer.IsNetworkEnabled = _readNicSensors.Value;
};
_readPsuSensors = new UserOption("psuMenuItem", true, psuMenuItem, _settings);
_readPsuSensors.Changed += delegate
{
_computer.IsPsuEnabled = _readPsuSensors.Value;
};
_showGadget = new UserOption("gadgetMenuItem", false, gadgetMenuItem, _settings);
_showGadget.Changed += delegate
{
+2
View File
@@ -72,6 +72,8 @@ namespace LibreHardwareMonitor.UI
}
}
public virtual string ToolTip { get; }
public Image Image { get; set; }
public virtual bool IsVisible
@@ -0,0 +1,15 @@
// 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) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using Aga.Controls.Tree;
using Aga.Controls.Tree.NodeControls;
namespace LibreHardwareMonitor.UI
{
internal class NodeToolTipProvider : IToolTipProvider
{
public string GetToolTip(TreeNodeAdv node, NodeControl nodeControl) => (node.Tag as Node)?.ToolTip;
}
}
+7
View File
@@ -640,6 +640,9 @@ namespace LibreHardwareMonitor.UI
case SensorType.Factor:
format = "{0:F3}";
break;
case SensorType.TimeSpan:
format = "{0:g}";
break;
}
if (sensor.SensorType == SensorType.Temperature && _unitManager.TemperatureUnit == TemperatureUnit.Fahrenheit)
@@ -687,6 +690,10 @@ namespace LibreHardwareMonitor.UI
}
formatted = result;
}
else if (sensor.SensorType == SensorType.TimeSpan)
{
formatted = string.Format(format, TimeSpan.FromSeconds(sensor.Value.Value));
}
else
{
formatted = string.Format(format, sensor.Value);
+165 -92
View File
@@ -6,6 +6,8 @@
using System;
using System.Drawing;
using System.Globalization;
using System.Text;
using LibreHardwareMonitor.Hardware;
using LibreHardwareMonitor.Utilities;
@@ -15,10 +17,157 @@ namespace LibreHardwareMonitor.UI
{
private readonly PersistentSettings _settings;
private readonly UnitManager _unitManager;
private bool _plot;
private Color? _penColor;
private bool _plot;
public SensorNode(ISensor sensor, PersistentSettings settings, UnitManager unitManager)
{
Sensor = sensor;
_settings = settings;
_unitManager = unitManager;
switch (sensor.SensorType)
{
case SensorType.Voltage:
Format = "{0:F3} V";
break;
case SensorType.Current:
Format = "{0:F3} A";
break;
case SensorType.Clock:
Format = "{0:F1} MHz";
break;
case SensorType.Load:
Format = "{0:F1} %";
break;
case SensorType.Temperature:
Format = "{0:F1} °C";
break;
case SensorType.Fan:
Format = "{0:F0} RPM";
break;
case SensorType.Flow:
Format = "{0:F1} L/h";
break;
case SensorType.Control:
Format = "{0:F1} %";
break;
case SensorType.Level:
Format = "{0:F1} %";
break;
case SensorType.Power:
Format = "{0:F1} W";
break;
case SensorType.Data:
Format = "{0:F1} GB";
break;
case SensorType.SmallData:
Format = "{0:F1} MB";
break;
case SensorType.Factor:
Format = "{0:F3}";
break;
case SensorType.Frequency:
Format = "{0:F1} Hz";
break;
case SensorType.Throughput:
Format = "{0:F1} B/s";
break;
case SensorType.TimeSpan:
Format = "{0:g}";
break;
}
bool hidden = settings.GetValue(new Identifier(sensor.Identifier, "hidden").ToString(), sensor.IsDefaultHidden);
base.IsVisible = !hidden;
Plot = settings.GetValue(new Identifier(sensor.Identifier, "plot").ToString(), false);
string id = new Identifier(sensor.Identifier, "penColor").ToString();
if (settings.Contains(id))
PenColor = settings.GetValue(id, Color.Black);
}
public event EventHandler PlotSelectionChanged;
public string Format { get; set; } = "";
public override bool IsVisible
{
get { return base.IsVisible; }
set
{
base.IsVisible = value;
_settings.SetValue(new Identifier(Sensor.Identifier, "hidden").ToString(), !value);
}
}
public string Max
{
get { return ValueToString(Sensor.Max); }
}
public string Min
{
get { return ValueToString(Sensor.Min); }
}
public Color? PenColor
{
get { return _penColor; }
set
{
_penColor = value;
string id = new Identifier(Sensor.Identifier, "penColor").ToString();
if (value.HasValue)
_settings.SetValue(id, value.Value);
else
_settings.Remove(id);
PlotSelectionChanged?.Invoke(this, null);
}
}
public bool Plot
{
get { return _plot; }
set
{
_plot = value;
_settings.SetValue(new Identifier(Sensor.Identifier, "plot").ToString(), value);
PlotSelectionChanged?.Invoke(this, null);
}
}
public ISensor Sensor { get; }
public override string Text
{
get { return Sensor.Name; }
set { Sensor.Name = value; }
}
public override string ToolTip
{
get
{
StringBuilder stringBuilder = new();
if (Sensor is ICriticalSensorLimits criticalSensorLimits)
OptionallyAppendCriticalRange(stringBuilder, criticalSensorLimits.CriticalLowLimit, criticalSensorLimits.CriticalHighLimit, "critical");
if (Sensor is ISensorLimits sensorLimits)
OptionallyAppendCriticalRange(stringBuilder, sensorLimits.LowLimit, sensorLimits.HighLimit, "normal");
return stringBuilder.ToString();
}
}
public string Value
{
get { return ValueToString(Sensor.Value); }
}
public string ValueToString(float? value)
{
if (value.HasValue)
@@ -59,7 +208,8 @@ namespace LibreHardwareMonitor.UI
else
result = $"{value / 1073741824:F1} Gbps";
}
break;
break;
}
break;
@@ -73,8 +223,13 @@ namespace LibreHardwareMonitor.UI
break;
}
}
return result;
}
case SensorType.TimeSpan:
{
return value.HasValue ? string.Format(Format, TimeSpan.FromSeconds(value.Value)) : "-";
}
default:
{
return string.Format(Format, value);
@@ -85,103 +240,20 @@ namespace LibreHardwareMonitor.UI
return "-";
}
public SensorNode(ISensor sensor, PersistentSettings settings, UnitManager unitManager)
private void OptionallyAppendCriticalRange(StringBuilder str, float? min, float? max, string kind)
{
Sensor = sensor;
_settings = settings;
_unitManager = unitManager;
switch (sensor.SensorType)
if (min.HasValue)
{
case SensorType.Voltage: Format = "{0:F3} V"; break;
case SensorType.Current: Format = "{0:F3} A"; break;
case SensorType.Clock: Format = "{0:F1} MHz"; break;
case SensorType.Load: Format = "{0:F1} %"; break;
case SensorType.Temperature: Format = "{0:F1} °C"; break;
case SensorType.Fan: Format = "{0:F0} RPM"; break;
case SensorType.Flow: Format = "{0:F1} L/h"; break;
case SensorType.Control: Format = "{0:F1} %"; break;
case SensorType.Level: Format = "{0:F1} %"; break;
case SensorType.Power: Format = "{0:F1} W"; break;
case SensorType.Data: Format = "{0:F1} GB"; break;
case SensorType.SmallData: Format = "{0:F1} MB"; break;
case SensorType.Factor: Format = "{0:F3}"; break;
case SensorType.Frequency: Format = "{0:F1} Hz"; break;
case SensorType.Throughput: Format = "{0:F1} B/s"; break;
str.AppendLine(max.HasValue
? $"{CultureInfo.CurrentUICulture.TextInfo.ToTitleCase(kind)} range: {ValueToString(min)} to {ValueToString(max)}."
: $"Minimal {kind} value: {ValueToString(min)}.");
}
bool hidden = settings.GetValue(new Identifier(sensor.Identifier, "hidden").ToString(), sensor.IsDefaultHidden);
base.IsVisible = !hidden;
Plot = settings.GetValue(new Identifier(sensor.Identifier, "plot").ToString(), false);
string id = new Identifier(sensor.Identifier, "penColor").ToString();
if (settings.Contains(id))
PenColor = settings.GetValue(id, Color.Black);
}
public override string Text
{
get { return Sensor.Name; }
set { Sensor.Name = value; }
}
public override bool IsVisible
{
get { return base.IsVisible; }
set
else if (max.HasValue)
{
base.IsVisible = value;
_settings.SetValue(new Identifier(Sensor.Identifier, "hidden").ToString(), !value);
str.AppendLine($"Maximal {kind} value: {ValueToString(max)}.");
}
}
public Color? PenColor
{
get { return _penColor; }
set
{
_penColor = value;
string id = new Identifier(Sensor.Identifier, "penColor").ToString();
if (value.HasValue)
_settings.SetValue(id, value.Value);
else
_settings.Remove(id);
PlotSelectionChanged?.Invoke(this, null);
}
}
public bool Plot
{
get { return _plot; }
set
{
_plot = value;
_settings.SetValue(new Identifier(Sensor.Identifier, "plot").ToString(), value);
PlotSelectionChanged?.Invoke(this, null);
}
}
public event EventHandler PlotSelectionChanged;
public ISensor Sensor { get; }
public string Value
{
get { return ValueToString(Sensor.Value); }
}
public string Min
{
get { return ValueToString(Sensor.Min); }
}
public string Max
{
get { return ValueToString(Sensor.Max); }
}
public override bool Equals(object obj)
{
if (obj == null)
@@ -190,6 +262,7 @@ namespace LibreHardwareMonitor.UI
if (!(obj is SensorNode s))
return false;
return (Sensor == s.Sensor);
}
+16 -18
View File
@@ -161,32 +161,30 @@ namespace LibreHardwareMonitor.UI
switch (Sensor.SensorType)
{
case SensorType.Voltage:
return $"{Sensor.Value:F1}";
case SensorType.Current:
return $"{Sensor.Value:F1}";
case SensorType.Clock:
return $"{1e-3f * Sensor.Value:F1}";
case SensorType.Load:
return $"{Sensor.Value:F0}";
case SensorType.Temperature:
return _unitManager.TemperatureUnit == TemperatureUnit.Fahrenheit ? $"{UnitManager.CelsiusToFahrenheit(Sensor.Value):F0}" : $"{Sensor.Value:F0}";
case SensorType.TimeSpan:
return $"{TimeSpan.FromSeconds(Sensor.Value.Value):g}";
case SensorType.Clock:
case SensorType.Fan:
return $"{1e-3f * Sensor.Value:F1}";
case SensorType.Flow:
return $"{1e-3f * Sensor.Value:F1}";
case SensorType.Control:
return $"{Sensor.Value:F0}";
case SensorType.Level:
return $"{Sensor.Value:F0}";
case SensorType.Power:
return $"{Sensor.Value:F0}";
case SensorType.Data:
return $"{Sensor.Value:F0}";
case SensorType.Voltage:
case SensorType.Current:
case SensorType.SmallData:
case SensorType.Factor:
case SensorType.Throughput:
return $"{Sensor.Value:F1}";
case SensorType.Control:
case SensorType.Frequency:
case SensorType.Level:
case SensorType.Power:
case SensorType.Data:
case SensorType.Load:
return $"{Sensor.Value:F0}";
default:
return "-";
}
return "-";
}
private Icon CreateTransparentIcon()
+4
View File
@@ -83,6 +83,10 @@ namespace LibreHardwareMonitor.UI
Image = Utilities.EmbeddedResources.GetImage("throughput.png");
Text = "Throughput";
break;
case SensorType.TimeSpan:
Image = Utilities.EmbeddedResources.GetImage("time.png");
Text = "Times";
break;
}
NodeAdded += TypeNode_NodeAdded;
@@ -641,6 +641,8 @@ namespace LibreHardwareMonitor.Utilities
return "fan.png";
case HardwareType.Network:
return "nic.png";
case HardwareType.Psu:
return "power-supply.png";
default:
return "cpu.png";
}
@@ -0,0 +1,40 @@
// 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) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Linq;
namespace LibreHardwareMonitor.Hardware
{
internal class CompositeSensor : Sensor
{
private readonly ISensor[] _components;
private readonly Func<float, ISensor, float> _reducer;
private readonly float _seedValue;
public CompositeSensor
(
string name,
int index,
SensorType sensorType,
Hardware hardware,
ISettings settings,
ISensor[] components,
Func<float, ISensor, float> reducer,
float seedValue = .0f)
: base(name, index, sensorType, hardware, settings)
{
_components = components;
_reducer = reducer;
_seedValue = seedValue;
}
public override float? Value
{
get { return _components.Aggregate(_seedValue, _reducer); }
set => throw new NotImplementedException();
}
}
}
@@ -18,6 +18,7 @@ using LibreHardwareMonitor.Hardware.Gpu;
using LibreHardwareMonitor.Hardware.Memory;
using LibreHardwareMonitor.Hardware.Motherboard;
using LibreHardwareMonitor.Hardware.Network;
using LibreHardwareMonitor.Hardware.Psu.Corsair;
using LibreHardwareMonitor.Hardware.Storage;
namespace LibreHardwareMonitor.Hardware
@@ -42,6 +43,7 @@ namespace LibreHardwareMonitor.Hardware
private bool _open;
private SMBios _smbios;
private bool _storageEnabled;
private bool _psuEnabled;
public Computer()
{
@@ -247,6 +249,30 @@ namespace LibreHardwareMonitor.Hardware
}
}
/// <summary>
/// Gets or sets a value indicating whether collecting information about <see cref="HardwareType.Psu"/> devices should be enabled and updated.
/// </summary>
/// <returns><see langword="true"/> if a given category of devices is already enabled.</returns>
public bool IsPsuEnabled
{
get { return _psuEnabled; }
set
{
if (_open && value != _psuEnabled)
{
if (value)
{
Add(new CorsairPsuGroup(_settings));
}
else
{
RemoveType<CorsairPsuGroup>();
}
}
_psuEnabled = value;
}
}
/// <summary>
/// Generates full LibreHardwareMonitor report for devices that have been enabled.
/// </summary>
@@ -490,6 +516,9 @@ namespace LibreHardwareMonitor.Hardware
if (_networkEnabled)
Add(new NetworkGroup(_settings));
if (_psuEnabled)
Add(new CorsairPsuGroup(_settings));
}
private static void NewSection(TextWriter writer)
+5 -3
View File
@@ -12,7 +12,7 @@ namespace LibreHardwareMonitor.Hardware
{
public abstract class Hardware : IHardware
{
protected readonly HashSet<ISensor> _active = new HashSet<ISensor>();
protected readonly HashSet<ISensor> _active = new();
protected readonly string _name;
protected readonly ISettings _settings;
private string _customName;
@@ -25,6 +25,8 @@ namespace LibreHardwareMonitor.Hardware
_customName = settings.GetValue(new Identifier(Identifier, "name").ToString(), name);
}
public event HardwareEventHandler Closing;
public abstract HardwareType HardwareType { get; }
public Identifier Identifier { get; }
@@ -45,6 +47,8 @@ namespace LibreHardwareMonitor.Hardware
get { return null; }
}
public virtual IDictionary<string, string> Properties => new SortedDictionary<string, string>();
public virtual ISensor[] Sensors
{
get { return _active.ToArray(); }
@@ -89,8 +93,6 @@ namespace LibreHardwareMonitor.Hardware
SensorRemoved?.Invoke(sensor);
}
public event HardwareEventHandler Closing;
public virtual void Close()
{
Closing?.Invoke(this);
@@ -31,6 +31,8 @@ namespace LibreHardwareMonitor.Hardware
bool IsMemoryEnabled { get; }
bool IsPsuEnabled { get; }
string GetReport();
event HardwareEventHandler HardwareAdded;
@@ -4,6 +4,8 @@
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
namespace LibreHardwareMonitor.Hardware
{
public delegate void SensorEventHandler(ISensor sensor);
@@ -22,7 +24,8 @@ namespace LibreHardwareMonitor.Hardware
Storage,
Network,
Cooler,
EmbeddedController
EmbeddedController,
Psu
}
/// <summary>
@@ -81,5 +84,10 @@ namespace LibreHardwareMonitor.Hardware
///
/// </summary>
event SensorEventHandler SensorRemoved;
/// <summary>
/// Rarely changed hardware properties that can't be represented as sensors.
/// </summary>
IDictionary<string, string> Properties { get; }
}
}
+2 -1
View File
@@ -28,7 +28,8 @@ namespace LibreHardwareMonitor.Hardware
Factor, // 1
Data, // GB = 2^30 Bytes
SmallData, // MB = 2^20 Bytes
Throughput // B/s
Throughput, // B/s
TimeSpan, // Seconds
}
/// <summary>
@@ -0,0 +1,21 @@
// 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) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware
{
public interface ISensorLimits
{
float? HighLimit { get; }
float? LowLimit { get; }
}
public interface ICriticalSensorLimits
{
float? CriticalHighLimit { get; }
float? CriticalLowLimit { get; }
}
}
@@ -85,7 +85,7 @@ namespace LibreHardwareMonitor.Hardware.Motherboard
public Identifier Identifier
{
get { return new("motherboard"); }
get { return new Identifier("motherboard"); }
}
public string Name
@@ -104,6 +104,8 @@ namespace LibreHardwareMonitor.Hardware.Motherboard
get { return null; }
}
public virtual IDictionary<string, string> Properties => new SortedDictionary<string, string>();
public ISensor[] Sensors
{
get { return new ISensor[0]; }
@@ -0,0 +1,512 @@
// 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) 2020 Wilken Gottwalt<wilken.gottwalt@posteo.net>
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
// Implemented after the Linuix kernel driver corsair_psu by Wilken Gottwalt and contributers
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Psu.Corsair
{
internal class SensorIndices
{
private readonly Dictionary<SensorType, int> _lastIndices = new();
public int NextIndex(SensorType type)
{
if (!_lastIndices.ContainsKey(type))
{
_lastIndices.Add(type, 0);
return 0;
}
int res = _lastIndices[type] + 1;
_lastIndices[type] = res;
return res;
}
}
// might need refactoring into two classes if AXi series API differs significantly
internal sealed class CorsairPsu : Hardware
{
private readonly List<CompositeSensor> _compositeSensors = new();
private readonly HidDevice _device;
private readonly List<PsuSensor> _sensors = new();
public CorsairPsu(HidDevice device, ISettings settings, int index)
: base("Corsair PSU", new Identifier("psu", "corsair", index.ToString()), settings)
{
_device = device;
using HidStream stream = device.Open();
UsbApi.Init(stream);
UsbApi.FirmwareInfo fwInfo = UsbApi.FwInfo(stream);
Name = $"{CultureInfo.InvariantCulture.TextInfo.ToTitleCase(fwInfo.Vendor.ToLowerInvariant())} {fwInfo.Product}";
AddSensors(UsbApi.GetOptionalCommands(stream), UsbApi.GetCriticals(stream), settings);
}
public override HardwareType HardwareType => HardwareType.Psu;
public override IDictionary<string, string> Properties
{
get
{
SortedDictionary<string, string> properties = new();
using HidStream stream = _device.Open();
float? mode = UsbApi.GetValue(stream, UsbApi.Command.OCPMODE, 0);
if (mode.HasValue)
properties.Add("Over-current protection", mode > 1.0 ? "multi-rail" : "single-rail");
return properties;
}
}
public override void Update()
{
using HidStream stream = _device.Open();
_sensors.ForEach(s => s.Update(stream));
}
private void AddSensors(UsbApi.OptionalCommands optionalCommands, UsbApi.Criticals criticals, ISettings settings)
{
SensorIndices indices = new();
_sensors.Add(new PsuSensorWithLimits("VRM",
indices,
SensorType.Temperature,
this,
settings,
UsbApi.Command.TEMP0,
null,
criticals.TempMax[0]));
_sensors.Add(new PsuSensorWithLimits("Case",
indices,
SensorType.Temperature,
this,
settings,
UsbApi.Command.TEMP1,
null,
criticals.TempMax[1]));
_sensors.Add(new PsuSensor("Case", indices, SensorType.Fan, this, settings, UsbApi.Command.FAN_RPM));
_sensors.Add(new PsuSensor("Input", indices, SensorType.Voltage, this, settings, UsbApi.Command.IN_VOLTS));
_sensors.Add(new PsuSensorWithLimits("+12V",
indices,
SensorType.Voltage,
this,
settings,
UsbApi.Command.RAIL_VOLTS,
criticals.VoltageMin[(byte)Rail._12V],
criticals.VoltageMax[(byte)Rail._12V]));
_sensors.Add(new PsuSensorWithLimits("+5V",
indices,
SensorType.Voltage,
this,
settings,
UsbApi.Command.RAIL_VOLTS,
criticals.VoltageMin[(byte)Rail._5V],
criticals.VoltageMax[(byte)Rail._5V],
Rail._5V));
_sensors.Add(new PsuSensorWithLimits("+3.3V",
indices,
SensorType.Voltage,
this,
settings,
UsbApi.Command.RAIL_VOLTS,
criticals.VoltageMin[(byte)Rail._3V],
criticals.VoltageMax[(byte)Rail._3V],
Rail._3V));
if (optionalCommands.HasFlag(UsbApi.OptionalCommands.InputCurrent))
{
_sensors.Add(new PsuSensor("Input", indices, SensorType.Current, this, settings, UsbApi.Command.IN_AMPS));
}
_sensors.Add(new PsuSensorWithLimits("+12V",
indices,
SensorType.Current,
this,
settings,
UsbApi.Command.RAIL_AMPS,
null,
criticals.CurrentMax[(byte)Rail._12V]));
_sensors.Add(new PsuSensorWithLimits("+5V",
indices,
SensorType.Current,
this,
settings,
UsbApi.Command.RAIL_AMPS,
null,
criticals.CurrentMax[(byte)Rail._5V],
Rail._5V));
_sensors.Add(new PsuSensorWithLimits("+3.3V",
indices,
SensorType.Current,
this,
settings,
UsbApi.Command.RAIL_AMPS,
null,
criticals.CurrentMax[(byte)Rail._3V],
Rail._3V));
PsuSensor[] powerRails =
{
new("+12V", indices, SensorType.Power, this, settings, UsbApi.Command.RAIL_WATTS),
new("+5V", indices, SensorType.Power, this, settings, UsbApi.Command.RAIL_WATTS, Rail._5V),
new("+3.3V", indices, SensorType.Power, this, settings, UsbApi.Command.RAIL_WATTS, Rail._3V)
};
_sensors.AddRange(powerRails);
_sensors.Add(new PsuSensor("Total watts", indices, SensorType.Power, this, settings, UsbApi.Command.TOTAL_WATTS));
_compositeSensors.Add(new CompositeSensor("Total Output",
indices.NextIndex(SensorType.Power),
SensorType.Power,
this,
settings,
powerRails,
(acc, sensor) => acc + sensor.Value ?? 0f));
ActivateSensor(_compositeSensors[_compositeSensors.Count - 1]);
_sensors.Add(new PsuSensor("Uptime", indices, SensorType.TimeSpan, this, settings, UsbApi.Command.UPTIME, Rail._12V, true));
_sensors.Add(new PsuSensor("Total uptime", indices, SensorType.TimeSpan, this, settings, UsbApi.Command.TOTAL_UPTIME, Rail._12V, true));
}
private class PsuSensor : Sensor
{
private readonly UsbApi.Command _cmd;
private readonly byte _rail;
public PsuSensor(string name, SensorIndices indices, SensorType type, CorsairPsu hardware, ISettings settings, UsbApi.Command cmd, Rail rail = Rail._12V, bool noHistory = false)
: base(name, indices.NextIndex(type), false, type, hardware, null, settings, noHistory)
{
_cmd = cmd;
_rail = (byte)rail;
hardware.ActivateSensor(this);
}
public void Update(HidStream stream)
{
Value = UsbApi.GetValue(stream, _cmd, _rail);
}
}
private class PsuSensorWithLimits : PsuSensor, ICriticalSensorLimits
{
public PsuSensorWithLimits
(
string name,
SensorIndices indices,
SensorType type,
CorsairPsu hardware,
ISettings settings,
UsbApi.Command cmd,
float? lowCritical,
float? highCritical,
Rail rail = Rail._12V)
: base(name, indices, type, hardware, settings, cmd, rail)
{
CriticalLowLimit = lowCritical;
CriticalHighLimit = highCritical;
}
public float? CriticalHighLimit { get; }
public float? CriticalLowLimit { get; }
}
private enum Rail : byte
{
_12V = 0,
_5V = 1,
_3V = 2
}
}
#region Exception classes
public class CommunicationProtocolError : ApplicationException
{
public CommunicationProtocolError(HidDevice device, string message)
: base($"Error communicating with the PSU controller at {device.DevicePath}: {message}")
{ }
}
#endregion
#region PSU USB communication protocol implementation
internal static class UsbApi
{
/* some values are SMBus LINEAR11 data which need a conversion */
#if false
static int Linear11ToInt(ushort val, int scale)
{
int exp = ((short)val) >> 11;
int mant = (((short)(val & 0x7ff)) << 5) >> 5;
int result = mant * scale;
return (exp >= 0) ? (result << exp) : (result >> -exp);
}
#endif
static float Linear11ToFloat32(ushort val)
{
int exp = ((short)val) >> 11;
int mant = (((short)(val & 0x7ff)) << 5) >> 5;
return mant * (float)Math.Pow(2, exp);
}
static bool SendCommand(HidStream stream, byte length, Command cmd, byte arg, out byte[] replyData)
{
/*
* Corsair protocol for PSUs
*
* message size = 64 bytes (request and response, little endian)
* request:
* [length][command][param0][param1][paramX]...
* reply:
* [echo of length][echo of command][data0][data1][dataX]...
*
* - commands are byte sized opcodes
* - length is the sum of all bytes of the commands/params
* - the micro-controller of most of these PSUs support concatenation in the request and reply,
* but it is better to not rely on this (it is also hard to parse)
* - the driver uses raw events to be accessible from userspace (though this is not really
* supported, it is just there for convenience, may be removed in the future)
* - a reply always start with the length and command in the same order the request used it
* - length of the reply data is specific to the command used
* - some of the commands work on a rail and can be switched to a specific rail (0 = 12v,
* 1 = 5v, 2 = 3.3v)
* - the format of the init command 0xFE is swapped length/command bytes
* - parameter bytes amount and values are specific to the command (rail setting is the only
* for now that uses non-zero values)
* - there are much more commands, especially for configuring the device, but they are not
* supported because a wrong command/length can lockup the micro-controller
* - the driver supports debugfs for values not fitting into the hwmon class
* - not every device class (HXi, RMi or AXi) supports all commands
* - it is a pure sensors reading driver (will not support configuring)
*/
const int cmdBufferSize = 64;
const int replySize = 16;
byte[] cmdBuffer = new byte[cmdBufferSize + 1];
cmdBuffer[0] = 0; // report id
cmdBuffer[1] = length;
cmdBuffer[2] = (byte)cmd;
cmdBuffer[3] = arg;
stream.Write(cmdBuffer);
byte[] reply = stream.Read();
replyData = new byte[replySize];
Array.Copy(reply, 3, replyData, 0, replySize);
return reply[1] == cmdBuffer[1] && reply[2] == cmdBuffer[2];
}
public static void Init(HidStream stream)
{
/*
* PSU_CMD_INIT uses swapped length/command and expects 2 parameter bytes, this command
* actually generates a reply, but we don't need it
*/
SendCommand(stream, (byte)Command.INIT, (Command)3, 0, out _);
}
public struct FirmwareInfo
{
public string Vendor;
public string Product;
}
public static FirmwareInfo FwInfo(HidStream stream)
{
if (!SendCommand(stream, 3, Command.VEND_STR, 0, out byte[] vendorArr))
throw new CommunicationProtocolError(stream.Device, "Can't read vendor string");
if (!SendCommand(stream, 3, Command.PROD_STR, 0, out byte[] productArr))
throw new CommunicationProtocolError(stream.Device, "Can't read product");
string ArrayToString(byte[] ar)
{
int i = 0;
while (ar[i] != 0)
i++;
byte[] trimmed = new byte[i];
for (int j = 0; j < i; j++)
trimmed[j] = ar[j];
return Encoding.ASCII.GetString(trimmed);
}
return new FirmwareInfo { Vendor = ArrayToString(vendorArr), Product = ArrayToString(productArr) };
}
static bool Request(HidStream stream, Command cmd, byte rail, out byte[] data)
{
//mutex_lock(&priv->lock) ;
switch (cmd)
{
case Command.RAIL_VOLTS_HCRIT:
case Command.RAIL_VOLTS_LCRIT:
case Command.RAIL_AMPS_HCRIT:
case Command.RAIL_VOLTS:
case Command.RAIL_AMPS:
case Command.RAIL_WATTS:
{
if (!SendCommand(stream, 2, Command.SELECT_RAIL, rail, out _))
{
data = null;
return false;
}
break;
}
}
return SendCommand(stream, 3, cmd, 0, out data);
// mutex_unlock(&priv->lock) ;
// return ret;
}
public static float? GetValue(HidStream stream, Command cmd, byte rail)
{
if (!Request(stream, cmd, rail, out byte[] data))
{
return null;
}
/*
* the biggest value here comes from the uptime command and to exceed MAXINT total uptime
* needs to be about 68 years, the rest are u16 values and the biggest value coming out of
* the LINEAR11 conversion are the watts values which are about 1200 for the strongest psu
* supported (HX1200i)
*/
int tmp = BitConverter.ToInt32(data, 0); // ((int)data[3] << 24) + (data[2] << 16) + (data[1] << 8) + data[0];
switch (cmd)
{
case Command.RAIL_VOLTS_HCRIT:
case Command.RAIL_VOLTS_LCRIT:
case Command.RAIL_AMPS_HCRIT:
case Command.TEMP_HCRIT:
case Command.IN_VOLTS:
case Command.IN_AMPS:
case Command.RAIL_VOLTS:
case Command.RAIL_AMPS:
case Command.TEMP0:
case Command.TEMP1:
case Command.FAN_RPM:
case Command.RAIL_WATTS:
case Command.TOTAL_WATTS:
return Linear11ToFloat32((ushort)tmp); // Linear11ToInt((ushort)tmp, 1000000);
case Command.TOTAL_UPTIME:
case Command.UPTIME:
case Command.OCPMODE:
return tmp;
}
return null;
}
public struct Criticals
{
public float?[] TempMax;
public float?[] VoltageMin;
public float?[] VoltageMax;
public float?[] CurrentMax;
}
public static Criticals GetCriticals(HidStream stream)
{
Criticals res = new();
const byte tempCount = 2;
res.TempMax = new float?[tempCount];
for (byte rail = 0; rail < tempCount; rail++)
{
res.TempMax[rail] = GetValue(stream, Command.TEMP_HCRIT, rail);
}
const byte railCount = 3; /* 3v + 5v + 12v */
res.VoltageMin = new float?[railCount];
res.VoltageMax = new float?[railCount];
res.CurrentMax = new float?[railCount];
for (byte rail = 0; rail < railCount; rail++)
{
res.VoltageMax[rail] = GetValue(stream, Command.RAIL_VOLTS_HCRIT, rail);
res.VoltageMin[rail] = GetValue(stream, Command.RAIL_VOLTS_LCRIT, rail);
res.CurrentMax[rail] = GetValue(stream, Command.RAIL_AMPS_HCRIT, rail);
}
return res;
}
[Flags]
public enum OptionalCommands
{
None = 0x0,
InputCurrent = 0x1
}
public static OptionalCommands GetOptionalCommands(HidStream stream)
{
OptionalCommands res = OptionalCommands.None;
if (GetValue(stream, Command.IN_AMPS, 0).HasValue)
{
res |= OptionalCommands.InputCurrent;
}
return res;
}
public enum Command : byte
{
SELECT_RAIL = 0x00, /* expects length 2 */
RAIL_VOLTS_HCRIT = 0x40, /* the rest of the commands expect length 3 */
RAIL_VOLTS_LCRIT = 0x44,
RAIL_AMPS_HCRIT = 0x46,
TEMP_HCRIT = 0x4F,
IN_VOLTS = 0x88,
IN_AMPS = 0x89,
RAIL_VOLTS = 0x8B,
RAIL_AMPS = 0x8C,
TEMP0 = 0x8D,
TEMP1 = 0x8E,
FAN_RPM = 0x90,
RAIL_WATTS = 0x96,
VEND_STR = 0x99,
PROD_STR = 0x9A,
TOTAL_UPTIME = 0xD1,
UPTIME = 0xD2,
OCPMODE = 0xD8,
TOTAL_WATTS = 0xEE,
INIT = 0xFE
}
}
#endregion
}
@@ -0,0 +1,71 @@
// This Source Code Form is subject to the terms of the GNU Public License, v. 2.0.
// Copyright(C) 2020 Wilken Gottwalt<wilken.gottwalt@posteo.net>
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
// Implemented after the Linuix kernel driver corsair_psu by Wilken Gottwalt and contributers
using System.Collections.Generic;
using System.Linq;
using System.Text;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Psu.Corsair
{
public class CorsairPsuGroup : IGroup
{
private static readonly int[] _productIds =
{
0x1c03, // HX550i
0x1c04, // HX650i
0x1c05, // HX750i
0x1c06, // HX850i
0x1c07, // HX1000i
0x1c08, // HX1200i
0x1c09, // RM550i
0x1c0a, // RM650i
0x1c0b, // RM750i
0x1c0c, // RM850i
0x1c0d, // RM1000i
// 0x1c11, // AX1600i
};
private static readonly ushort _vendorId = 0x1b1c;
private readonly List<IHardware> _hardware;
private readonly StringBuilder _report;
public CorsairPsuGroup(ISettings settings)
{
_report = new StringBuilder();
_report.AppendLine("Corsair HXi/RMi series PSU Hardware");
_report.AppendLine();
_hardware = new List<IHardware>();
foreach (HidDevice dev in DeviceList.Local.GetHidDevices(_vendorId))
{
if (_productIds.Contains(dev.ProductID))
{
var device = new CorsairPsu(dev, settings, _hardware.Count);
_hardware.Add(device);
_report.AppendLine($"Device name: {device.Name}");
_report.AppendLine();
}
}
}
public IReadOnlyList<IHardware> Hardware => _hardware;
public void Close()
{
foreach (IHardware iHardware in _hardware)
{
if (iHardware is Hardware hardware)
hardware.Close();
}
}
public string GetReport()
{
return _report.ToString();
}
}
}
+62 -58
View File
@@ -17,7 +17,8 @@ namespace LibreHardwareMonitor.Hardware
private readonly string _defaultName;
private readonly Hardware _hardware;
private readonly ISettings _settings;
private readonly List<SensorValue> _values = new List<SensorValue>();
private readonly bool _trackMinMax;
private readonly List<SensorValue> _values = new();
private int _count;
private float? _currentValue;
private string _name;
@@ -32,7 +33,16 @@ namespace LibreHardwareMonitor.Hardware
this(name, index, false, sensorType, hardware, parameterDescriptions, settings)
{ }
public Sensor(string name, int index, bool defaultHidden, SensorType sensorType, Hardware hardware, ParameterDescription[] parameterDescriptions, ISettings settings)
public Sensor
(
string name,
int index,
bool defaultHidden,
SensorType sensorType,
Hardware hardware,
ParameterDescription[] parameterDescriptions,
ISettings settings,
bool disableHistory = false)
{
Index = index;
IsDefaultHidden = defaultHidden;
@@ -51,6 +61,11 @@ namespace LibreHardwareMonitor.Hardware
_settings = settings;
_defaultName = name;
_name = settings.GetValue(new Identifier(Identifier, "name").ToString(), name);
_trackMinMax = !disableHistory;
if (disableHistory)
{
_valuesTimeWindow = TimeSpan.Zero;
}
GetSensorValuesFromSettings();
@@ -92,7 +107,7 @@ namespace LibreHardwareMonitor.Hardware
public SensorType SensorType { get; }
public float? Value
public virtual float? Value
{
get { return _currentValue; }
set
@@ -117,11 +132,14 @@ namespace LibreHardwareMonitor.Hardware
}
_currentValue = value;
if (Min > value || !Min.HasValue)
Min = value;
if (_trackMinMax)
{
if (!Min.HasValue || Min > value)
Min = value;
if (Max < value || !Max.HasValue)
Max = value;
if (!Max.HasValue || Max < value)
Max = value;
}
}
}
@@ -168,31 +186,24 @@ namespace LibreHardwareMonitor.Hardware
private void SetSensorValuesToSettings()
{
using (MemoryStream memoryStream = new MemoryStream())
using MemoryStream memoryStream = new();
using GZipStream gZipStream = new(memoryStream, CompressionMode.Compress);
using BufferedStream outputStream = new(gZipStream, 65536);
using BinaryWriter binaryWriter = new(outputStream);
long t = 0;
foreach (SensorValue sensorValue in _values)
{
using (GZipStream gZipStream = new GZipStream(memoryStream, CompressionMode.Compress))
{
using (BufferedStream outputStream = new BufferedStream(gZipStream, 65536))
{
using (BinaryWriter binaryWriter = new BinaryWriter(outputStream))
{
long t = 0;
foreach (SensorValue sensorValue in _values)
{
long v = sensorValue.Time.ToBinary();
binaryWriter.Write(v - t);
t = v;
binaryWriter.Write(sensorValue.Value);
}
binaryWriter.Flush();
}
}
}
_settings.SetValue(new Identifier(Identifier, "values").ToString(), Convert.ToBase64String(memoryStream.ToArray()));
long v = sensorValue.Time.ToBinary();
binaryWriter.Write(v - t);
t = v;
binaryWriter.Write(sensorValue.Value);
}
binaryWriter.Flush();
_settings.SetValue(new Identifier(Identifier, "values").ToString(), Convert.ToBase64String(memoryStream.ToArray()));
}
private void GetSensorValuesFromSettings()
@@ -207,40 +218,33 @@ namespace LibreHardwareMonitor.Hardware
byte[] array = Convert.FromBase64String(s);
DateTime now = DateTime.UtcNow;
using (MemoryStream memoryStream = new MemoryStream(array))
using MemoryStream memoryStream = new(array);
using GZipStream gZipStream = new(memoryStream, CompressionMode.Decompress);
using MemoryStream destination = new();
gZipStream.CopyTo(destination);
destination.Seek(0, SeekOrigin.Begin);
using BinaryReader reader = new(destination);
try
{
using (GZipStream gZipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
long t = 0;
long readLen = reader.BaseStream.Length - reader.BaseStream.Position;
while (readLen > 0)
{
using (MemoryStream destination = new MemoryStream())
{
gZipStream.CopyTo(destination);
destination.Seek(0, SeekOrigin.Begin);
using (BinaryReader reader = new BinaryReader(destination))
{
try
{
long t = 0;
long readLen = reader.BaseStream.Length - reader.BaseStream.Position;
while (readLen > 0)
{
t += reader.ReadInt64();
DateTime time = DateTime.FromBinary(t);
if (time > now)
break;
t += reader.ReadInt64();
DateTime time = DateTime.FromBinary(t);
if (time > now)
break;
float value = reader.ReadSingle();
AppendValue(value, time);
readLen = reader.BaseStream.Length - reader.BaseStream.Position;
}
}
catch (EndOfStreamException)
{ }
}
}
float value = reader.ReadSingle();
AppendValue(value, time);
readLen = reader.BaseStream.Length - reader.BaseStream.Position;
}
}
catch (EndOfStreamException)
{ }
}
catch
{