first commit

This commit is contained in:
2025-04-07 07:44:27 -07:00
commit d6cde0c05e
512 changed files with 142392 additions and 0 deletions

View File

@@ -0,0 +1,256 @@
// 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 _degradationLevel;
private readonly Sensor _designedCapacity;
private readonly Sensor _fullChargedCapacity;
private readonly Sensor _remainingCapacity;
private readonly Sensor _remainingTime;
private readonly Sensor _temperature;
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", $"{name.Replace(' ', '-')}_{batteryTag}"), 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;
}
_designedCapacity = new Sensor("Designed Capacity", 0, SensorType.Energy, this, settings);
_fullChargedCapacity = new Sensor("Fully-Charged Capacity", 1, SensorType.Energy, this, settings);
_degradationLevel = new Sensor("Degradation Level", 1, SensorType.Level, this, settings);
_chargeLevel = new Sensor("Charge Level", 0, SensorType.Level, this, settings);
_voltage = new Sensor("Voltage", 0, SensorType.Voltage, this, settings);
_remainingCapacity = new Sensor("Remaining Capacity", 2, SensorType.Energy, this, settings);
_chargeDischargeCurrent = new Sensor("Charge/Discharge Current", 0, SensorType.Current, this, settings);
_chargeDischargeRate = new Sensor("Charge/Discharge Rate", 0, SensorType.Power, this, settings);
_remainingTime = new Sensor("Remaining Time (Estimated)", 0, SensorType.TimeSpan, this, settings);
_temperature = new Sensor("Battery Temperature", 0, SensorType.Temperature, this, settings);
if (batteryInfo.FullChargedCapacity is not Kernel32.BATTERY_UNKNOWN_CAPACITY &&
batteryInfo.DesignedCapacity is not Kernel32.BATTERY_UNKNOWN_CAPACITY)
{
_designedCapacity.Value = batteryInfo.DesignedCapacity;
_fullChargedCapacity.Value = batteryInfo.FullChargedCapacity;
_degradationLevel.Value = 100f - (batteryInfo.FullChargedCapacity * 100f / batteryInfo.DesignedCapacity);
DesignedCapacity = batteryInfo.DesignedCapacity;
FullChargedCapacity = batteryInfo.FullChargedCapacity;
ActivateSensor(_designedCapacity);
ActivateSensor(_fullChargedCapacity);
ActivateSensor(_degradationLevel);
}
}
public float? ChargeDischargeCurrent { get; private set; }
public float? ChargeDischargeRate { get; private set; }
public float? ChargeLevel => _chargeLevel.Value;
public BatteryChemistry Chemistry { get; }
public float? DegradationLevel => _degradationLevel.Value;
public float? DesignedCapacity { get; }
public float? FullChargedCapacity { get; }
public override HardwareType HardwareType => HardwareType.Battery;
public string Manufacturer { get; }
public float? RemainingCapacity => _remainingCapacity.Value;
public float? RemainingTime => _remainingTime.Value;
public float? Temperature => _temperature.Value;
public float? Voltage => _voltage.Value;
private void ActivateSensorIfValueNotNull(ISensor sensor)
{
if (sensor.Value != null)
ActivateSensor(sensor);
else
DeactivateSensor(sensor);
}
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))
{
if (batteryStatus.Capacity != Kernel32.BATTERY_UNKNOWN_CAPACITY)
_remainingCapacity.Value = batteryStatus.Capacity;
else
_remainingCapacity.Value = null;
_chargeLevel.Value = _remainingCapacity.Value * 100f / _fullChargedCapacity.Value;
if (batteryStatus.Voltage is not Kernel32.BATTERY_UNKNOWN_VOLTAGE)
_voltage.Value = batteryStatus.Voltage / 1000f;
else
_voltage.Value = null;
if (batteryStatus.Rate is Kernel32.BATTERY_UNKNOWN_RATE)
{
ChargeDischargeCurrent = null;
_chargeDischargeCurrent.Value = null;
ChargeDischargeRate = null;
_chargeDischargeRate.Value = null;
}
else
{
float rateWatts = batteryStatus.Rate / 1000f;
ChargeDischargeRate = rateWatts;
_chargeDischargeRate.Value = Math.Abs(rateWatts);
float? current = rateWatts / _voltage.Value;
ChargeDischargeCurrent = current;
if (current is not null)
_chargeDischargeCurrent.Value = Math.Abs(current.Value);
else
_chargeDischargeCurrent.Value = null;
if (rateWatts > 0)
{
_chargeDischargeRate.Name = "Charge Rate";
_chargeDischargeCurrent.Name = "Charge Current";
}
else if (rateWatts < 0)
{
_chargeDischargeRate.Name = "Discharge Rate";
_chargeDischargeCurrent.Name = "Discharge Current";
}
else
{
_chargeDischargeRate.Name = "Charge/Discharge Rate";
_chargeDischargeCurrent.Name = "Charge/Discharge Current";
}
}
}
uint estimatedRunTime = 0;
Kernel32.BATTERY_QUERY_INFORMATION bqi = default;
bqi.BatteryTag = _batteryTag;
bqi.InformationLevel = Kernel32.BATTERY_QUERY_INFORMATION_LEVEL.BatteryEstimatedTime;
if (Kernel32.DeviceIoControl(_batteryHandle,
Kernel32.IOCTL.IOCTL_BATTERY_QUERY_INFORMATION,
ref bqi,
Marshal.SizeOf(bqi),
ref estimatedRunTime,
Marshal.SizeOf<uint>(),
out _,
IntPtr.Zero))
{
if (estimatedRunTime != Kernel32.BATTERY_UNKNOWN_TIME)
_remainingTime.Value = estimatedRunTime;
else
_remainingTime.Value = null;
}
else
{
_remainingTime.Value = null;
}
uint temperature = 0;
bqi.InformationLevel = Kernel32.BATTERY_QUERY_INFORMATION_LEVEL.BatteryTemperature;
if (Kernel32.DeviceIoControl(_batteryHandle,
Kernel32.IOCTL.IOCTL_BATTERY_QUERY_INFORMATION,
ref bqi,
Marshal.SizeOf(bqi),
ref temperature,
Marshal.SizeOf<uint>(),
out _,
IntPtr.Zero))
{
_temperature.Value = (temperature / 10f) - 273.15f;
}
else
{
_temperature.Value = null;
}
ActivateSensorIfValueNotNull(_remainingCapacity);
ActivateSensorIfValueNotNull(_chargeLevel);
ActivateSensorIfValueNotNull(_voltage);
ActivateSensorIfValueNotNull(_chargeDischargeCurrent);
ActivateSensorIfValueNotNull(_chargeDischargeRate);
ActivateSensorIfValueNotNull(_remainingTime);
ActivateSensorIfValueNotNull(_temperature);
}
public override void Close()
{
base.Close();
_batteryHandle.Close();
}
}

