Windows Kernel32.BatteryLifePercent = 255

心已入冬 提交于 2020-08-05 04:09:31

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!