问题
I'm trying to build a Java app that reads the status of a laptop battery and sends a notification to the user if it's low. In order to do this, I'm using jna with Kernel32 native library as explained in the first answer of this question: How to get the remaining battery life in a Windows system?
Running the example, the program yields this output:
ACLineStatus: Offline
Battery Flag: High, more than 66 percent
Battery Life: Unknown
Battery Left: 0 seconds
Battery Full: 10832 seconds
The fields battery life and battery left are read in Kernel32 BatteryLifePercent and BatteryLifeTime values which are 255 (Unknown) and 0 (I don't get this value. Unknown would be -1 according to Microsoft documentation here: https://msdn.microsoft.com/en-us/library/windows/desktop/aa373232(v=vs.85).aspx).
My question is: why am I getting these values back? The Windows battery tray icon displays the correct percentage, so why I can't get that data from here?
I'm running Windows 7 Ultimate Edition 64-bit.
Thank you.
回答1:
The code from the linked answer was wrong (edit: now it is fixed).
The fields are ordered in the wrong way, change getFieldOrder
method with
@Override
protected List<String> getFieldOrder()
{
ArrayList<String> fields = new ArrayList<String>();
fields.add("ACLineStatus");
fields.add("BatteryFlag");
fields.add("BatteryLifePercent");
fields.add("Reserved1");
fields.add("BatteryLifeTime");
fields.add("BatteryFullLifeTime");
return fields;
}
Also add this constructor that specify the correct alignment
public SYSTEM_POWER_STATUS()
{
setAlignType(ALIGN_MSVC);
}
The alignment could also be ALIGN_NONE
as Microsoft usually take care to explicitly align data with reserved fields.
It could also be ALIGN_DEFAULT
since, as far as I know, Windows is compiled with Microsoft compiler, and it aligns data on their the natural boundaries.
In other words the structure is naturally aligned by design, so it requires no specific alignment constraints.
This is the output, on my system, from the original code
ACLineStatus: Offline
Battery Flag: High, more than 66 percent
Battery Life: Unknown
Battery Left: 0 seconds
Battery Full: 12434 seconds
This is the output with the corrected code
ACLineStatus: Offline
Battery Flag: High, more than 66 percent
Battery Life: 95%
Battery Left: 12434 seconds
Battery Full: Unknown
On why this happens
Considering the output above, we can reconstruct how the structure SYSTEM_POWER_STATUS
is filled in memory.
00 08 5f 00 96 30 00 00 ff ff ff ff
¯¯ ¯¯ ¯¯ ¯¯ ¯¯¯¯¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯¯¯¯¯
| | | | | |
| | | | BatteryLifeTime |
| | | Reserved1 |
| | | BatteryFullLifeTime
| | BatteryLifePercent
| |
| BatteryFlags
|
AcLineStatus
According to the fields order of the original code, this is how the fields get initialized
00 08 5f 00 96 30 00 00 ff ff ff ff 00 00 00 00
¯¯ ¯¯ ¯¯¯¯¯¯¯¯¯¯¯ ¯¯ ¯¯¯¯¯¯¯¯¯¯¯
| | | | |
| BatteryFlags | BatteryLifePercent |
| | |
AcLineStatus | BatteryLifeTime
BatteryFullLifeTime
The gaps are due to the default alignment that align data on their natural boundaries.
Since the fields have been reordered they are no longer in their original positions and continuous.
On why BatteryFullLifeTime is Unknown
If you disassemble the function GetSystemPowerStatus
for Win7 64 bit (you can find my disassembly here) and rewrite an equivalent C program you got something like this
BOOL WINAPI GetSystemPowerStatus(
_Out_ LPSYSTEM_POWER_STATUS lpSystemPowerStatus
)
{
SYSTEM_BATTERY_STATE battery_state;
//Get power information
NTStatus pi_status = NtPowerInformation(SystemBatteryState, NULL, 0, &battery_state, sizeof(battery_state));
//Check success
if (!NTSuccess(pi_status))
{
BaseSetLastNtError(pi_status);
return FALSE;
}
//Zero out the input structure
memset(lpSystemPowerStatus, sizeof(lpSystemPowerStatus), 0);
//Set AC line status
lpSystemPowerStatus->ACLineStatus = battery_state.BatteryPresent && battery_state.AcOnLine ? 1 : 0;
//Set flags
lpSystemPowerStatus->BatteryFlags |= (battery_state.Charging ? 8 : 0)
| (battery_state.BatteryPresent ? 0 : 0x80);
//Set battery life time percent
lpSystemPowerStatus->BatteryLifePercent = 0xff;
if (battery_state.MaxCapacity)
{
lpSystemPowerStatus->BatteryLifePercent = battery_state.RemainingCapacity > battery_state.MaxCapacity
? 100
: (battery_state.RemainingCapacity*100 + battery_state.MaxCapacity/2)/battery_state.MaxCapacity;
lpSystemPowerStatus->BatteryFlags |= (lpSystemPowerStatus->BatteryLifePercent > 66 ? 1 : 0)
| (lpSystemPowerStatus->BatteryLifePercent < 33 ? 2 : 0);
}
//Set battery life time and full life time
lpSystemPowerStatus->BatteryLifeTime = lpSystemPowerStatus->BatteryFullLifeTime = -1;
if (battery_state.EstimatedTime)
lpSystemPowerStatus->BatteryLifeTime = battery_state.EstimatedTime;
}
Which show that BatterFullLifeTime
is never copied from the SYSTEM_BATTERY_STATE
structure. It is always -1.
Also the flag with value 4 (Critical battery level) is never set.
In newer version of Windows these may have probably been fixed.
A newer version
You can call CallNtPowerInformation
in PowrProf.dll
to obtain more reliable information on the battery status.
If you are unfamiliar with accessing the Win APIs, here a JNA class that do the work for you
PowrProf.Java
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package javaapplication5;
/**
*
* @author mijo
*/
import java.util.List;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.win32.StdCallLibrary;
import java.util.Arrays;
public interface PowrProf extends StdCallLibrary {
public PowrProf INSTANCE = (PowrProf) Native.loadLibrary("PowrProf", PowrProf.class);
public class SYSTEM_BATTERY_STATE extends Structure
{
public static class ByReference extends SYSTEM_BATTERY_STATE implements Structure.ByReference {}
public byte AcOnLine;
public byte BatteryPresent;
public byte Charging;
public byte Discharging;
public byte Spare1_0;
public byte Spare1_1;
public byte Spare1_2;
public byte Spare1_3;
public int MaxCapacity;
public int RemainingCapacity;
public int Rate;
public int EstimatedTime;
public int DefaultAlert1;
public int DefaultAlert2;
@Override
protected List<String> getFieldOrder()
{
return Arrays.asList(new String[]
{
"AcOnLine", "BatteryPresent", "Charging", "Discharging",
"Spare1_0", "Spare1_1", "Spare1_2", "Spare1_3",
"MaxCapacity", "RemainingCapacity", "Rate",
"EstimatedTime", "DefaultAlert1", "DefaultAlert2"
});
}
public SYSTEM_BATTERY_STATE ()
{
setAlignType(ALIGN_MSVC);
}
public boolean isAcConnected()
{
return AcOnLine != 0;
}
public boolean isBatteryPresent()
{
return BatteryPresent != 0;
}
public enum BatteryFlow{ Charging, Discharging, None }
public BatteryFlow getBatteryFlow()
{
if (Charging != 0) return BatteryFlow.Charging;
if (Discharging != 0) return BatteryFlow.Discharging;
return BatteryFlow.None;
}
//in mWh
public int getMaxCapacity()
{
return MaxCapacity;
}
//in mWh
public int getCurrentCharge()
{
return RemainingCapacity;
}
//in mW
public int getFlowRate()
{
return Rate;
}
//in s
public int getEstimatedTime()
{
return EstimatedTime;
}
//in s
//-1 if not available
public int getTimeToEmpty()
{
if (getBatteryFlow() != BatteryFlow.Discharging)
return -1;
return -getCurrentCharge()*3600/getFlowRate();
}
//in s
//-1 if not available
public int getTimeToFull()
{
if (getBatteryFlow() != BatteryFlow.Charging)
return -1;
return (getMaxCapacity()-getCurrentCharge())*3600/getFlowRate();
}
public double getCurrentChargePercent()
{
return getCurrentCharge()*100/getMaxCapacity();
}
public int getCurrentChargeIntegralPercent()
{
return (getCurrentCharge()*100+getMaxCapacity()/2)/getMaxCapacity();
}
@Override
public String toString()
{
StringBuilder b = new StringBuilder(4096);
b.append("AC Line? "); b.append(isAcConnected());
b.append("\nBattery present? "); b.append(isBatteryPresent());
b.append("\nBattery flow: "); b.append(getBatteryFlow());
b.append("\nMax capacity (mWh): "); b.append(getMaxCapacity());
b.append("\nCurrent charge (mWh): "); b.append(getCurrentCharge());
b.append("\nFlow rate (mW/s): "); b.append(getFlowRate());
b.append("\nEstimated time (from OS): "); b.append(getEstimatedTime());
b.append("\nEstimated time (manual): "); b.append(getTimeToEmpty());
b.append("\nEstimated time to full (manual): "); b.append(getTimeToFull());
b.append("\nCurrent charge (percent): "); b.append(getCurrentChargePercent());
b.append("\nCurrent charge (integral percent): "); b.append(getCurrentChargeIntegralPercent());
return b.toString();
}
}
public int CallNtPowerInformation(int informationLevel, Pointer inBuffer, long inBufferLen, SYSTEM_BATTERY_STATE.ByReference outBuffer, long outBufferLen);
static final int SystemBatteryState = 5;
public static SYSTEM_BATTERY_STATE GetBatteryState()
{
SYSTEM_BATTERY_STATE.ByReference battery_state = new SYSTEM_BATTERY_STATE.ByReference();
int retVal = PowrProf.INSTANCE.CallNtPowerInformation(SystemBatteryState, Pointer.NULL, 0, battery_state, battery_state.size());
if (retVal != 0)
return null;
return battery_state;
}
}
And its use
public static void main(String[] args)
{
PowrProf.SYSTEM_BATTERY_STATE sbs = PowrProf.GetBatteryState();
System.out.println(sbs);
}
Sample output when discharging:
AC Line? false
Battery present? true
Battery flow: Discharging
Max capacity (mWh): 35090
Current charge (mWh): 34160
Flow rate (mW/s): -11234
Estimated time (from OS): 10940
Estimated time (manual): 10946
Estimated time to full (manual): -1
Current charge (percent): 97.34
Current charge (integral percent): 98
Sample output when charging:
AC Line? true
Battery present? true
Battery flow: Charging
Max capacity (mWh): 35090
Current charge (mWh): 33710
Flow rate (mW/s): 3529
Estimated time (from OS): -1
Estimated time (manual): -1
Estimated time to full (manual): 1407 Current charge (percent): 96.06
Current charge (integral percent): 97
N.B. When plugging and unplugging the power cable to test, wait some sec as the monitoring is not in real time.
P.S.
I sign my code with the pseudonym Mijo, you can remove that comment.
来源:https://stackoverflow.com/questions/35864321/windows-kernel32-batterylifepercent-255