diff --git a/LibreHardwareMonitorLib/Hardware/Computer.cs b/LibreHardwareMonitorLib/Hardware/Computer.cs index a3ff091..53a630d 100644 --- a/LibreHardwareMonitorLib/Hardware/Computer.cs +++ b/LibreHardwareMonitorLib/Hardware/Computer.cs @@ -22,6 +22,7 @@ using LibreHardwareMonitor.Hardware.Memory; using LibreHardwareMonitor.Hardware.Motherboard; using LibreHardwareMonitor.Hardware.Network; using LibreHardwareMonitor.Hardware.Psu.Corsair; +using LibreHardwareMonitor.Hardware.Psu.Msi; using LibreHardwareMonitor.Hardware.Storage; namespace LibreHardwareMonitor.Hardware; @@ -250,10 +251,12 @@ public class Computer : IComputer if (value) { Add(new CorsairPsuGroup(_settings)); + Add(new MsiPsuGroup(_settings)); } else { RemoveType(); + RemoveType(); } } @@ -524,7 +527,10 @@ public class Computer : IComputer Add(new NetworkGroup(_settings)); if (_psuEnabled) + { Add(new CorsairPsuGroup(_settings)); + Add(new MsiPsuGroup(_settings)); + } if (_batteryEnabled) Add(new BatteryGroup(_settings)); diff --git a/LibreHardwareMonitorLib/Hardware/Psu/Msi/MsiPsu.cs b/LibreHardwareMonitorLib/Hardware/Psu/Msi/MsiPsu.cs new file mode 100644 index 0000000..f7bae49 --- /dev/null +++ b/LibreHardwareMonitorLib/Hardware/Psu/Msi/MsiPsu.cs @@ -0,0 +1,75 @@ +// 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) 2024 demorfi +// Copyright (C) LibreHardwareMonitor and Contributors. +// All Rights Reserved. + +using System.Collections.Generic; +using System.Globalization; +using HidSharp; + +namespace LibreHardwareMonitor.Hardware.Psu.Msi; + +internal sealed class MsiPsu : Hardware +{ + private readonly HidDevice _device; + private readonly List _sensors = []; + + public MsiPsu(HidDevice device, ISettings settings, int index) + : base("MSI PSU", new Identifier("psu", "msi", index.ToString()), settings) + { + _device = device; + using HidStream stream = device.Open(); + UsbApi.FirmwareInfo fwInfo = UsbApi.FwInfo(stream); + Name = $"{fwInfo.Vendor} {fwInfo.Product}"; + + AddSensors(settings); + } + + public override HardwareType HardwareType => HardwareType.Psu; + + public override void Update() + { + using HidStream stream = _device.Open(); + float[] info = UsbApi.InfoList(stream); + _sensors.ForEach(s => s.Update(info)); + } + + private void AddSensors(ISettings settings) + { + int sensorIndex = 0; + + _sensors.Add(new PsuSensor("Case", sensorIndex++, SensorType.Fan, this, settings, UsbApi.IndexInfo.FAN_RPM)); + _sensors.Add(new PsuSensor("Case", sensorIndex++, SensorType.Temperature, this, settings, UsbApi.IndexInfo.TEMP)); + + _sensors.Add(new PsuSensor("+12V", sensorIndex++, SensorType.Voltage, this, settings, UsbApi.IndexInfo.VOLTS_12)); + _sensors.Add(new PsuSensor("+12V", sensorIndex++, SensorType.Current, this, settings, UsbApi.IndexInfo.AMPS_12)); + + _sensors.Add(new PsuSensor("+5V", sensorIndex++, SensorType.Voltage, this, settings, UsbApi.IndexInfo.VOLTS_5)); + _sensors.Add(new PsuSensor("+5V", sensorIndex++, SensorType.Current, this, settings, UsbApi.IndexInfo.AMPS_5)); + + _sensors.Add(new PsuSensor("+3.3V", sensorIndex++, SensorType.Voltage, this, settings, UsbApi.IndexInfo.VOLTS_3V3)); + _sensors.Add(new PsuSensor("+3.3V", sensorIndex++, SensorType.Current, this, settings, UsbApi.IndexInfo.AMPS_3V3)); + + _sensors.Add(new PsuSensor("PSU Efficiency", sensorIndex++, SensorType.Level, this, settings, UsbApi.IndexInfo.EFFICIENCY)); + _sensors.Add(new PsuSensor("PSU Out", sensorIndex++, SensorType.Power, this, settings, UsbApi.IndexInfo.PSU_OUT)); + _sensors.Add(new PsuSensor("Total Runtime", sensorIndex++, SensorType.TimeSpan, this, settings, UsbApi.IndexInfo.RUNTIME, true)); + } + + private class PsuSensor : Sensor + { + private readonly UsbApi.IndexInfo _indexInfo; + + public PsuSensor(string name, int index, SensorType type, MsiPsu hardware, ISettings settings, UsbApi.IndexInfo indexInfo, bool noHistory = false) + : base(name, index, false, type, hardware, null, settings, noHistory) + { + _indexInfo = indexInfo; + hardware.ActivateSensor(this); + } + + public void Update(float[] info) + { + Value = info[(int)_indexInfo]; + } + } +} diff --git a/LibreHardwareMonitorLib/Hardware/Psu/Msi/MsiPsuGroup.cs b/LibreHardwareMonitorLib/Hardware/Psu/Msi/MsiPsuGroup.cs new file mode 100644 index 0000000..f442ddc --- /dev/null +++ b/LibreHardwareMonitorLib/Hardware/Psu/Msi/MsiPsuGroup.cs @@ -0,0 +1,64 @@ +// This Source Code Form is subject to the terms of the GNU Public License, v. 2.0. +// Copyright (C) 2024 demorfi +// Copyright (C) LibreHardwareMonitor and Contributors. +// All Rights Reserved. + +using System.Collections.Generic; +using System.Linq; +using System.Text; +using HidSharp; + +namespace LibreHardwareMonitor.Hardware.Psu.Msi; + +public class MsiPsuGroup : IGroup +{ + private static readonly int[] _productIds = + { + 0x56d4, // MEG Ai1300P + }; + + private static readonly ushort _vendorId = 0x0db0; + private readonly List _hardware; + private readonly StringBuilder _report; + + public MsiPsuGroup(ISettings settings) + { + _report = new StringBuilder(); + _report.AppendLine("MSI Ai series PSU Hardware"); + _report.AppendLine(); + + _hardware = new List(); + foreach (HidDevice dev in DeviceList.Local.GetHidDevices(_vendorId)) + { + if (_productIds.Contains(dev.ProductID)) + { + var device = new MsiPsu(dev, settings, _hardware.Count); + _hardware.Add(device); + _report.AppendLine($"Device name: {device.Name}"); + _report.AppendLine(); + } + } + + if (_hardware.Count == 0) + { + _report.AppendLine("No MSI PSU Hardware found."); + _report.AppendLine(); + } + } + + public IReadOnlyList Hardware => _hardware; + + public void Close() + { + foreach (IHardware iHardware in _hardware) + { + if (iHardware is Hardware hardware) + hardware.Close(); + } + } + + public string GetReport() + { + return _report.ToString(); + } +} diff --git a/LibreHardwareMonitorLib/Hardware/Psu/Msi/UsbApi.cs b/LibreHardwareMonitorLib/Hardware/Psu/Msi/UsbApi.cs new file mode 100644 index 0000000..ac8e29a --- /dev/null +++ b/LibreHardwareMonitorLib/Hardware/Psu/Msi/UsbApi.cs @@ -0,0 +1,103 @@ +// 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) 2024 demorfi +// Copyright (C) LibreHardwareMonitor and Contributors. +// All Rights Reserved. + +using System; +using System.Linq; +using System.Text; +using HidSharp; + +namespace LibreHardwareMonitor.Hardware.Psu.Msi; + +internal static class UsbApi +{ + public enum IndexInfo : int + { + VOLTS_12V1 = 0, + VOLTS_12V2 = 2, + VOLTS_12V3 = 4, + VOLTS_12V4 = 6, + VOLTS_12V5 = 8, + VOLTS_12 = 10, + VOLTS_5 = 12, + VOLTS_3V3 = 14, + AMPS_12V1 = 1, + AMPS_12V2 = 3, + AMPS_12V3 = 5, + AMPS_12V4 = 7, + AMPS_12V5 = 9, + AMPS_12 = 11, + AMPS_5 = 13, + AMPS_3V3 = 15, + + PSU_OUT = 16, + EFFICIENCY = 17, + TEMP = 18, + FAN_RPM = 19, + RUNTIME = 20 + } + + public static FirmwareInfo FwInfo(HidStream stream) + { + if (!Request(stream, new byte[2] { 0xFA, 0x51 }, out byte[] productArr, 1)) + throw new ProtocolError(stream.Device, "Can't read product name"); + + static string ArrayToString(byte[] ar) + { + return Encoding.ASCII.GetString(ar.TakeWhile(x => x != 0).ToArray()); + } + + return new FirmwareInfo { Vendor = "MSI", Product = ArrayToString(productArr) }; + } + + 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); + } + + private static bool Request(HidStream stream, byte[] command, out byte[] response, int offset = 0) + { + byte[] buffer = new byte[64]; + Array.Copy(command, 0, buffer, 1, 2); + stream.Write(buffer); + + byte[] reply = stream.Read(); + response = new byte[42]; + Array.Copy(reply, 2 + offset, response, 0, 42); + + return reply[0] == buffer[0] && reply[1] == buffer[1]; + } + + public static float[] InfoList(HidStream stream) + { + int length = Enum.GetNames(typeof(IndexInfo)).Length; + float[] info = new float[length]; + + if (!Request(stream, new byte[2] { 0x51, 0xE0 }, out byte[] basic)) + throw new ProtocolError(stream.Device, "Can't read basic info"); + + // basic has information only about the first 20 sensors + for (int i = 0; i < 20; i++) + { + byte[] replyData = new byte[4]; + Array.Copy(basic, (i * 2) + 1, replyData, 0, 2); + info[i] = Linear11ToFloat32((ushort)BitConverter.ToInt32(replyData, 0)); + } + + // runtime info + Request(stream, new byte[2] { 0x51, 0xD1 }, out byte[] runtime); + info[(int)IndexInfo.RUNTIME] = BitConverter.ToInt32(runtime, 0) / 100; + + return info; + } + + public struct FirmwareInfo + { + public string Vendor; + public string Product; + } +}