From 9ad730d8110b2a57d5f60079c5b062adb7c3531e Mon Sep 17 00:00:00 2001 From: PhyxionNL <7643972+PhyxionNL@users.noreply.github.com> Date: Thu, 25 Sep 2025 18:54:08 +0200 Subject: [PATCH] Clean up CorsairPsu --- .../Hardware/Psu/Corsair/CorsairPsu.cs | 351 ++---------------- .../Hardware/Psu/Corsair/UsbApi.cs | 218 +++++++++++ .../Hardware/Psu/ProtocolError.cs | 11 + 3 files changed, 270 insertions(+), 310 deletions(-) create mode 100644 LibreHardwareMonitorLib/Hardware/Psu/Corsair/UsbApi.cs create mode 100644 LibreHardwareMonitorLib/Hardware/Psu/ProtocolError.cs diff --git a/LibreHardwareMonitorLib/Hardware/Psu/Corsair/CorsairPsu.cs b/LibreHardwareMonitorLib/Hardware/Psu/Corsair/CorsairPsu.cs index 2456ac1..a89ff8e 100644 --- a/LibreHardwareMonitorLib/Hardware/Psu/Corsair/CorsairPsu.cs +++ b/LibreHardwareMonitorLib/Hardware/Psu/Corsair/CorsairPsu.cs @@ -3,41 +3,19 @@ // Copyright (C) 2020 Wilken Gottwalt // Copyright (C) LibreHardwareMonitor and Contributors. // All Rights Reserved. -// Implemented after the Linuix kernel driver corsair_psu by Wilken Gottwalt and contributers +// Implemented after the Linux kernel driver corsair_psu by Wilken Gottwalt and contributers -using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; -using System.Text; using HidSharp; namespace LibreHardwareMonitor.Hardware.Psu.Corsair; -internal class SensorIndices -{ - private readonly Dictionary _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 _compositeSensors = new(); + private readonly List _compositeSensors = []; private readonly HidDevice _device; - private readonly List _sensors = new(); + private readonly List _sensors = []; public CorsairPsu(HidDevice device, ISettings settings, int index) : base("Corsair PSU", new Identifier("psu", "corsair", index.ToString()), settings) @@ -78,9 +56,10 @@ internal sealed class CorsairPsu : Hardware private void AddSensors(UsbApi.OptionalCommands optionalCommands, UsbApi.Criticals criticals, ISettings settings) { - SensorIndices indices = new(); + int sensorIndex = 0; + _sensors.Add(new PsuSensorWithLimits("VRM", - indices, + sensorIndex++, SensorType.Temperature, this, settings, @@ -89,7 +68,7 @@ internal sealed class CorsairPsu : Hardware criticals.TempMax[0])); _sensors.Add(new PsuSensorWithLimits("Case", - indices, + sensorIndex++, SensorType.Temperature, this, settings, @@ -97,11 +76,11 @@ internal sealed class CorsairPsu : Hardware null, criticals.TempMax[1])); - _sensors.Add(new PsuSensor("Case", indices, SensorType.Fan, this, settings, UsbApi.Command.FAN_RPM)); + _sensors.Add(new PsuSensor("Case", sensorIndex++, 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 PsuSensor("Input", sensorIndex++, SensorType.Voltage, this, settings, UsbApi.Command.IN_VOLTS)); _sensors.Add(new PsuSensorWithLimits("+12V", - indices, + sensorIndex++, SensorType.Voltage, this, settings, @@ -110,7 +89,7 @@ internal sealed class CorsairPsu : Hardware criticals.VoltageMax[(byte)Rail._12V])); _sensors.Add(new PsuSensorWithLimits("+5V", - indices, + sensorIndex++, SensorType.Voltage, this, settings, @@ -120,7 +99,7 @@ internal sealed class CorsairPsu : Hardware Rail._5V)); _sensors.Add(new PsuSensorWithLimits("+3.3V", - indices, + sensorIndex++, SensorType.Voltage, this, settings, @@ -131,11 +110,11 @@ internal sealed class CorsairPsu : Hardware if (optionalCommands.HasFlag(UsbApi.OptionalCommands.InputCurrent)) { - _sensors.Add(new PsuSensor("Input", indices, SensorType.Current, this, settings, UsbApi.Command.IN_AMPS)); + _sensors.Add(new PsuSensor("Input", sensorIndex++, SensorType.Current, this, settings, UsbApi.Command.IN_AMPS)); } _sensors.Add(new PsuSensorWithLimits("+12V", - indices, + sensorIndex++, SensorType.Current, this, settings, @@ -144,7 +123,7 @@ internal sealed class CorsairPsu : Hardware criticals.CurrentMax[(byte)Rail._12V])); _sensors.Add(new PsuSensorWithLimits("+5V", - indices, + sensorIndex++, SensorType.Current, this, settings, @@ -154,7 +133,7 @@ internal sealed class CorsairPsu : Hardware Rail._5V)); _sensors.Add(new PsuSensorWithLimits("+3.3V", - indices, + sensorIndex++, SensorType.Current, this, settings, @@ -164,16 +143,16 @@ internal sealed class CorsairPsu : Hardware 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) - }; + [ + new("+12V", sensorIndex++, SensorType.Power, this, settings, UsbApi.Command.RAIL_WATTS), + new("+5V", sensorIndex++, SensorType.Power, this, settings, UsbApi.Command.RAIL_WATTS, Rail._5V), + new("+3.3V", sensorIndex++, 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)); + _sensors.Add(new PsuSensor("Total watts", sensorIndex++, SensorType.Power, this, settings, UsbApi.Command.TOTAL_WATTS)); _compositeSensors.Add(new CompositeSensor("Total Output", - indices.NextIndex(SensorType.Power), + sensorIndex++, SensorType.Power, this, settings, @@ -182,8 +161,8 @@ internal sealed class CorsairPsu : Hardware 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)); + _sensors.Add(new PsuSensor("Uptime", sensorIndex++, SensorType.TimeSpan, this, settings, UsbApi.Command.UPTIME, Rail._12V, true)); + _sensors.Add(new PsuSensor("Total uptime", sensorIndex, SensorType.TimeSpan, this, settings, UsbApi.Command.TOTAL_UPTIME, Rail._12V, true)); } private class PsuSensor : Sensor @@ -191,8 +170,8 @@ internal sealed class CorsairPsu : Hardware 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) + public PsuSensor(string name, int index, SensorType type, CorsairPsu hardware, ISettings settings, UsbApi.Command cmd, Rail rail = Rail._12V, bool noHistory = false) + : base(name, index, false, type, hardware, null, settings, noHistory) { _cmd = cmd; _rail = (byte)rail; @@ -206,28 +185,22 @@ internal sealed class CorsairPsu : Hardware } } - private class PsuSensorWithLimits : PsuSensor, ICriticalSensorLimits + private class PsuSensorWithLimits + ( + string name, + int index, + SensorType type, + CorsairPsu hardware, + ISettings settings, + UsbApi.Command cmd, + float? lowCritical, + float? highCritical, + Rail rail = Rail._12V) + : PsuSensor(name, index, type, hardware, settings, cmd, rail), 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; } = highCritical; - public float? CriticalHighLimit { get; } - - public float? CriticalLowLimit { get; } + public float? CriticalLowLimit { get; } = lowCritical; } private enum Rail : byte @@ -237,245 +210,3 @@ internal sealed class CorsairPsu : Hardware _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) - { - return Encoding.ASCII.GetString(ar.TakeWhile(x => x != 0).ToArray()); - } - - 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]; - return cmd switch - { - Command.RAIL_VOLTS_HCRIT or Command.RAIL_VOLTS_LCRIT or Command.RAIL_AMPS_HCRIT or Command.TEMP_HCRIT or Command.IN_VOLTS or Command.IN_AMPS or Command.RAIL_VOLTS or Command.RAIL_AMPS - or Command.TEMP0 or Command.TEMP1 or Command.FAN_RPM or Command.RAIL_WATTS or Command.TOTAL_WATTS => Linear11ToFloat32((ushort)tmp), // Linear11ToInt((ushort)tmp, 1000000); - Command.TOTAL_UPTIME or Command.UPTIME or Command.OCPMODE => tmp, - _ => 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 diff --git a/LibreHardwareMonitorLib/Hardware/Psu/Corsair/UsbApi.cs b/LibreHardwareMonitorLib/Hardware/Psu/Corsair/UsbApi.cs new file mode 100644 index 0000000..737debb --- /dev/null +++ b/LibreHardwareMonitorLib/Hardware/Psu/Corsair/UsbApi.cs @@ -0,0 +1,218 @@ +// 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; +using System.Text; +using HidSharp; + +namespace LibreHardwareMonitor.Hardware.Psu.Corsair; + +internal static class UsbApi +{ + 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 + } + + [Flags] + public enum OptionalCommands + { + None = 0x0, + InputCurrent = 0x1 + } + + 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 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]; + return cmd switch + { + Command.RAIL_VOLTS_HCRIT or Command.RAIL_VOLTS_LCRIT or Command.RAIL_AMPS_HCRIT or Command.TEMP_HCRIT or Command.IN_VOLTS or Command.IN_AMPS or Command.RAIL_VOLTS or Command.RAIL_AMPS + or Command.TEMP0 or Command.TEMP1 or Command.FAN_RPM or Command.RAIL_WATTS or Command.TOTAL_WATTS => Linear11ToFloat32((ushort)tmp), // Linear11ToInt((ushort)tmp, 1000000); + Command.TOTAL_UPTIME or Command.UPTIME or Command.OCPMODE => tmp, + _ => null + }; + } + + 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; + } + + public static OptionalCommands GetOptionalCommands(HidStream stream) + { + OptionalCommands res = OptionalCommands.None; + + if (GetValue(stream, Command.IN_AMPS, 0).HasValue) + res |= OptionalCommands.InputCurrent; + + return res; + } + + public static FirmwareInfo FwInfo(HidStream stream) + { + if (!SendCommand(stream, 3, Command.VEND_STR, 0, out byte[] vendorArr)) + throw new ProtocolError(stream.Device, "Can't read vendor string"); + + if (!SendCommand(stream, 3, Command.PROD_STR, 0, out byte[] productArr)) + throw new ProtocolError(stream.Device, "Can't read product"); + + static string ArrayToString(byte[] ar) + { + return Encoding.ASCII.GetString(ar.TakeWhile(x => x != 0).ToArray()); + } + + return new FirmwareInfo { Vendor = ArrayToString(vendorArr), Product = ArrayToString(productArr) }; + } + + private 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]; + } + + private static bool Request(HidStream stream, Command cmd, byte rail, out byte[] data) + { + 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); + } + + private static float Linear11ToFloat32(ushort val) + { + int exp = (short)val >> 11; + int mant = (short)(val & 0x7ff) << 5 >> 5; + return mant * (float)Math.Pow(2, exp); + } + + public struct FirmwareInfo + { + public string Vendor; + public string Product; + } + + public struct Criticals + { + public float?[] TempMax; + + public float?[] VoltageMin; + public float?[] VoltageMax; + + public float?[] CurrentMax; + } +} diff --git a/LibreHardwareMonitorLib/Hardware/Psu/ProtocolError.cs b/LibreHardwareMonitorLib/Hardware/Psu/ProtocolError.cs new file mode 100644 index 0000000..170a256 --- /dev/null +++ b/LibreHardwareMonitorLib/Hardware/Psu/ProtocolError.cs @@ -0,0 +1,11 @@ +using System; +using HidSharp; + +namespace LibreHardwareMonitor.Hardware.Psu; + +/// +/// Represents an error that occurs during communication with a PSU controller over USB. +/// +/// The HID device associated with the communication error. Cannot be null. +/// The error message that describes the nature of the communication failure. +public class ProtocolError(HidDevice device, string message) : ApplicationException($"Error communicating with the PSU controller at {device.DevicePath}: {message}");