From 5152b435fc8b3b46aee549d37cd373ff105ec858 Mon Sep 17 00:00:00 2001 From: EMN-CSharp <84488929+EMN-CSharp@users.noreply.github.com> Date: Mon, 7 Feb 2022 13:05:36 -0300 Subject: [PATCH] Add support for battery monitoring (#612) Thanks to @EMN-CSharp. --- LibreHardwareMonitor/Resources/battery.png | Bin 0 -> 648 bytes LibreHardwareMonitor/UI/HardwareTypeImage.cs | 3 + LibreHardwareMonitor/UI/MainForm.Designer.cs | 54 +++-- LibreHardwareMonitor/UI/MainForm.cs | 7 + LibreHardwareMonitor/UI/PlotPanel.cs | 3 +- LibreHardwareMonitor/UI/SensorGadget.cs | 3 + LibreHardwareMonitor/UI/SensorNode.cs | 3 + LibreHardwareMonitor/UI/SensorNotifyIcon.cs | 2 + LibreHardwareMonitor/UI/TypeNode.cs | 4 + LibreHardwareMonitor/Utilities/HttpServer.cs | 2 + .../Hardware/Battery/Battery.cs | 195 +++++++++++++++ .../Hardware/Battery/BatteryChemistry.cs | 17 ++ .../Hardware/Battery/BatteryGroup.cs | 227 ++++++++++++++++++ LibreHardwareMonitorLib/Hardware/Computer.cs | 155 ++++++------ .../Hardware/HardwareType.cs | 3 +- LibreHardwareMonitorLib/Hardware/IComputer.cs | 128 +++++----- LibreHardwareMonitorLib/Hardware/ISensor.cs | 1 + LibreHardwareMonitorLib/Interop/Kernel32.cs | 136 ++++++++++- LibreHardwareMonitorLib/Interop/SetupApi.cs | 50 ++++ 19 files changed, 844 insertions(+), 149 deletions(-) create mode 100644 LibreHardwareMonitor/Resources/battery.png create mode 100644 LibreHardwareMonitorLib/Hardware/Battery/Battery.cs create mode 100644 LibreHardwareMonitorLib/Hardware/Battery/BatteryChemistry.cs create mode 100644 LibreHardwareMonitorLib/Hardware/Battery/BatteryGroup.cs create mode 100644 LibreHardwareMonitorLib/Interop/SetupApi.cs diff --git a/LibreHardwareMonitor/Resources/battery.png b/LibreHardwareMonitor/Resources/battery.png new file mode 100644 index 0000000000000000000000000000000000000000..835a8b11075ef204de3eb302634d34b8abe3d2cc GIT binary patch literal 648 zcmV;30(bq1P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0v$<2K~y+Tjgn7j z(@-48zfJ4@ShFRrY-BAPMR39<9-=3ih=@23;-Pr);$b4o!|wOib&N#b{RP+%tU*j?Q%YfspsU-}n0_-w+@EL2CRlk!6*x zFUQaPb-rQ1mG~{v+`{7Y@DOaq|gMZ4yxB_}x=vDwl=2tbwf5TK0s( i= and Contributors. +// Partial Copyright (C) Michael Möller and Contributors. // All Rights Reserved. namespace LibreHardwareMonitor.UI @@ -59,6 +59,7 @@ namespace LibreHardwareMonitor.UI this.fanControllerMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.hddMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.nicMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.psuMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.menuItem6 = new System.Windows.Forms.ToolStripSeparator(); this.exitMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.viewMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -125,6 +126,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.batteryMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.psuMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.backgroundUpdater = new System.ComponentModel.BackgroundWorker(); this.mainMenu.SuspendLayout(); @@ -240,19 +242,19 @@ namespace LibreHardwareMonitor.UI // saveReportMenuItem // this.saveReportMenuItem.Name = "saveReportMenuItem"; - this.saveReportMenuItem.Size = new System.Drawing.Size(145, 22); + this.saveReportMenuItem.Size = new System.Drawing.Size(180, 22); this.saveReportMenuItem.Text = "Save Report..."; this.saveReportMenuItem.Click += new System.EventHandler(this.SaveReportMenuItem_Click); // // MenuItem2 // this.MenuItem2.Name = "MenuItem2"; - this.MenuItem2.Size = new System.Drawing.Size(142, 6); + this.MenuItem2.Size = new System.Drawing.Size(177, 6); // // resetMenuItem // this.resetMenuItem.Name = "resetMenuItem"; - this.resetMenuItem.Size = new System.Drawing.Size(145, 22); + this.resetMenuItem.Size = new System.Drawing.Size(180, 22); this.resetMenuItem.Text = "Reset"; this.resetMenuItem.Click += new System.EventHandler(this.ResetClick); // @@ -266,62 +268,69 @@ namespace LibreHardwareMonitor.UI this.fanControllerMenuItem, this.hddMenuItem, this.nicMenuItem, - this.psuMenuItem}); + this.psuMenuItem, + this.batteryMenuItem}); this.menuItem5.Name = "menuItem5"; - this.menuItem5.Size = new System.Drawing.Size(145, 22); + this.menuItem5.Size = new System.Drawing.Size(180, 22); this.menuItem5.Text = "Hardware"; // // mainboardMenuItem // this.mainboardMenuItem.Name = "mainboardMenuItem"; - this.mainboardMenuItem.Size = new System.Drawing.Size(160, 22); + this.mainboardMenuItem.Size = new System.Drawing.Size(180, 22); this.mainboardMenuItem.Text = "Motherboard"; // // cpuMenuItem // this.cpuMenuItem.Name = "cpuMenuItem"; - this.cpuMenuItem.Size = new System.Drawing.Size(160, 22); + this.cpuMenuItem.Size = new System.Drawing.Size(180, 22); this.cpuMenuItem.Text = "CPU"; // // ramMenuItem // this.ramMenuItem.Name = "ramMenuItem"; - this.ramMenuItem.Size = new System.Drawing.Size(160, 22); + this.ramMenuItem.Size = new System.Drawing.Size(180, 22); this.ramMenuItem.Text = "RAM"; // // gpuMenuItem // this.gpuMenuItem.Name = "gpuMenuItem"; - this.gpuMenuItem.Size = new System.Drawing.Size(160, 22); + this.gpuMenuItem.Size = new System.Drawing.Size(180, 22); this.gpuMenuItem.Text = "GPU"; // // fanControllerMenuItem // this.fanControllerMenuItem.Name = "fanControllerMenuItem"; - this.fanControllerMenuItem.Size = new System.Drawing.Size(160, 22); + this.fanControllerMenuItem.Size = new System.Drawing.Size(180, 22); this.fanControllerMenuItem.Text = "Fan Controllers"; // // hddMenuItem // this.hddMenuItem.Name = "hddMenuItem"; - this.hddMenuItem.Size = new System.Drawing.Size(160, 22); + this.hddMenuItem.Size = new System.Drawing.Size(180, 22); this.hddMenuItem.Text = "Hard Disk Drives"; // // nicMenuItem // this.nicMenuItem.Name = "nicMenuItem"; - this.nicMenuItem.Size = new System.Drawing.Size(160, 22); + this.nicMenuItem.Size = new System.Drawing.Size(180, 22); this.nicMenuItem.Text = "Network"; // + // psuMenuItem + // + this.psuMenuItem.Name = "psuMenuItem"; + this.psuMenuItem.Size = new System.Drawing.Size(180, 22); + this.psuMenuItem.Text = "Power supplies"; + // // menuItem6 // this.menuItem6.Name = "menuItem6"; - this.menuItem6.Size = new System.Drawing.Size(142, 6); + this.menuItem6.Size = new System.Drawing.Size(177, 6); // // exitMenuItem // this.exitMenuItem.Name = "exitMenuItem"; - this.exitMenuItem.Size = new System.Drawing.Size(145, 22); + this.exitMenuItem.Size = new System.Drawing.Size(180, 22); this.exitMenuItem.Text = "Exit"; this.exitMenuItem.Click += new System.EventHandler(this.ExitClick); // @@ -387,19 +396,19 @@ namespace LibreHardwareMonitor.UI // valueMenuItem // this.valueMenuItem.Name = "valueMenuItem"; - this.valueMenuItem.Size = new System.Drawing.Size(102, 22); + this.valueMenuItem.Size = new System.Drawing.Size(180, 22); this.valueMenuItem.Text = "Value"; // // minMenuItem // this.minMenuItem.Name = "minMenuItem"; - this.minMenuItem.Size = new System.Drawing.Size(102, 22); + this.minMenuItem.Size = new System.Drawing.Size(180, 22); this.minMenuItem.Text = "Min"; // // maxMenuItem // this.maxMenuItem.Name = "maxMenuItem"; - this.maxMenuItem.Size = new System.Drawing.Size(102, 22); + this.maxMenuItem.Size = new System.Drawing.Size(180, 22); this.maxMenuItem.Text = "Max"; // // optionsMenuItem @@ -847,11 +856,11 @@ 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 + // batteryMenuItem // - this.psuMenuItem.Name = "psuMenuItem"; - this.psuMenuItem.Size = new System.Drawing.Size(180, 22); - this.psuMenuItem.Text = "Power supplies"; + this.batteryMenuItem.Name = "batteryMenuItem"; + this.batteryMenuItem.Size = new System.Drawing.Size(180, 22); + this.batteryMenuItem.Text = "Batteries"; // // MainForm // @@ -970,6 +979,7 @@ namespace LibreHardwareMonitor.UI private ToolStripRadioButtonMenuItem timeWindow24hMenuItem; private System.Windows.Forms.ToolStripMenuItem authWebServerMenuItem; private System.Windows.Forms.ToolStripMenuItem psuMenuItem; + private System.Windows.Forms.ToolStripMenuItem batteryMenuItem; private System.ComponentModel.BackgroundWorker backgroundUpdater; } } diff --git a/LibreHardwareMonitor/UI/MainForm.cs b/LibreHardwareMonitor/UI/MainForm.cs index b367a09..1b08c95 100644 --- a/LibreHardwareMonitor/UI/MainForm.cs +++ b/LibreHardwareMonitor/UI/MainForm.cs @@ -50,6 +50,7 @@ namespace LibreHardwareMonitor.UI private readonly UserOption _readHddSensors; private readonly UserOption _readNicSensors; private readonly UserOption _readPsuSensors; + private readonly UserOption _readBatterySensors; private readonly UserOption _showGadget; private UserRadioGroup _plotLocation; @@ -262,6 +263,12 @@ namespace LibreHardwareMonitor.UI _computer.IsPsuEnabled = _readPsuSensors.Value; }; + _readBatterySensors = new UserOption("batteryMenuItem", true, batteryMenuItem, _settings); + _readBatterySensors.Changed += delegate + { + _computer.IsBatteryEnabled = _readBatterySensors.Value; + }; + _showGadget = new UserOption("gadgetMenuItem", false, gadgetMenuItem, _settings); _showGadget.Changed += delegate { diff --git a/LibreHardwareMonitor/UI/PlotPanel.cs b/LibreHardwareMonitor/UI/PlotPanel.cs index 1a06123..a96379c 100644 --- a/LibreHardwareMonitor/UI/PlotPanel.cs +++ b/LibreHardwareMonitor/UI/PlotPanel.cs @@ -171,7 +171,8 @@ namespace LibreHardwareMonitor.UI { SensorType.Factor, "1" }, { SensorType.Power, "W" }, { SensorType.Data, "GB" }, - { SensorType.Frequency, "Hz" } + { SensorType.Frequency, "Hz" }, + { SensorType.Energy, "mWh" } }; foreach (SensorType type in Enum.GetValues(typeof(SensorType))) diff --git a/LibreHardwareMonitor/UI/SensorGadget.cs b/LibreHardwareMonitor/UI/SensorGadget.cs index 341c716..9fb6f64 100644 --- a/LibreHardwareMonitor/UI/SensorGadget.cs +++ b/LibreHardwareMonitor/UI/SensorGadget.cs @@ -643,6 +643,9 @@ namespace LibreHardwareMonitor.UI case SensorType.TimeSpan: format = "{0:g}"; break; + case SensorType.Energy: + format = "{0:F0} mWh"; + break; } if (sensor.SensorType == SensorType.Temperature && _unitManager.TemperatureUnit == TemperatureUnit.Fahrenheit) diff --git a/LibreHardwareMonitor/UI/SensorNode.cs b/LibreHardwareMonitor/UI/SensorNode.cs index 2d88343..2016130 100644 --- a/LibreHardwareMonitor/UI/SensorNode.cs +++ b/LibreHardwareMonitor/UI/SensorNode.cs @@ -76,6 +76,9 @@ namespace LibreHardwareMonitor.UI case SensorType.TimeSpan: Format = "{0:g}"; break; + case SensorType.Energy: + Format = "{0:F0} mWh"; + break; } bool hidden = settings.GetValue(new Identifier(sensor.Identifier, "hidden").ToString(), sensor.IsDefaultHidden); diff --git a/LibreHardwareMonitor/UI/SensorNotifyIcon.cs b/LibreHardwareMonitor/UI/SensorNotifyIcon.cs index dac2fbd..7e37319 100644 --- a/LibreHardwareMonitor/UI/SensorNotifyIcon.cs +++ b/LibreHardwareMonitor/UI/SensorNotifyIcon.cs @@ -181,6 +181,7 @@ namespace LibreHardwareMonitor.UI case SensorType.Power: case SensorType.Data: case SensorType.Load: + case SensorType.Energy: return $"{Sensor.Value:F0}"; default: return "-"; @@ -255,6 +256,7 @@ namespace LibreHardwareMonitor.UI case SensorType.Power: format = "\n{0}: {1:F0} W"; break; case SensorType.Data: format = "\n{0}: {1:F0} GB"; break; case SensorType.Factor: format = "\n{0}: {1:F3} GB"; break; + case SensorType.Energy: format = "\n{0}: {0:F0} mWh"; break; } string formattedValue = string.Format(format, Sensor.Name, Sensor.Value); diff --git a/LibreHardwareMonitor/UI/TypeNode.cs b/LibreHardwareMonitor/UI/TypeNode.cs index 183ff0b..a5901d5 100644 --- a/LibreHardwareMonitor/UI/TypeNode.cs +++ b/LibreHardwareMonitor/UI/TypeNode.cs @@ -31,6 +31,10 @@ namespace LibreHardwareMonitor.UI Image = Utilities.EmbeddedResources.GetImage("voltage.png"); Text = "Currents"; break; + case SensorType.Energy: + Image = Utilities.EmbeddedResources.GetImage("battery.png"); + Text = "Capacities"; + break; case SensorType.Clock: Image = Utilities.EmbeddedResources.GetImage("clock.png"); Text = "Clocks"; diff --git a/LibreHardwareMonitor/Utilities/HttpServer.cs b/LibreHardwareMonitor/Utilities/HttpServer.cs index 1b9491d..1540aa5 100644 --- a/LibreHardwareMonitor/Utilities/HttpServer.cs +++ b/LibreHardwareMonitor/Utilities/HttpServer.cs @@ -643,6 +643,8 @@ namespace LibreHardwareMonitor.Utilities return "nic.png"; case HardwareType.Psu: return "power-supply.png"; + case HardwareType.Battery: + return "battery.png"; default: return "cpu.png"; } diff --git a/LibreHardwareMonitorLib/Hardware/Battery/Battery.cs b/LibreHardwareMonitorLib/Hardware/Battery/Battery.cs new file mode 100644 index 0000000..87079a1 --- /dev/null +++ b/LibreHardwareMonitorLib/Hardware/Battery/Battery.cs @@ -0,0 +1,195 @@ +// 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.Runtime.InteropServices; +using LibreHardwareMonitor.Interop; +using Microsoft.Win32.SafeHandles; + +namespace LibreHardwareMonitor.Hardware.Battery +{ + internal sealed class Battery : Hardware + { + private readonly SafeFileHandle _batteryHandle; + private readonly Kernel32.BATTERY_INFORMATION _batteryInformation; + private readonly uint _batteryTag; + private readonly Sensor _chargeDischargeCurrent; + private readonly Sensor _chargeDischargeRate; + private readonly Sensor _chargeLevel; + private readonly Sensor _degradationPercentage; + private readonly Sensor _designedCapacity; + private readonly Sensor _fullChargedCapacity; + private readonly Sensor _remainingCapacity; + private readonly Sensor _voltage; + + public Battery + ( + string name, + string manufacturer, + SafeFileHandle batteryHandle, + Kernel32.BATTERY_INFORMATION batteryInfo, + uint batteryTag, + ISettings settings) : + base(name, new Identifier("battery"), settings) + { + Name = name; + Manufacturer = manufacturer; + + _batteryTag = batteryTag; + _batteryHandle = batteryHandle; + _batteryInformation = batteryInfo; + + if (batteryInfo.Chemistry.SequenceEqual(new[] { 'P', 'b', 'A', 'c' })) + { + Chemistry = BatteryChemistry.LeadAcid; + } + else if (batteryInfo.Chemistry.SequenceEqual(new[] { 'L', 'I', 'O', 'N' }) || batteryInfo.Chemistry.SequenceEqual(new[] { 'L', 'i', '-', 'I' })) + { + Chemistry = BatteryChemistry.LithiumIon; + } + else if (batteryInfo.Chemistry.SequenceEqual(new[] { 'N', 'i', 'C', 'd' })) + { + Chemistry = BatteryChemistry.NickelCadmium; + } + else if (batteryInfo.Chemistry.SequenceEqual(new[] { 'N', 'i', 'M', 'H' })) + { + Chemistry = BatteryChemistry.NickelMetalHydride; + } + else if (batteryInfo.Chemistry.SequenceEqual(new[] { 'N', 'i', 'Z', 'n' })) + { + Chemistry = BatteryChemistry.NickelZinc; + } + else if (batteryInfo.Chemistry.SequenceEqual(new[] { 'R', 'A', 'M', '\x00' })) + { + Chemistry = BatteryChemistry.AlkalineManganese; + } + else + { + Chemistry = BatteryChemistry.Unknown; + } + + DegradationLevel = 100f - (batteryInfo.FullChargedCapacity * 100f / batteryInfo.DesignedCapacity); + DesignedCapacity = batteryInfo.DesignedCapacity; + FullChargedCapacity = batteryInfo.FullChargedCapacity; + + _chargeLevel = new Sensor("Charge Level", 0, SensorType.Level, this, settings); + ActivateSensor(_chargeLevel); + + _voltage = new Sensor("Voltage", 1, SensorType.Voltage, this, settings); + ActivateSensor(_voltage); + + _chargeDischargeCurrent = new Sensor("Current", 2, SensorType.Current, this, settings); + ActivateSensor(_chargeDischargeCurrent); + + _designedCapacity = new Sensor("Designed Capacity", 3, SensorType.Energy, this, settings); + ActivateSensor(_designedCapacity); + + _fullChargedCapacity = new Sensor("Full Charged Capacity", 4, SensorType.Energy, this, settings); + ActivateSensor(_fullChargedCapacity); + + _remainingCapacity = new Sensor("Remaining Capacity", 5, SensorType.Energy, this, settings); + ActivateSensor(_remainingCapacity); + + _chargeDischargeRate = new Sensor("Charge/Discharge Rate", 0, SensorType.Power, this, settings); + ActivateSensor(_chargeDischargeRate); + + _degradationPercentage = new Sensor("Degradation Level", 0, SensorType.Level, this, settings); + ActivateSensor(_degradationPercentage); + } + + public float ChargeDischargeCurrent { get; private set; } + + public float ChargeDischargeRate { get; private set; } + + public float ChargeLevel { get; private set; } + + public BatteryChemistry Chemistry { get; } + + public float DegradationLevel { get; } + + public float DesignedCapacity { get; } + + public float FullChargedCapacity { get; } + + public override HardwareType HardwareType => HardwareType.Battery; + + public string Manufacturer { get; } + + public float RemainingCapacity { get; private set; } + + public float Voltage { get; private set; } + + public override void Update() + { + Kernel32.BATTERY_WAIT_STATUS bws = default; + bws.BatteryTag = _batteryTag; + Kernel32.BATTERY_STATUS batteryStatus = default; + if (Kernel32.DeviceIoControl(_batteryHandle, + Kernel32.IOCTL.IOCTL_BATTERY_QUERY_STATUS, + ref bws, + Marshal.SizeOf(bws), + ref batteryStatus, + Marshal.SizeOf(batteryStatus), + out _, + IntPtr.Zero)) + { + _designedCapacity.Value = Convert.ToSingle(_batteryInformation.DesignedCapacity); + _fullChargedCapacity.Value = Convert.ToSingle(_batteryInformation.FullChargedCapacity); + + _remainingCapacity.Value = Convert.ToSingle(batteryStatus.Capacity); + RemainingCapacity = Convert.ToSingle(batteryStatus.Capacity); + + _voltage.Value = Convert.ToSingle(batteryStatus.Voltage) / 1000f; + Voltage = Convert.ToSingle(batteryStatus.Voltage) / 1000f; + + _chargeLevel.Value = _remainingCapacity.Value * 100f / _fullChargedCapacity.Value; + ChargeLevel = (_remainingCapacity.Value * 100f / _fullChargedCapacity.Value).GetValueOrDefault(); + + ChargeDischargeRate = batteryStatus.Rate / 1000f; + + switch (batteryStatus.Rate) + { + case > 0: + _chargeDischargeRate.Name = "Charge Rate"; + _chargeDischargeRate.Value = batteryStatus.Rate / 1000f; + + _chargeDischargeCurrent.Name = "Charge Current"; + _chargeDischargeCurrent.Value = _chargeDischargeRate.Value / _voltage.Value; + ChargeDischargeCurrent = (_chargeDischargeRate.Value / _voltage.Value).GetValueOrDefault(); + + break; + case < 0: + _chargeDischargeRate.Name = "Discharge Rate"; + _chargeDischargeRate.Value = Math.Abs(batteryStatus.Rate) / 1000f; + + _chargeDischargeCurrent.Name = "Discharge Current"; + _chargeDischargeCurrent.Value = _chargeDischargeRate.Value / _voltage.Value; + ChargeDischargeCurrent = (_chargeDischargeRate.Value / _voltage.Value).GetValueOrDefault(); + + break; + default: + _chargeDischargeRate.Name = "Charge/Discharge Rate"; + _chargeDischargeRate.Value = 0f; + ChargeDischargeRate = 0f; + + _chargeDischargeCurrent.Name = "Charge/Discharge Current"; + _chargeDischargeCurrent.Value = 0f; + ChargeDischargeCurrent = 0f; + + break; + } + + _degradationPercentage.Value = 100f - (_fullChargedCapacity.Value * 100f / _designedCapacity.Value); + } + } + + public override void Close() + { + base.Close(); + _batteryHandle.Close(); + } + } +} diff --git a/LibreHardwareMonitorLib/Hardware/Battery/BatteryChemistry.cs b/LibreHardwareMonitorLib/Hardware/Battery/BatteryChemistry.cs new file mode 100644 index 0000000..5f355c7 --- /dev/null +++ b/LibreHardwareMonitorLib/Hardware/Battery/BatteryChemistry.cs @@ -0,0 +1,17 @@ +// 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.Battery; + +internal enum BatteryChemistry +{ + Unknown, + LeadAcid, + NickelCadmium, + NickelMetalHydride, + LithiumIon, + NickelZinc, + AlkalineManganese +} diff --git a/LibreHardwareMonitorLib/Hardware/Battery/BatteryGroup.cs b/LibreHardwareMonitorLib/Hardware/Battery/BatteryGroup.cs new file mode 100644 index 0000000..bb7c173 --- /dev/null +++ b/LibreHardwareMonitorLib/Hardware/Battery/BatteryGroup.cs @@ -0,0 +1,227 @@ +// 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.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using LibreHardwareMonitor.Interop; +using Microsoft.Win32.SafeHandles; + +namespace LibreHardwareMonitor.Hardware.Battery +{ + internal class BatteryGroup : IGroup + { + private readonly List _hardware = new(); + + public unsafe BatteryGroup(ISettings settings) + { + // No implementation for battery information on Unix systems + if (Software.OperatingSystem.IsUnix) + return; + + IntPtr hdev = SetupApi.SetupDiGetClassDevs(ref SetupApi.GUID_DEVICE_BATTERY, IntPtr.Zero, IntPtr.Zero, SetupApi.DIGCF_PRESENT | SetupApi.DIGCF_DEVICEINTERFACE); + if (hdev != SetupApi.INVALID_HANDLE_VALUE) + { + for (uint i = 0;; i++) + { + SetupApi.SP_DEVICE_INTERFACE_DATA did = default; + did.cbSize = (uint)Marshal.SizeOf(typeof(SetupApi.SP_DEVICE_INTERFACE_DATA)); + + if (!SetupApi.SetupDiEnumDeviceInterfaces(hdev, + IntPtr.Zero, + ref SetupApi.GUID_DEVICE_BATTERY, + i, + ref did)) + { + if (Marshal.GetLastWin32Error() == SetupApi.ERROR_NO_MORE_ITEMS) + break; + } + else + { + SetupApi.SetupDiGetDeviceInterfaceDetail(hdev, + did, + IntPtr.Zero, + 0, + out uint cbRequired, + IntPtr.Zero); + + if (Marshal.GetLastWin32Error() == SetupApi.ERROR_INSUFFICIENT_BUFFER) + { + IntPtr pdidd = Kernel32.LocalAlloc(Kernel32.LPTR, cbRequired); + Marshal.WriteInt32(pdidd, Environment.Is64BitOperatingSystem ? 8 : 4); // cbSize. + + if (SetupApi.SetupDiGetDeviceInterfaceDetail(hdev, + did, + pdidd, + cbRequired, + out _, + IntPtr.Zero)) + { + string devicePath = new((char*)(pdidd + 4)); + + SafeFileHandle battery = Kernel32.CreateFile(devicePath, FileAccess.ReadWrite, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero); + if (!battery.IsInvalid) + { + Kernel32.BATTERY_QUERY_INFORMATION bqi = default; + + uint dwWait = 0; + if (Kernel32.DeviceIoControl(battery, + Kernel32.IOCTL.IOCTL_BATTERY_QUERY_TAG, + ref dwWait, + Marshal.SizeOf(dwWait), + ref bqi.BatteryTag, + Marshal.SizeOf(bqi.BatteryTag), + out _, + IntPtr.Zero)) + { + Kernel32.BATTERY_INFORMATION bi = default; + bqi.InformationLevel = Kernel32.BATTERY_QUERY_INFORMATION_LEVEL.BatteryInformation; + + if (Kernel32.DeviceIoControl(battery, + Kernel32.IOCTL.IOCTL_BATTERY_QUERY_INFORMATION, + ref bqi, + Marshal.SizeOf(bqi), + ref bi, + Marshal.SizeOf(bi), + out _, + IntPtr.Zero)) + { + // Only batteries count. + if (bi.Capabilities == Kernel32.BatteryCapabilities.BATTERY_SYSTEM_BATTERY) + { + const int maxLoadString = 100; + + IntPtr ptrDevName = Marshal.AllocCoTaskMem(maxLoadString); + bqi.InformationLevel = Kernel32.BATTERY_QUERY_INFORMATION_LEVEL.BatteryDeviceName; + + if (Kernel32.DeviceIoControl(battery, + Kernel32.IOCTL.IOCTL_BATTERY_QUERY_INFORMATION, + ref bqi, + Marshal.SizeOf(bqi), + ptrDevName, + maxLoadString, + out _, + IntPtr.Zero)) + { + IntPtr ptrManName = Marshal.AllocCoTaskMem(maxLoadString); + bqi.InformationLevel = Kernel32.BATTERY_QUERY_INFORMATION_LEVEL.BatteryManufactureName; + + if (Kernel32.DeviceIoControl(battery, + Kernel32.IOCTL.IOCTL_BATTERY_QUERY_INFORMATION, + ref bqi, + Marshal.SizeOf(bqi), + ptrManName, + maxLoadString, + out _, + IntPtr.Zero)) + { + string name = Marshal.PtrToStringUni(ptrDevName); + string manufacturer = Marshal.PtrToStringUni(ptrManName); + + _hardware.Add(new Battery(name, manufacturer, battery, bi, bqi.BatteryTag, settings)); + } + + Marshal.FreeCoTaskMem(ptrManName); + } + + Marshal.FreeCoTaskMem(ptrDevName); + } + } + } + } + } + + Kernel32.LocalFree(pdidd); + } + } + } + + SetupApi.SetupDiDestroyDeviceInfoList(hdev); + } + } + + /// + public IReadOnlyList Hardware => _hardware; + + /// + public void Close() + { + foreach (Battery battery in _hardware) + battery.Close(); + } + + /// + public string GetReport() + { + StringBuilder reportBuilder = new(); + + uint count = 1; + + foreach (Battery bat in _hardware) + { + string chemistry = "Unknown"; + + switch (bat.Chemistry) + { + case BatteryChemistry.LeadAcid: + chemistry = "Lead Acid"; + break; + case BatteryChemistry.NickelCadmium: + chemistry = "Nickel-Cadmium"; + break; + case BatteryChemistry.NickelMetalHydride: + chemistry = "Nickel-Metal Hydride"; + break; + case BatteryChemistry.LithiumIon: + chemistry = "Lithium Ion"; + break; + case BatteryChemistry.NickelZinc: + chemistry = "Nickel-Zinc"; + break; + case BatteryChemistry.AlkalineManganese: + chemistry = "Rechargeable Alkaline-Manganese"; + break; + } + + reportBuilder.Append("Battery #").Append(count).AppendLine(":") + .Append(" Name: ").AppendLine(bat.Name) + .Append(" Manufacturer: ").AppendLine(bat.Manufacturer) + .Append(" Chemistry: ").AppendLine(chemistry) + .Append(" Degradation Level: ").AppendFormat("{0:F2}", bat.DegradationLevel).AppendLine(" %") + .Append(" Designed Capacity: ").Append(bat.DesignedCapacity).AppendLine(" mWh") + .Append(" Full Charged Capacity: ").Append(bat.FullChargedCapacity).AppendLine(" mWh") + .Append(" Remaining Capacity: ").Append(bat.RemainingCapacity).AppendLine(" mWh") + .Append(" Charge Level: ").AppendFormat("{0:F2}", bat.RemainingCapacity * 100f / bat.FullChargedCapacity).AppendLine(" %") + .Append(" Voltage: ").AppendFormat("{0:F3}", bat.Voltage).AppendLine(" V"); + + switch (bat.ChargeDischargeRate) + { + case > 0: + reportBuilder.Append(" Charge Rate: ").AppendFormat("{0:F1}", bat.ChargeDischargeRate).AppendLine(" W") + .Append(" Charge Current: ").AppendFormat("{0:F3}", bat.ChargeDischargeRate / bat.Voltage).AppendLine(" A"); + + break; + case < 0: + reportBuilder.Append(" Discharge Rate: ").AppendFormat("{0:F1}", Math.Abs(bat.ChargeDischargeRate)).AppendLine(" W") + .Append(" Discharge Current: ").AppendFormat("{0:F3}", Math.Abs(bat.ChargeDischargeRate) / bat.Voltage).AppendLine(" A"); + + break; + default: + reportBuilder.AppendLine(" Charge/Discharge Rate: 0 W") + .AppendLine(" Charge/Discharge Current: 0 A"); + + break; + } + + reportBuilder.AppendLine(); + count++; + } + + return reportBuilder.ToString(); + } + } +} diff --git a/LibreHardwareMonitorLib/Hardware/Computer.cs b/LibreHardwareMonitorLib/Hardware/Computer.cs index da48e94..0ea3bde 100644 --- a/LibreHardwareMonitorLib/Hardware/Computer.cs +++ b/LibreHardwareMonitorLib/Hardware/Computer.cs @@ -8,7 +8,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Security.Permissions; +using LibreHardwareMonitor.Hardware.Battery; using LibreHardwareMonitor.Hardware.Controller.AeroCool; using LibreHardwareMonitor.Hardware.Controller.AquaComputer; using LibreHardwareMonitor.Hardware.Controller.Heatmaster; @@ -28,16 +28,11 @@ namespace LibreHardwareMonitor.Hardware /// public class Computer : IComputer { - /// - public event HardwareEventHandler HardwareAdded; - - /// - public event HardwareEventHandler HardwareRemoved; - - private readonly object _lock = new object(); - private readonly List _groups = new List(); + private readonly List _groups = new(); + private readonly object _lock = new(); private readonly ISettings _settings; - + + private bool _batteryEnabled; private bool _controllerEnabled; private bool _cpuEnabled; private bool _gpuEnabled; @@ -45,13 +40,12 @@ namespace LibreHardwareMonitor.Hardware private bool _motherboardEnabled; private bool _networkEnabled; private bool _open; - private bool _storageEnabled; private bool _psuEnabled; - private SMBios _smbios; + private bool _storageEnabled; /// - /// Creates a new instance with basic initial . + /// Creates a new instance with basic initial . /// public Computer() { @@ -59,14 +53,20 @@ namespace LibreHardwareMonitor.Hardware } /// - /// Creates a new instance with additional . + /// Creates a new instance with additional . /// - /// Computer settings that will be transferred to each . + /// Computer settings that will be transferred to each . public Computer(ISettings settings) { _settings = settings ?? new Settings(); } + /// + public event HardwareEventHandler HardwareAdded; + + /// + public event HardwareEventHandler HardwareRemoved; + /// public IList Hardware { @@ -74,7 +74,7 @@ namespace LibreHardwareMonitor.Hardware { lock (_lock) { - List list = new List(); + List list = new(); foreach (IGroup group in _groups) list.AddRange(group.Hardware); @@ -84,35 +84,25 @@ namespace LibreHardwareMonitor.Hardware } } - /// - /// Contains computer information table read in accordance with System Management BIOS (SMBIOS) Reference Specification. - /// - public SMBios SMBios - { - get - { - if (!_open) - throw new InvalidOperationException("SMBIOS cannot be accessed before opening."); - - return _smbios; - } - } - /// - public bool IsCpuEnabled + public bool IsBatteryEnabled { - get { return _cpuEnabled; } + get { return _batteryEnabled; } set { - if (_open && value != _cpuEnabled) + if (_open && value != _batteryEnabled) { if (value) - Add(new CPU.CpuGroup(_settings)); + { + Add(new BatteryGroup(_settings)); + } else - RemoveType(); + { + RemoveType(); + } } - _cpuEnabled = value; + _batteryEnabled = value; } } @@ -146,6 +136,24 @@ namespace LibreHardwareMonitor.Hardware } } + /// + public bool IsCpuEnabled + { + get { return _cpuEnabled; } + set + { + if (_open && value != _cpuEnabled) + { + if (value) + Add(new CPU.CpuGroup(_settings)); + else + RemoveType(); + } + + _cpuEnabled = value; + } + } + /// public bool IsGpuEnabled { @@ -224,6 +232,28 @@ namespace LibreHardwareMonitor.Hardware } } + /// + public bool IsPsuEnabled + { + get { return _psuEnabled; } + set + { + if (_open && value != _psuEnabled) + { + if (value) + { + Add(new CorsairPsuGroup(_settings)); + } + else + { + RemoveType(); + } + } + + _psuEnabled = value; + } + } + /// public bool IsStorageEnabled { @@ -242,24 +272,17 @@ namespace LibreHardwareMonitor.Hardware } } - /// - public bool IsPsuEnabled + /// + /// Contains computer information table read in accordance with System Management BIOS (SMBIOS) Reference Specification. + /// + public SMBios SMBios { - get { return _psuEnabled; } - set + get { - if (_open && value != _psuEnabled) - { - if (value) - { - Add(new CorsairPsuGroup(_settings)); - } - else - { - RemoveType(); - } - } - _psuEnabled = value; + if (!_open) + throw new InvalidOperationException("SMBIOS cannot be accessed before opening."); + + return _smbios; } } @@ -268,7 +291,7 @@ namespace LibreHardwareMonitor.Hardware { lock (_lock) { - using StringWriter w = new StringWriter(CultureInfo.InvariantCulture); + using StringWriter w = new(CultureInfo.InvariantCulture); w.WriteLine(); w.WriteLine(nameof(LibreHardwareMonitor) + " Report"); @@ -341,7 +364,7 @@ namespace LibreHardwareMonitor.Hardware } /// - /// Triggers the method for the given observer. + /// Triggers the method for the given observer. /// /// Observer who call to devices. public void Accept(IVisitor visitor) @@ -349,12 +372,11 @@ namespace LibreHardwareMonitor.Hardware if (visitor == null) throw new ArgumentNullException(nameof(visitor)); - visitor.VisitComputer(this); } /// - /// Triggers the method with the given visitor for each device in each group. + /// Triggers the method with the given visitor for each device in each group. /// /// Observer who call to devices. public void Traverse(IVisitor visitor) @@ -387,13 +409,11 @@ namespace LibreHardwareMonitor.Hardware if (group == null) return; - lock (_lock) { if (_groups.Contains(group)) return; - _groups.Add(group); if (group is IHardwareChanged hardwareChanged) @@ -417,7 +437,6 @@ namespace LibreHardwareMonitor.Hardware if (!_groups.Contains(group)) return; - _groups.Remove(group); if (group is IHardwareChanged hardwareChanged) @@ -438,7 +457,7 @@ namespace LibreHardwareMonitor.Hardware private void RemoveType() where T : IGroup { - List list = new List(); + List list = new(); lock (_lock) { @@ -454,14 +473,14 @@ namespace LibreHardwareMonitor.Hardware } /// - /// If hasn't been opened before, opens , , and triggers the private method depending on which categories are enabled. + /// If hasn't been opened before, opens , , and triggers the private method depending on which categories are + /// enabled. /// public void Open() { if (_open) return; - _smbios = new SMBios(); Ring0.Open(); @@ -506,6 +525,9 @@ namespace LibreHardwareMonitor.Hardware if (_psuEnabled) Add(new CorsairPsuGroup(_settings)); + + if (_batteryEnabled) + Add(new BatteryGroup(_settings)); } private static void NewSection(TextWriter writer) @@ -523,7 +545,6 @@ namespace LibreHardwareMonitor.Hardware if (c == 0) return a.Index.CompareTo(b.Index); - return c; } @@ -584,14 +605,13 @@ namespace LibreHardwareMonitor.Hardware } /// - /// If opened before, removes all and triggers , and . + /// If opened before, removes all and triggers , and . /// public void Close() { if (!_open) return; - lock (_lock) { while (_groups.Count > 0) @@ -610,14 +630,13 @@ namespace LibreHardwareMonitor.Hardware } /// - /// If opened before, removes all and recreates it. + /// If opened before, removes all and recreates it. /// public void Reset() { if (!_open) return; - RemoveGroups(); AddGroups(); } @@ -635,7 +654,7 @@ namespace LibreHardwareMonitor.Hardware } /// - /// specific additional settings passed to its . + /// specific additional settings passed to its . /// private class Settings : ISettings { diff --git a/LibreHardwareMonitorLib/Hardware/HardwareType.cs b/LibreHardwareMonitorLib/Hardware/HardwareType.cs index 6db7aa2..689a5e2 100644 --- a/LibreHardwareMonitorLib/Hardware/HardwareType.cs +++ b/LibreHardwareMonitorLib/Hardware/HardwareType.cs @@ -21,6 +21,7 @@ namespace LibreHardwareMonitor.Hardware Network, Cooler, EmbeddedController, - Psu + Psu, + Battery } } diff --git a/LibreHardwareMonitorLib/Hardware/IComputer.cs b/LibreHardwareMonitorLib/Hardware/IComputer.cs index 1b40a38..947db04 100644 --- a/LibreHardwareMonitorLib/Hardware/IComputer.cs +++ b/LibreHardwareMonitorLib/Hardware/IComputer.cs @@ -20,77 +20,93 @@ namespace LibreHardwareMonitor.Hardware public interface IComputer : IElement { /// - /// Gets or sets a value indicating whether collecting information about devices should be enabled and updated. + /// Triggered when a new is registered. /// - /// if a given category of devices is already enabled. - bool IsCpuEnabled { get; } + event HardwareEventHandler HardwareAdded; /// - /// Gets or sets a value indicating whether collecting information about: - /// - /// - /// - /// - /// - /// - /// - /// devices should be enabled and updated. + /// Triggered when a is removed. /// - /// if a given category of devices is already enabled. - bool IsControllerEnabled { get; } + event HardwareEventHandler HardwareRemoved; /// - /// Gets or sets a value indicating whether collecting information about or devices should be enabled and updated. - /// - /// if a given category of devices is already enabled. - bool IsGpuEnabled { get; } - - /// - /// Gets or sets a value indicating whether collecting information about devices should be enabled and updated. - /// - /// if a given category of devices is already enabled. - bool IsStorageEnabled { get; } - - /// - /// Gets or sets a value indicating whether collecting information about devices should be enabled and updated. - /// - /// if a given category of devices is already enabled. - bool IsMotherboardEnabled { get; } - - /// - /// Gets or sets a value indicating whether collecting information about devices should be enabled and updated. - /// - /// if a given category of devices is already enabled. - bool IsNetworkEnabled { get; } - - /// - /// Gets or sets a value indicating whether collecting information about devices should be enabled and updated. - /// - /// if a given category of devices is already enabled. - bool IsMemoryEnabled { get; } - - /// - /// Gets or sets a value indicating whether collecting information about devices should be enabled and updated. - /// - /// if a given category of devices is already enabled. - bool IsPsuEnabled { get; } - - /// - /// Gets a list of all known . - /// Can be updated by . + /// Gets a list of all known . + /// Can be updated by . /// /// List of all enabled devices. IList Hardware { get; } /// - /// Triggered when a new is registered. + /// Gets or sets a value indicating whether collecting information about devices should be enabled and updated. /// - event HardwareEventHandler HardwareAdded; + /// if a given category of devices is already enabled. + bool IsBatteryEnabled { get; } /// - /// Triggered when a is removed. + /// Gets or sets a value indicating whether collecting information about: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// devices should be enabled and updated. /// - event HardwareEventHandler HardwareRemoved; + /// if a given category of devices is already enabled. + bool IsControllerEnabled { get; } + + /// + /// Gets or sets a value indicating whether collecting information about devices should be enabled and updated. + /// + /// if a given category of devices is already enabled. + bool IsCpuEnabled { get; } + + /// + /// Gets or sets a value indicating whether collecting information about or devices should be enabled and updated. + /// + /// if a given category of devices is already enabled. + bool IsGpuEnabled { get; } + + /// + /// Gets or sets a value indicating whether collecting information about devices should be enabled and updated. + /// + /// if a given category of devices is already enabled. + bool IsMemoryEnabled { get; } + + /// + /// Gets or sets a value indicating whether collecting information about devices should be enabled and updated. + /// + /// if a given category of devices is already enabled. + bool IsMotherboardEnabled { get; } + + /// + /// Gets or sets a value indicating whether collecting information about devices should be enabled and updated. + /// + /// if a given category of devices is already enabled. + bool IsNetworkEnabled { get; } + + /// + /// Gets or sets a value indicating whether collecting information about devices should be enabled and updated. + /// + /// if a given category of devices is already enabled. + bool IsPsuEnabled { get; } + + /// + /// Gets or sets a value indicating whether collecting information about devices should be enabled and updated. + /// + /// if a given category of devices is already enabled. + bool IsStorageEnabled { get; } /// /// Generates full LibreHardwareMonitor report for devices that have been enabled. diff --git a/LibreHardwareMonitorLib/Hardware/ISensor.cs b/LibreHardwareMonitorLib/Hardware/ISensor.cs index ee19f77..20223d9 100644 --- a/LibreHardwareMonitorLib/Hardware/ISensor.cs +++ b/LibreHardwareMonitorLib/Hardware/ISensor.cs @@ -30,6 +30,7 @@ namespace LibreHardwareMonitor.Hardware SmallData, // MB = 2^20 Bytes Throughput, // B/s TimeSpan, // Seconds + Energy // milliwatt-hour (mWh) } /// diff --git a/LibreHardwareMonitorLib/Interop/Kernel32.cs b/LibreHardwareMonitorLib/Interop/Kernel32.cs index 54b1048..44c50ee 100644 --- a/LibreHardwareMonitorLib/Interop/Kernel32.cs +++ b/LibreHardwareMonitorLib/Interop/Kernel32.cs @@ -26,6 +26,9 @@ namespace LibreHardwareMonitor.Interop internal const byte SMART_LBA_MID = 0x4F; internal const byte SMART_LBA_HI_EXCEEDED = 0x2C; internal const byte SMART_LBA_MID_EXCEEDED = 0xF4; + + internal const uint LPTR = 0x0000 | 0x0040; + private const string DllName = "kernel32.dll"; [Flags] @@ -203,6 +206,64 @@ namespace LibreHardwareMonitor.Interop out uint lpBytesReturned, IntPtr lpOverlapped); + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl + ( + SafeFileHandle hDevice, + IOCTL dwIoControlCode, + ref BATTERY_QUERY_INFORMATION lpInBuffer, + int nInBufferSize, + ref BATTERY_INFORMATION lpOutBuffer, + int nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl + ( + SafeFileHandle hDevice, + IOCTL dwIoControlCode, + ref uint lpInBuffer, + int nInBufferSize, + ref uint lpOutBuffer, + int nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl + ( + SafeFileHandle hDevice, + IOCTL dwIoControlCode, + ref BATTERY_WAIT_STATUS lpInBuffer, + int nInBufferSize, + ref BATTERY_STATUS lpOutBuffer, + int nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl + ( + SafeFileHandle hDevice, + IOCTL dwIoControlCode, + ref BATTERY_QUERY_INFORMATION lpInBuffer, + int nInBufferSize, + IntPtr lpOutBuffer, + int nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi)] + internal static extern IntPtr LocalAlloc(uint uFlags, ulong uBytes); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi)] + internal static extern IntPtr LocalFree(IntPtr hMem); + [DllImport(DllName, SetLastError = true)] internal static extern void RtlZeroMemory(IntPtr Destination, int Length); @@ -291,7 +352,80 @@ namespace LibreHardwareMonitor.Interop IOCTL_SCSI_MINIPORT = 0x04d008, IOCTL_SCSI_PASS_THROUGH_DIRECT = 0x04d014, IOCTL_SCSI_GET_ADDRESS = 0x41018, - IOCTL_STORAGE_QUERY_PROPERTY = 0x2D1400 + IOCTL_STORAGE_QUERY_PROPERTY = 0x2D1400, + IOCTL_BATTERY_QUERY_TAG = 0x294040, + IOCTL_BATTERY_QUERY_INFORMATION = 0x294044, + IOCTL_BATTERY_QUERY_STATUS = 0x29404C + } + + [Flags] + internal enum BatteryCapabilities : uint + { + BATTERY_CAPACITY_RELATIVE = 0x40000000, + BATTERY_IS_SHORT_TERM = 0x20000000, + BATTERY_SET_CHARGE_SUPPORTED = 0x00000001, + BATTERY_SET_DISCHARGE_SUPPORTED = 0x00000002, + BATTERY_SYSTEM_BATTERY = 0x80000000 + } + + internal enum BATTERY_QUERY_INFORMATION_LEVEL + { + BatteryInformation, + BatteryGranularityInformation, + BatteryTemperature, + BatteryEstimatedTime, + BatteryDeviceName, + BatteryManufactureDate, + BatteryManufactureName, + BatteryUniqueID, + BatterySerialNumber + } + + [StructLayout(LayoutKind.Sequential)] + internal struct BATTERY_QUERY_INFORMATION + { + public uint BatteryTag; + public BATTERY_QUERY_INFORMATION_LEVEL InformationLevel; + public uint AtRate; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct BATTERY_INFORMATION + { + public BatteryCapabilities Capabilities; + public byte Technology; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public byte[] Reserved; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public char[] Chemistry; + + public uint DesignedCapacity; + public uint FullChargedCapacity; + public uint DefaultAlert1; + public uint DefaultAlert2; + public uint CriticalBias; + public uint CycleCount; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct BATTERY_WAIT_STATUS + { + public uint BatteryTag; + public uint Timeout; + public uint PowerState; + public uint LowCapacity; + public uint HighCapacity; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct BATTERY_STATUS + { + public uint PowerState; + public uint Capacity; + public uint Voltage; + public int Rate; } [Flags] diff --git a/LibreHardwareMonitorLib/Interop/SetupApi.cs b/LibreHardwareMonitorLib/Interop/SetupApi.cs new file mode 100644 index 0000000..a7c49c6 --- /dev/null +++ b/LibreHardwareMonitorLib/Interop/SetupApi.cs @@ -0,0 +1,50 @@ +// 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.Runtime.InteropServices; + +namespace LibreHardwareMonitor.Interop +{ + internal class SetupApi + { + internal const int ERROR_INSUFFICIENT_BUFFER = 122; + internal const int ERROR_NO_MORE_ITEMS = 259; + internal const int DIGCF_PRESENT = 0x00000002; + internal const int DIGCF_DEVICEINTERFACE = 0x00000010; + internal static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); + internal static Guid GUID_DEVICE_BATTERY = new Guid(0x72631e54, 0x78A4, 0x11d0, 0xbc, 0xf7, 0x00, 0xaa, 0x00, 0xb7, 0xb3, 0x2a); + private const string DllName = "SetupAPI.dll"; + + [StructLayout(LayoutKind.Sequential)] + internal struct SP_DEVICE_INTERFACE_DATA + { + public uint cbSize; + public Guid InterfaceClassGuid; + public uint Flags; + public IntPtr Reserved; + } + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, SetLastError = true)] + internal static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, IntPtr Enumerator, IntPtr hwndParent, uint Flags); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SetupDiEnumDeviceInterfaces(IntPtr DeviceInfoSet, IntPtr DeviceInfoData, ref Guid InterfaceClassGuid, uint MemberIndex, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData); + + [DllImport(DllName, SetLastError = true, EntryPoint = "SetupDiGetDeviceInterfaceDetailW", CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, + in SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, + [Out, Optional] IntPtr DeviceInterfaceDetailData, + uint DeviceInterfaceDetailDataSize, + out uint RequiredSize, + IntPtr DeviceInfoData = default); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet); + } +}