Add support for Thermal Grizzly WireView Pro 2. (#2171)
* Add support for Thermal Grizzly WireView Pro 2. * Process PR comments. * PR comments. * Add private keyword. * PR comments. * Replace WMI with SetupAPI. Some first updates according to Thermal Grizzly code. * Add report. * Further improve implementation according to code of Thermal Grizzly (thanks !). * No connection check for welcome message. * Rename class + change SerialPort to SharedSerialPort for guard - fixing mutex issues. * PR comments. * Remove SP_DEVINFO_DATA from NativeMethods.txt * Update WireViewPro2.cs * Rename hasMutex to _hasMutex for consistency * Formatting * Fix build --------- Co-authored-by: PhyxionNL <7643972+PhyxionNL@users.noreply.github.com>
This commit is contained in:
@@ -3,10 +3,15 @@
|
||||
// Copyright (C) LibreHardwareMonitor and Contributors.
|
||||
// All Rights Reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using LibreHardwareMonitor.Hardware.Gpu.PowerMonitor;
|
||||
|
||||
namespace LibreHardwareMonitor.Hardware.Gpu;
|
||||
|
||||
public abstract class GenericGpu : Hardware
|
||||
{
|
||||
readonly List<Hardware> _subHardware = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GenericGpu" /> class.
|
||||
/// </summary>
|
||||
@@ -14,10 +19,30 @@ public abstract class GenericGpu : Hardware
|
||||
/// <param name="identifier">Identifier that will be assigned to the device. Based on <see cref="Identifier" /></param>
|
||||
/// <param name="settings">Additional settings passed by the <see cref="IComputer" />.</param>
|
||||
protected GenericGpu(string name, Identifier identifier, ISettings settings) : base(name, identifier, settings)
|
||||
{ }
|
||||
{
|
||||
TryAddSubHardware();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the device identifier.
|
||||
/// </summary>
|
||||
public abstract string DeviceId { get; }
|
||||
}
|
||||
|
||||
public override IHardware[] SubHardware => _subHardware.ToArray();
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
_subHardware.ForEach(h => h.Close());
|
||||
|
||||
base.Close();
|
||||
}
|
||||
|
||||
private void TryAddSubHardware()
|
||||
{
|
||||
var wireViewPro2 = WireViewPro2.TryFindDevice(_settings);
|
||||
if (wireViewPro2 != null && wireViewPro2.IsConnected)
|
||||
{
|
||||
_subHardware.Add(wireViewPro2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// 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.
|
||||
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
|
||||
// All Rights Reserved.
|
||||
|
||||
namespace LibreHardwareMonitor.Hardware.Gpu.PowerMonitor;
|
||||
|
||||
internal interface IPowerMonitor
|
||||
{
|
||||
//To be filled
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System.IO.Ports;
|
||||
|
||||
namespace LibreHardwareMonitor.Hardware.Gpu.PowerMonitor;
|
||||
|
||||
internal sealed class SharedSerialPort : SerialPort
|
||||
{
|
||||
private bool _hasMutex;
|
||||
|
||||
public SharedSerialPort()
|
||||
{ }
|
||||
|
||||
public SharedSerialPort(string portName) : base(portName)
|
||||
{ }
|
||||
|
||||
public SharedSerialPort(string portName, int baudRate) : base(portName, baudRate)
|
||||
{ }
|
||||
|
||||
public SharedSerialPort(string portName, int baudRate, Parity parity) : base(portName, baudRate, parity)
|
||||
{ }
|
||||
|
||||
public SharedSerialPort(string portName, int baudRate, Parity parity, int dataBits)
|
||||
: base(portName, baudRate, parity, dataBits)
|
||||
{ }
|
||||
|
||||
public SharedSerialPort(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits)
|
||||
: base(portName, baudRate, parity, dataBits, stopBits)
|
||||
{ }
|
||||
|
||||
private int MutexTimeout { get; set; } = 500;
|
||||
|
||||
public new void Open()
|
||||
{
|
||||
_hasMutex = Mutexes.WaitUsbSensors(MutexTimeout);
|
||||
if (_hasMutex)
|
||||
{
|
||||
base.Open();
|
||||
}
|
||||
}
|
||||
|
||||
public new void Close()
|
||||
{
|
||||
if (_hasMutex)
|
||||
{
|
||||
if (IsOpen)
|
||||
{
|
||||
BaseStream.Flush();
|
||||
BaseStream.Close();
|
||||
}
|
||||
|
||||
_hasMutex = false;
|
||||
Mutexes.ReleaseUsbSensors();
|
||||
}
|
||||
}
|
||||
|
||||
public new void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_hasMutex)
|
||||
{
|
||||
base.Write(buffer, offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
public new int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_hasMutex)
|
||||
{
|
||||
return base.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public new void DiscardInBuffer()
|
||||
{
|
||||
if (_hasMutex)
|
||||
{
|
||||
base.DiscardInBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
public new void DiscardOutBuffer()
|
||||
{
|
||||
if (_hasMutex)
|
||||
{
|
||||
base.DiscardOutBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,547 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Ports;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using LibreHardwareMonitor.Interop.PowerMonitor;
|
||||
|
||||
namespace LibreHardwareMonitor.Hardware.Gpu.PowerMonitor;
|
||||
|
||||
/// <summary>
|
||||
/// Thermal Grizzly WireView Pro II power monitor.
|
||||
/// </summary>
|
||||
public sealed class WireViewPro2 : Hardware, IPowerMonitor
|
||||
{
|
||||
public const string WelcomeMessage = "Thermal Grizzly WireView Pro II";
|
||||
|
||||
private const byte VendorID = 0xEF;
|
||||
private const byte ProductID = 0x05;
|
||||
|
||||
private readonly int _baudRate;
|
||||
private readonly string _portName;
|
||||
private readonly List<WireViewPro2Sensor> _sensors = [];
|
||||
|
||||
private SharedSerialPort _serialPort;
|
||||
|
||||
public WireViewPro2(string portName, ISettings settings, int baud = 115200)
|
||||
: base("WireView Pro II", new Identifier("gpu-powermonitor", portName), settings)
|
||||
{
|
||||
_portName = portName;
|
||||
_baudRate = baud;
|
||||
|
||||
Connect();
|
||||
|
||||
if (IsConnected)
|
||||
{
|
||||
CreateSensors();
|
||||
}
|
||||
}
|
||||
|
||||
public override HardwareType HardwareType => HardwareType.GpuPowerMonitor;
|
||||
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
public string UniqueID { get; private set; }
|
||||
|
||||
public VendorDataStruct? VendorData { get; private set; }
|
||||
|
||||
public static WireViewPro2 TryFindDevice(ISettings settings)
|
||||
{
|
||||
if (!Software.OperatingSystem.IsWindows8OrGreater)
|
||||
{
|
||||
return null; //No Linux implementation yet
|
||||
}
|
||||
|
||||
List<string> matches = Stm32PortFinder.FindMatchingComPorts(0x0483, 0x5740);
|
||||
|
||||
WireViewPro2 wireViewPro2 = null;
|
||||
|
||||
foreach (string port in matches)
|
||||
{
|
||||
try
|
||||
{
|
||||
wireViewPro2 = new WireViewPro2(port, settings);
|
||||
if (wireViewPro2.IsConnected)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
wireViewPro2.Close();
|
||||
wireViewPro2 = null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
wireViewPro2?.Close();
|
||||
wireViewPro2 = null;
|
||||
}
|
||||
}
|
||||
|
||||
return wireViewPro2;
|
||||
}
|
||||
|
||||
public override string GetReport()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(Name);
|
||||
|
||||
foreach (WireViewPro2Sensor sensor in _sensors)
|
||||
{
|
||||
sb.AppendLine($" Sensor: {sensor.Name} = {sensor.Value}");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
Disconnect();
|
||||
|
||||
base.Close();
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
SensorStruct? sensorValues = ReadSensorValues();
|
||||
if (sensorValues.HasValue)
|
||||
{
|
||||
DeviceData deviceData = MapSensorStructure(sensorValues.Value);
|
||||
|
||||
_sensors.ForEach(wvps => wvps.Update(deviceData));
|
||||
}
|
||||
}
|
||||
|
||||
public DeviceConfigStruct? ReadConfig()
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_serialPort.Open();
|
||||
_serialPort.DiscardInBuffer();
|
||||
_serialPort.Write([(byte)UsbCmd.CMD_READ_CONFIG], 0, 1);
|
||||
|
||||
byte[] bytes = ReadExact(Marshal.SizeOf<DeviceConfigStruct>());
|
||||
|
||||
return bytes == null ? null : BytesToStructure<DeviceConfigStruct>(bytes);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_serialPort.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteConfig(DeviceConfigStruct config)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] payload = StructureToBytes(config);
|
||||
|
||||
byte[] frame = new byte[64];
|
||||
frame[0] = (byte)UsbCmd.CMD_WRITE_CONFIG;
|
||||
|
||||
try
|
||||
{
|
||||
_serialPort.Open();
|
||||
_serialPort.DiscardInBuffer();
|
||||
|
||||
const int maxPayloadPerFrame = 62;
|
||||
|
||||
for (byte offset = 0; offset < payload.Length; offset += maxPayloadPerFrame)
|
||||
{
|
||||
int bytesToWrite = Math.Min(maxPayloadPerFrame, payload.Length - offset);
|
||||
|
||||
frame[1] = offset;
|
||||
Buffer.BlockCopy(payload, offset, frame, 2, bytesToWrite);
|
||||
|
||||
_serialPort.Write(frame, 0, bytesToWrite + 2);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_serialPort.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public void NonVolatileMemoryCommand(NVM_CMD cmd)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_serialPort.Open();
|
||||
_serialPort.DiscardInBuffer();
|
||||
_serialPort.Write([
|
||||
(byte)UsbCmd.CMD_NVM_CONFIG,
|
||||
0x55, //Magic
|
||||
0xAA, //Magic
|
||||
0x55, //Magic
|
||||
0xAA, //Magic
|
||||
(byte)cmd
|
||||
],
|
||||
0,
|
||||
6);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_serialPort.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public void ScreenCmd(SCREEN_CMD cmd)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_serialPort.Open();
|
||||
_serialPort.DiscardInBuffer();
|
||||
_serialPort.Write([(byte)UsbCmd.CMD_SCREEN_CHANGE, (byte)cmd], 0, 2);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_serialPort.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearFaults(int faultStatusMask = 0xFFFF, int faultLogMask = 0xFFFF)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_serialPort.Open();
|
||||
_serialPort.DiscardInBuffer();
|
||||
_serialPort.Write([
|
||||
(byte)UsbCmd.CMD_CLEAR_FAULTS,
|
||||
(byte)(faultStatusMask & 0xFF),
|
||||
(byte)((faultStatusMask >> 8) & 0xFF),
|
||||
(byte)(faultLogMask & 0xFF),
|
||||
(byte)((faultLogMask >> 8) & 0xFF)
|
||||
],
|
||||
0,
|
||||
5);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_serialPort.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateSensors()
|
||||
{
|
||||
//Onboard temperature sensors
|
||||
AddSensor("Onboard Temperature In", 0, SensorType.Temperature, dd => (float)dd.OnboardTempInC);
|
||||
AddSensor("Onboard Temperature Out", 1, SensorType.Temperature, dd => (float)dd.OnboardTempOutC);
|
||||
|
||||
//External temperature sensors, requires shipped temperature sensors to be connected
|
||||
AddSensor("External Temperature 1", 2, SensorType.Temperature, dd => (float)dd.ExternalTemp1C);
|
||||
AddSensor("External Temperature 2", 3, SensorType.Temperature, dd => (float)dd.ExternalTemp2C);
|
||||
|
||||
//Pin voltages
|
||||
AddSensor("Pin 1 Voltage", 10, SensorType.Voltage, dd => (float)dd.PinVoltage[0]);
|
||||
AddSensor("Pin 2 Voltage", 11, SensorType.Voltage, dd => (float)dd.PinVoltage[1]);
|
||||
AddSensor("Pin 3 Voltage", 12, SensorType.Voltage, dd => (float)dd.PinVoltage[2]);
|
||||
AddSensor("Pin 4 Voltage", 13, SensorType.Voltage, dd => (float)dd.PinVoltage[3]);
|
||||
AddSensor("Pin 5 Voltage", 14, SensorType.Voltage, dd => (float)dd.PinVoltage[4]);
|
||||
AddSensor("Pin 6 Voltage", 15, SensorType.Voltage, dd => (float)dd.PinVoltage[5]);
|
||||
|
||||
//Pin currents
|
||||
AddSensor("Total Current", 20, SensorType.Current, dd => (float)dd.SumCurrentA);
|
||||
AddSensor("Pin 1 Current", 21, SensorType.Current, dd => (float)dd.PinCurrent[0]);
|
||||
AddSensor("Pin 2 Current", 22, SensorType.Current, dd => (float)dd.PinCurrent[1]);
|
||||
AddSensor("Pin 3 Current", 23, SensorType.Current, dd => (float)dd.PinCurrent[2]);
|
||||
AddSensor("Pin 4 Current", 24, SensorType.Current, dd => (float)dd.PinCurrent[3]);
|
||||
AddSensor("Pin 5 Current", 25, SensorType.Current, dd => (float)dd.PinCurrent[4]);
|
||||
AddSensor("Pin 6 Current", 26, SensorType.Current, dd => (float)dd.PinCurrent[5]);
|
||||
|
||||
//Power
|
||||
AddSensor("Total Power", 30, SensorType.Power, dd => (float)dd.SumPowerW);
|
||||
}
|
||||
|
||||
private void AddSensor(string name, int index, SensorType sensorType, GetWireViewPro2SensorValue getValue)
|
||||
{
|
||||
var sensor = new WireViewPro2Sensor(name, index, sensorType, this, _settings, getValue);
|
||||
|
||||
_sensors.Add(sensor);
|
||||
|
||||
ActivateSensor(sensor);
|
||||
}
|
||||
|
||||
private void Connect()
|
||||
{
|
||||
if (IsConnected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_serialPort = new SharedSerialPort(_portName, _baudRate, Parity.None, 8, StopBits.One);
|
||||
_serialPort.ReadTimeout = 1000;
|
||||
_serialPort.WriteTimeout = 1000;
|
||||
|
||||
if (!ReadWelcomeMessage())
|
||||
{
|
||||
IsConnected = false;
|
||||
return;
|
||||
}
|
||||
|
||||
VendorDataStruct? vendorData = ReadVendorData();
|
||||
|
||||
if (vendorData.HasValue &&
|
||||
vendorData.Value.VendorId == VendorID &&
|
||||
vendorData.Value.ProductId == ProductID)
|
||||
{
|
||||
VendorData = vendorData.Value;
|
||||
UniqueID = ReadUniqueID();
|
||||
|
||||
IsConnected = true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (_serialPort != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_serialPort.IsOpen)
|
||||
{
|
||||
_serialPort.Close();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
//Ignore exceptions during cleanup
|
||||
}
|
||||
finally
|
||||
{
|
||||
_serialPort.Dispose();
|
||||
_serialPort = null;
|
||||
}
|
||||
}
|
||||
|
||||
IsConnected = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Disconnect()
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_serialPort != null)
|
||||
{
|
||||
_serialPort.Close();
|
||||
_serialPort.Dispose();
|
||||
_serialPort = null;
|
||||
}
|
||||
|
||||
IsConnected = false;
|
||||
|
||||
VendorData = null;
|
||||
UniqueID = null;
|
||||
}
|
||||
|
||||
private bool ReadWelcomeMessage(bool sendCmd = false)
|
||||
{
|
||||
int size = WelcomeMessage.Length + 1;
|
||||
|
||||
try
|
||||
{
|
||||
_serialPort.Open();
|
||||
_serialPort.RtsEnable = true;
|
||||
|
||||
if (sendCmd)
|
||||
{
|
||||
_serialPort.DiscardInBuffer();
|
||||
_serialPort.Write([(byte)UsbCmd.CMD_WELCOME], 0, 1);
|
||||
}
|
||||
|
||||
byte[] bytes = ReadExact(size);
|
||||
|
||||
_serialPort.RtsEnable = false;
|
||||
|
||||
return bytes == null ? false : Encoding.ASCII.GetString(bytes, 0, size).TrimEnd('\0').CompareTo(WelcomeMessage) == 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_serialPort.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private VendorDataStruct? ReadVendorData()
|
||||
{
|
||||
try
|
||||
{
|
||||
_serialPort.Open();
|
||||
_serialPort.DiscardInBuffer();
|
||||
_serialPort.Write([(byte)UsbCmd.CMD_READ_VENDOR_DATA], 0, 1);
|
||||
|
||||
byte[] bytes = ReadExact(Marshal.SizeOf<VendorDataStruct>());
|
||||
|
||||
return bytes == null ? null : BytesToStructure<VendorDataStruct>(bytes);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_serialPort.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private string ReadUniqueID()
|
||||
{
|
||||
try
|
||||
{
|
||||
_serialPort.Open();
|
||||
_serialPort.DiscardInBuffer();
|
||||
_serialPort.Write([(byte)UsbCmd.CMD_READ_UID], 0, 1);
|
||||
|
||||
byte[] bytes = ReadExact(12);
|
||||
|
||||
return bytes == null ? null : BitConverter.ToString(bytes).Replace("-", string.Empty);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_serialPort.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private SensorStruct? ReadSensorValues()
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_serialPort.Open();
|
||||
_serialPort.DiscardInBuffer();
|
||||
_serialPort.Write([(byte)UsbCmd.CMD_READ_SENSOR_VALUES], 0, 1);
|
||||
|
||||
byte[] bytes = ReadExact(Marshal.SizeOf<SensorStruct>());
|
||||
|
||||
return bytes == null ? null : BytesToStructure<SensorStruct>(bytes);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_serialPort.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private DeviceData MapSensorStructure(SensorStruct sensorStruct)
|
||||
{
|
||||
var deviceData = new DeviceData
|
||||
{
|
||||
Connected = true,
|
||||
HardwareRevision = $"{VendorData?.VendorId}{VendorData?.ProductId}",
|
||||
FirmwareVersion = $"{VendorData?.FwVersion}",
|
||||
OnboardTempInC = sensorStruct.Ts[(int)SensorTs.SENSOR_TS_IN] / 10.0,
|
||||
OnboardTempOutC = sensorStruct.Ts[(int)SensorTs.SENSOR_TS_OUT] / 10.0,
|
||||
ExternalTemp1C = sensorStruct.Ts[(int)SensorTs.SENSOR_TS3] / 10.0,
|
||||
ExternalTemp2C = sensorStruct.Ts[(int)SensorTs.SENSOR_TS4] / 10.0,
|
||||
FaultStatus = sensorStruct.FaultStatus,
|
||||
FaultLog = sensorStruct.FaultLog,
|
||||
};
|
||||
|
||||
switch (sensorStruct.HpwrCapability)
|
||||
{
|
||||
case HpwrCapability.PSU_CAP_600W:
|
||||
deviceData.PsuCapabilityW = 600;
|
||||
break;
|
||||
case HpwrCapability.PSU_CAP_450W:
|
||||
deviceData.PsuCapabilityW = 450;
|
||||
break;
|
||||
case HpwrCapability.PSU_CAP_300W:
|
||||
deviceData.PsuCapabilityW = 300;
|
||||
break;
|
||||
case HpwrCapability.PSU_CAP_150W:
|
||||
deviceData.PsuCapabilityW = 150;
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 6; ++i)
|
||||
{
|
||||
deviceData.PinVoltage[i] = sensorStruct.PowerReadings[i].Voltage / 1000.0;
|
||||
deviceData.PinCurrent[i] = sensorStruct.PowerReadings[i].Current / 1000.0;
|
||||
}
|
||||
|
||||
return deviceData;
|
||||
}
|
||||
|
||||
private byte[] ReadExact(int size)
|
||||
{
|
||||
byte[] buffer = new byte[size];
|
||||
|
||||
int offset = 0;
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
while (offset < size && sw.ElapsedMilliseconds < _serialPort.ReadTimeout)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_serialPort.BytesToRead > 0)
|
||||
{
|
||||
offset += _serialPort.Read(buffer, offset, size - offset);
|
||||
}
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
//Ignore timeout exceptions and continue reading
|
||||
}
|
||||
}
|
||||
|
||||
return offset != size ? null : buffer;
|
||||
}
|
||||
|
||||
private T BytesToStructure<T>(byte[] bytes) where T : struct
|
||||
{
|
||||
var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
|
||||
|
||||
try
|
||||
{
|
||||
return Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
|
||||
}
|
||||
finally
|
||||
{
|
||||
handle.Free();
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] StructureToBytes<T>(T value) where T : struct
|
||||
{
|
||||
int size = Marshal.SizeOf<T>();
|
||||
|
||||
byte[] bytes = new byte[size];
|
||||
IntPtr ptr = Marshal.AllocHGlobal(size);
|
||||
|
||||
try
|
||||
{
|
||||
Marshal.StructureToPtr(value, ptr, false);
|
||||
Marshal.Copy(ptr, bytes, 0, size);
|
||||
return bytes;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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.
|
||||
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
|
||||
// All Rights Reserved.
|
||||
|
||||
using LibreHardwareMonitor.Interop.PowerMonitor;
|
||||
|
||||
namespace LibreHardwareMonitor.Hardware.Gpu.PowerMonitor;
|
||||
|
||||
internal delegate float GetWireViewPro2SensorValue(DeviceData wvp);
|
||||
|
||||
internal class WireViewPro2Sensor : Sensor
|
||||
{
|
||||
readonly GetWireViewPro2SensorValue _getValue;
|
||||
|
||||
public WireViewPro2Sensor(string name, int index, SensorType sensorType, Hardware hardware, ISettings settings, GetWireViewPro2SensorValue getValue)
|
||||
: base(name, index, sensorType, hardware, settings)
|
||||
{
|
||||
_getValue = getValue;
|
||||
}
|
||||
|
||||
internal void Update(DeviceData wvp)
|
||||
{
|
||||
float value = _getValue(wvp);
|
||||
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ public abstract class Hardware : IHardware
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IHardware[] SubHardware
|
||||
public virtual IHardware[] SubHardware
|
||||
{
|
||||
get { return Array.Empty<IHardware>(); }
|
||||
}
|
||||
@@ -131,4 +131,4 @@ public abstract class Hardware : IHardware
|
||||
|
||||
public event SensorEventHandler SensorRemoved;
|
||||
#pragma warning restore 67
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,5 +23,6 @@ public enum HardwareType
|
||||
Cooler,
|
||||
EmbeddedController,
|
||||
Psu,
|
||||
Battery
|
||||
}
|
||||
Battery,
|
||||
GpuPowerMonitor,
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ internal static class Mutexes
|
||||
private static Mutex _isaBusMutex;
|
||||
private static Mutex _pciBusMutex;
|
||||
private static Mutex _razerMutex;
|
||||
private static Mutex _usbSensors;
|
||||
|
||||
/// <summary>
|
||||
/// Opens the mutexes.
|
||||
@@ -21,6 +22,7 @@ internal static class Mutexes
|
||||
_pciBusMutex = CreateOrOpenExistingMutex("Global\\Access_PCI");
|
||||
_ecMutex = CreateOrOpenExistingMutex("Global\\Access_EC");
|
||||
_razerMutex = CreateOrOpenExistingMutex("Global\\RazerReadWriteGuardMutex");
|
||||
_usbSensors = CreateOrOpenExistingMutex("Global\\Access_USB_Sensors");
|
||||
|
||||
static Mutex CreateOrOpenExistingMutex(string name)
|
||||
{
|
||||
@@ -61,6 +63,7 @@ internal static class Mutexes
|
||||
_pciBusMutex?.Close();
|
||||
_ecMutex?.Close();
|
||||
_razerMutex?.Close();
|
||||
_usbSensors?.Close();
|
||||
}
|
||||
|
||||
public static bool WaitIsaBus(int millisecondsTimeout)
|
||||
@@ -103,6 +106,16 @@ internal static class Mutexes
|
||||
_razerMutex?.ReleaseMutex();
|
||||
}
|
||||
|
||||
public static bool WaitUsbSensors(int millisecondsTimeout)
|
||||
{
|
||||
return WaitMutex(_usbSensors, millisecondsTimeout);
|
||||
}
|
||||
|
||||
public static void ReleaseUsbSensors()
|
||||
{
|
||||
_usbSensors?.ReleaseMutex();
|
||||
}
|
||||
|
||||
private static bool WaitMutex(Mutex mutex, int millisecondsTimeout)
|
||||
{
|
||||
if (mutex == null)
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// 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.
|
||||
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
|
||||
// All Rights Reserved.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibreHardwareMonitor.Interop.PowerMonitor;
|
||||
|
||||
internal sealed class DeviceData
|
||||
{
|
||||
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public bool Connected { get; set; }
|
||||
|
||||
public string HardwareRevision { get; set; } = "A0";
|
||||
|
||||
public string FirmwareVersion { get; set; } = "0.0.0";
|
||||
|
||||
public double[] PinVoltage { get; set; } = new double[6];
|
||||
|
||||
public double[] PinCurrent { get; set; } = new double[6];
|
||||
|
||||
public double OnboardTempInC { get; set; }
|
||||
|
||||
public double OnboardTempOutC { get; set; }
|
||||
|
||||
public double ExternalTemp1C { get; set; }
|
||||
|
||||
public double ExternalTemp2C { get; set; }
|
||||
|
||||
public int PsuCapabilityW { get; set; }
|
||||
|
||||
public double SumCurrentA => PinCurrent.Sum();
|
||||
|
||||
public double SumPowerW => PinVoltage.Zip(PinCurrent, (v, i) => v * i).Sum();
|
||||
|
||||
public ushort FaultStatus { get; set; }
|
||||
|
||||
public ushort FaultLog { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// 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.
|
||||
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
|
||||
// All Rights Reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Devices.DeviceAndDriverInstallation;
|
||||
using Windows.Win32.Foundation;
|
||||
|
||||
namespace LibreHardwareMonitor.Interop.PowerMonitor;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class to find STM32 COM ports based on VID and PID.
|
||||
/// </summary>
|
||||
public static class Stm32PortFinder
|
||||
{
|
||||
private const int BufferSize = 1024;
|
||||
|
||||
/// <summary>
|
||||
/// Finds the names of all available COM ports that match the specified USB vendor ID (VID) and product ID (PID).
|
||||
/// </summary>
|
||||
/// <param name="vid">The USB vendor ID to match.</param>
|
||||
/// <param name="pid">The USB product ID to match.</param>
|
||||
/// <returns>
|
||||
/// A list of strings containing the names of matching COM ports.<br />
|
||||
/// The list is empty if no matching ports are found.
|
||||
/// </returns>
|
||||
public static List<string> FindMatchingComPorts(uint vid, uint pid)
|
||||
{
|
||||
return FindMatchingComPorts($"{vid:X4}", $"{pid:X4}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="FindMatchingComPorts(uint, uint)" />
|
||||
/// </summary>
|
||||
/// <param name="vid">
|
||||
/// <inheritdoc cref="FindMatchingComPorts(uint, uint)" />
|
||||
/// </param>
|
||||
/// <param name="pid">
|
||||
/// <inheritdoc cref="FindMatchingComPorts(uint, uint)" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <inheritdoc cref="FindMatchingComPorts(uint, uint)" />
|
||||
/// </returns>
|
||||
public static List<string> FindMatchingComPorts(string vid, string pid)
|
||||
{
|
||||
var result = new List<string>();
|
||||
|
||||
//Setup
|
||||
SetupDiDestroyDeviceInfoListSafeHandle devInfo = PInvoke.SetupDiGetClassDevs(PInvoke.GUID_DEVCLASS_PORTS, null, HWND.Null, SETUP_DI_GET_CLASS_DEVS_FLAGS.DIGCF_PRESENT);
|
||||
|
||||
//Check handle
|
||||
if (devInfo.IsInvalid)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
SP_DEVINFO_DATA devInfoData = new() { cbSize = (uint)Marshal.SizeOf<SP_DEVINFO_DATA>() };
|
||||
|
||||
uint index = 0;
|
||||
while (PInvoke.SetupDiEnumDeviceInfo(devInfo, index++, ref devInfoData))
|
||||
{
|
||||
//Get hardware ID
|
||||
string hwId = GetProperty(devInfo, SETUP_DI_REGISTRY_PROPERTY.SPDRP_HARDWAREID, devInfoData);
|
||||
if (string.IsNullOrWhiteSpace(hwId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//Check if hardware ID contains VID and PID
|
||||
if (!hwId.Contains($"VID_{vid}", StringComparison.OrdinalIgnoreCase) ||
|
||||
!hwId.Contains($"PID_{pid}", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//Get friendly name
|
||||
string friendlyName = GetProperty(devInfo, SETUP_DI_REGISTRY_PROPERTY.SPDRP_FRIENDLYNAME, devInfoData);
|
||||
|
||||
//Extract COM port from friendly name
|
||||
int start = friendlyName.LastIndexOf("(COM", StringComparison.OrdinalIgnoreCase);
|
||||
int end = friendlyName.LastIndexOf(')');
|
||||
|
||||
//Check if valid and add to result
|
||||
if (start >= 0 && end > start)
|
||||
{
|
||||
string com = friendlyName.Substring(start + 1, end - start - 1);
|
||||
result.Add(com);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
//Cleanup
|
||||
devInfo.Dispose();
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
static string GetProperty(SafeHandle hDevInfo, SETUP_DI_REGISTRY_PROPERTY property, SP_DEVINFO_DATA devInfoData)
|
||||
{
|
||||
byte[] buffer = new byte[BufferSize];
|
||||
|
||||
if (PInvoke.SetupDiGetDeviceRegistryProperty(hDevInfo, devInfoData, property, buffer))
|
||||
{
|
||||
//Take first string only, no need for the rest
|
||||
return NormalizeString(Encoding.Unicode.GetString(buffer));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeString(string str)
|
||||
{
|
||||
int end = str.IndexOf('\0');
|
||||
if (end == -1)
|
||||
{
|
||||
end = 0;
|
||||
}
|
||||
|
||||
return str.Substring(0, end);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// 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.
|
||||
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
|
||||
// All Rights Reserved.
|
||||
|
||||
namespace LibreHardwareMonitor.Interop.PowerMonitor;
|
||||
|
||||
public enum UsbCmd : byte
|
||||
{
|
||||
CMD_WELCOME,
|
||||
CMD_READ_VENDOR_DATA,
|
||||
CMD_READ_UID,
|
||||
CMD_READ_DEVICE_DATA,
|
||||
CMD_READ_SENSOR_VALUES,
|
||||
CMD_READ_CONFIG,
|
||||
CMD_WRITE_CONFIG,
|
||||
CMD_READ_CALIBRATION,
|
||||
CMD_WRITE_CALIBRATION,
|
||||
CMD_SPI_FLASH_WRITE_PAGE,
|
||||
CMD_SPI_FLASH_READ_PAGE,
|
||||
CMD_SPI_FLASH_ERASE_SECTOR,
|
||||
CMD_SCREEN_CHANGE,
|
||||
CMD_READ_BUILD_INFO,
|
||||
CMD_CLEAR_FAULTS,
|
||||
CMD_RESET = 0xF0,
|
||||
CMD_BOOTLOADER = 0xF1,
|
||||
CMD_NVM_CONFIG = 0xF2,
|
||||
CMD_NOP = 0xFF
|
||||
}
|
||||
|
||||
public enum SensorTs
|
||||
{
|
||||
SENSOR_TS_IN,
|
||||
SENSOR_TS_OUT,
|
||||
SENSOR_TS3,
|
||||
SENSOR_TS4,
|
||||
}
|
||||
|
||||
public enum HpwrCapability : byte
|
||||
{
|
||||
PSU_CAP_600W = 0,
|
||||
PSU_CAP_450W = 1,
|
||||
PSU_CAP_300W = 2,
|
||||
PSU_CAP_150W = 3
|
||||
}
|
||||
|
||||
public enum FanMode : byte
|
||||
{
|
||||
FanModeCurve = 0,
|
||||
FanModeFixed = 1
|
||||
}
|
||||
|
||||
public enum TempSource : byte
|
||||
{
|
||||
TempSourceTsIn = 0,
|
||||
TempSourceTsOut = 1,
|
||||
TempSourceTs1 = 2,
|
||||
TempSourceTs2 = 3,
|
||||
TempSourceTmax = 4
|
||||
}
|
||||
|
||||
public enum CurrentScale : byte
|
||||
{
|
||||
CurrentScale5A = 0,
|
||||
CurrentScale10A = 1,
|
||||
CurrentScale15A = 2,
|
||||
CurrentScale20A = 3
|
||||
}
|
||||
|
||||
public enum PowerScale : byte
|
||||
{
|
||||
PowerScaleAuto = 0,
|
||||
PowerScale300W = 1,
|
||||
PowerScale600W = 2
|
||||
}
|
||||
|
||||
public enum Theme : byte
|
||||
{
|
||||
ThemeTg1 = 0,
|
||||
ThemeTg2 = 1,
|
||||
ThemeTg3 = 2
|
||||
}
|
||||
|
||||
public enum DisplayRotation : byte
|
||||
{
|
||||
DisplayRotation0 = 0,
|
||||
DisplayRotation180 = 1
|
||||
}
|
||||
|
||||
public enum TimeoutMode : byte
|
||||
{
|
||||
TimeoutModeStatic = 0,
|
||||
TimeoutModeCycle = 1,
|
||||
TimeoutModeSleep = 2
|
||||
}
|
||||
|
||||
public enum NVM_CMD : byte
|
||||
{
|
||||
NVM_CMD_NONE,
|
||||
NVM_CMD_LOAD,
|
||||
NVM_CMD_STORE,
|
||||
NVM_CMD_RESET,
|
||||
NVM_CMD_LOAD_CAL,
|
||||
NVM_CMD_STORE_CAL,
|
||||
NVM_CMD_LOAD_CAL_FACTORY,
|
||||
NVM_CMD_STORE_CAL_FACTORY,
|
||||
}
|
||||
|
||||
public enum SCREEN_CMD : byte
|
||||
{
|
||||
SCREEN_GOTO_MAIN = 0xE0,
|
||||
SCREEN_GOTO_SIMPLE = 0xE1,
|
||||
SCREEN_GOTO_CURRENT = 0xE2,
|
||||
SCREEN_GOTO_TEMP = 0xE3,
|
||||
SCREEN_GOTO_STATUS = 0xE4,
|
||||
SCREEN_GOTO_SAME = 0xEF,
|
||||
SCREEN_PAUSE_UPDATES = 0xF0,
|
||||
SCREEN_RESUME_UPDATES = 0xF1
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// 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.
|
||||
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
|
||||
// All Rights Reserved.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LibreHardwareMonitor.Interop.PowerMonitor;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct VendorDataStruct
|
||||
{
|
||||
public byte VendorId;
|
||||
public byte ProductId;
|
||||
public byte FwVersion;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct PowerSensor
|
||||
{
|
||||
public short Voltage;
|
||||
public uint Current;
|
||||
public uint Power;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct SensorStruct
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
public short[] Ts; // 0.1 °C
|
||||
|
||||
public ushort Vdd; // mV
|
||||
public byte FanDuty; // %
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||
public PowerSensor[] PowerReadings;
|
||||
|
||||
public uint TotalPower; // mW
|
||||
public uint TotalCurrent; // mA
|
||||
public ushort AvgVoltage; // mV
|
||||
public HpwrCapability HpwrCapability; // 8-bit enum
|
||||
public ushort FaultStatus;
|
||||
public ushort FaultLog;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct FanConfigStruct
|
||||
{
|
||||
public FanMode Mode;
|
||||
public TempSource TempSource;
|
||||
public byte DutyMin;
|
||||
public byte DutyMax;
|
||||
public short TempMin;
|
||||
public short TempMax;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct UiConfigStruct
|
||||
{
|
||||
public CurrentScale CurrentScale;
|
||||
public PowerScale PowerScale;
|
||||
public Theme Theme;
|
||||
public DisplayRotation DisplayRotation;
|
||||
public TimeoutMode TimeoutMode;
|
||||
public byte CycleScreens;
|
||||
public byte CycleTime;
|
||||
public byte Timeout;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct DeviceConfigStruct
|
||||
{
|
||||
public ushort Crc;
|
||||
public byte Version;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
|
||||
public byte[] FriendlyName;
|
||||
|
||||
public FanConfigStruct FanConfig;
|
||||
public byte BacklightDuty;
|
||||
|
||||
public ushort FaultDisplayEnable;
|
||||
public ushort FaultBuzzerEnable;
|
||||
public ushort FaultSoftPowerEnable;
|
||||
public ushort FaultHardPowerEnable;
|
||||
public short TsFaultThreshold; // 0.1 °C
|
||||
public byte OcpFaultThreshold; // A
|
||||
public byte WireOcpFaultThreshold; // 0.1A
|
||||
public ushort OppFaultThreshold; // W
|
||||
public byte CurrentImbalanceFaultThreshold; // %
|
||||
public byte CurrentImbalanceFaultMinLoad; // A
|
||||
public byte ShutdownWaitTime; // seconds
|
||||
public byte LoggingInterval; // seconds
|
||||
public UiConfigStruct Ui;
|
||||
}
|
||||
@@ -13,13 +13,17 @@ D3DKMT_ADAPTERTYPE
|
||||
D3DKMT_SEGMENTSIZEINFO
|
||||
D3DKMT_NODEMETADATA
|
||||
D3DKMT_QUERYSTATISTICS_NODE_INFORMATION
|
||||
SetupDiEnumDeviceInfo
|
||||
SetupDiGetDeviceRegistryProperty
|
||||
SetupDiDestroyDeviceInfoList
|
||||
SetupDiGetDeviceInterfaceDetail
|
||||
SetupDiEnumDeviceInterfaces
|
||||
SetupDiGetClassDevs
|
||||
SP_DEVICE_INTERFACE_DATA
|
||||
SP_DEVICE_INTERFACE_DETAIL_DATA_W
|
||||
GUID_DEVCLASS_PORTS
|
||||
GUID_DEVICE_BATTERY
|
||||
SETUP_DI_REGISTRY_PROPERTY
|
||||
WIN32_ERROR
|
||||
HRESULT_FROM_WIN32
|
||||
GlobalMemoryStatusEx
|
||||
|
||||
Reference in New Issue
Block a user