View File

@@ -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
}

View File

@@ -0,0 +1,235 @@
// 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<Battery> _hardware = new();
static bool QueryStringFromBatteryInfo(SafeFileHandle battery, Kernel32.BATTERY_QUERY_INFORMATION bqi, out string value)
{
const int maxLoadString = 100;
value = null;
bool result = false;
IntPtr ptrString = Marshal.AllocHGlobal(maxLoadString);
if (Kernel32.DeviceIoControl(battery,
Kernel32.IOCTL.IOCTL_BATTERY_QUERY_INFORMATION,
ref bqi,
Marshal.SizeOf(bqi),
ptrString,
maxLoadString,
out uint stringSizeBytes,
IntPtr.Zero))
{
// Use the value stored in stringSizeBytes to avoid relying on a
// terminator char.
// See https://github.com/LibreHardwareMonitor/LibreHardwareMonitor/pull/1158#issuecomment-1979559929
int stringSizeChars = (int)stringSizeBytes / 2;
value = Marshal.PtrToStringUni(ptrString, stringSizeChars);
result = true;
}
Marshal.FreeHGlobal(ptrString);
return result;
}
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.Is64BitProcess ? 8 : 4 + Marshal.SystemDefaultCharSize); // 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.HasFlag(Kernel32.BatteryCapabilities.BATTERY_SYSTEM_BATTERY))
{
bqi.InformationLevel = Kernel32.BATTERY_QUERY_INFORMATION_LEVEL.BatteryDeviceName;
QueryStringFromBatteryInfo(battery, bqi, out string batteryName);
bqi.InformationLevel = Kernel32.BATTERY_QUERY_INFORMATION_LEVEL.BatteryManufactureName;
QueryStringFromBatteryInfo(battery, bqi, out string manufacturer);
_hardware.Add(new Battery(batteryName, manufacturer, battery, bi, bqi.BatteryTag, settings));
}
}
}
}
}
Kernel32.LocalFree(pdidd);
}
}
}
SetupApi.SetupDiDestroyDeviceInfoList(hdev);
}
}
/// <inheritdoc />
public IReadOnlyList<IHardware> Hardware => _hardware;
/// <inheritdoc />
public void Close()
{
foreach (Battery battery in _hardware)
battery.Close();
}
/// <inheritdoc />
public string GetReport()
{
StringBuilder reportBuilder = new();
uint count = 1;
foreach (Battery bat in _hardware)
{
string chemistry = bat.Chemistry switch
{
BatteryChemistry.LeadAcid => "Lead Acid",
BatteryChemistry.NickelCadmium => "Nickel-Cadmium",
BatteryChemistry.NickelMetalHydride => "Nickel-Metal Hydride",
BatteryChemistry.LithiumIon => "Lithium Ion",
BatteryChemistry.NickelZinc => "Nickel-Zinc",
BatteryChemistry.AlkalineManganese => "Rechargeable Alkaline-Manganese",
_ => "Unknown"
};
reportBuilder.Append("Battery #").Append(count).AppendLine(":")
.Append(" Name: ").AppendLine(bat.Name)
.Append(" Manufacturer: ").AppendLine(bat.Manufacturer)
.Append(" Chemistry: ").AppendLine(chemistry);
if (bat.DegradationLevel.HasValue)
reportBuilder.Append(" Degradation Level: ").AppendFormat("{0:F2}", bat.DegradationLevel).AppendLine(" %");
if (bat.DesignedCapacity.HasValue)
reportBuilder.Append(" Designed Capacity: ").Append(bat.DesignedCapacity).AppendLine(" mWh");
if (bat.FullChargedCapacity.HasValue)
reportBuilder.Append(" Fully-Charged Capacity: ").Append(bat.FullChargedCapacity).AppendLine(" mWh");
if (bat.RemainingCapacity.HasValue)
reportBuilder.Append(" Remaining Capacity: ").Append(bat.RemainingCapacity).AppendLine(" mWh");
if (bat.ChargeLevel.HasValue)
reportBuilder.Append(" Charge Level: ").AppendFormat("{0:F2}", bat.ChargeLevel).AppendLine(" %");
if (bat.Voltage.HasValue)
reportBuilder.Append(" Voltage: ").AppendFormat("{0:F3}", bat.Voltage).AppendLine(" V");
if (bat.Temperature.HasValue)
reportBuilder.Append(" Temperature: ").AppendFormat("{0:F3}", bat.Temperature).AppendLine(" ºC");
if (bat.RemainingTime.HasValue)
reportBuilder.Append(" Remaining Time (Estimated): ").AppendFormat("{0:g}", TimeSpan.FromSeconds(bat.RemainingTime.Value)).AppendLine();
string cdRateSensorName;
string cdCurrentSensorName;
if (bat.ChargeDischargeRate > 0)
{
cdRateSensorName = " Charge Rate: ";
cdCurrentSensorName = " Charge Current: ";
}
else if (bat.ChargeDischargeRate < 0)
{
cdRateSensorName = " Discharge Rate: ";
cdCurrentSensorName = " Discharge Current: ";
}
else
{
cdRateSensorName = " Charge/Discharge Rate: ";
cdCurrentSensorName = " Charge/Discharge Current: ";
}
if (bat.ChargeDischargeRate.HasValue)
reportBuilder.Append(cdRateSensorName).AppendFormat("{0:F1}", Math.Abs(bat.ChargeDischargeRate.Value)).AppendLine(" W");
if (bat.ChargeDischargeCurrent.HasValue)
reportBuilder.Append(cdCurrentSensorName).AppendFormat("{0:F3}", Math.Abs(bat.ChargeDischargeCurrent.Value)).AppendLine(" A");
reportBuilder.AppendLine();
count++;
}
return reportBuilder.ToString();
}
}