@@ -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 |
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user