Search code examples
windowswindows-7-x64jnabatterykernel32

Windows Kernel32.BatteryLifePercent = 255


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.


Solution

  • 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.