View Javadoc
1   /*
2    * Copyright 2016-2022 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.hardware.platform.windows;
6   
7   import java.nio.charset.StandardCharsets;
8   import java.time.LocalDate;
9   import java.util.Arrays;
10  import java.util.List;
11  
12  import com.sun.jna.Memory;
13  import com.sun.jna.Native;
14  import com.sun.jna.Platform;
15  import com.sun.jna.platform.win32.Guid.GUID;
16  import com.sun.jna.platform.win32.Kernel32;
17  import com.sun.jna.platform.win32.PowrProf.POWER_INFORMATION_LEVEL;
18  import com.sun.jna.platform.win32.SetupApi;
19  import com.sun.jna.platform.win32.WinBase;
20  import com.sun.jna.platform.win32.WinError;
21  import com.sun.jna.platform.win32.WinNT;
22  import com.sun.jna.platform.win32.WinNT.HANDLE;
23  import com.sun.jna.win32.W32APITypeMapper;
24  
25  import oshi.annotation.concurrent.ThreadSafe;
26  import oshi.hardware.PowerSource;
27  import oshi.hardware.common.AbstractPowerSource;
28  import oshi.jna.ByRef.CloseableIntByReference;
29  import oshi.jna.Struct.CloseableSpDeviceInterfaceData;
30  import oshi.jna.platform.windows.PowrProf;
31  import oshi.jna.platform.windows.PowrProf.BATTERY_INFORMATION;
32  import oshi.jna.platform.windows.PowrProf.BATTERY_MANUFACTURE_DATE;
33  import oshi.jna.platform.windows.PowrProf.BATTERY_QUERY_INFORMATION;
34  import oshi.jna.platform.windows.PowrProf.BATTERY_QUERY_INFORMATION_LEVEL;
35  import oshi.jna.platform.windows.PowrProf.BATTERY_STATUS;
36  import oshi.jna.platform.windows.PowrProf.BATTERY_WAIT_STATUS;
37  import oshi.jna.platform.windows.PowrProf.SystemBatteryState;
38  import oshi.util.Constants;
39  
40  /**
41   * A Power Source
42   */
43  @ThreadSafe
44  public final class WindowsPowerSource extends AbstractPowerSource {
45  
46      private static final GUID GUID_DEVCLASS_BATTERY = GUID.fromString("{72631E54-78A4-11D0-BCF7-00AA00B7B32A}");
47      private static final int CHAR_WIDTH = W32APITypeMapper.DEFAULT == W32APITypeMapper.UNICODE ? 2 : 1;
48      private static final boolean X64 = Platform.is64Bit();
49  
50      private static final int BATTERY_SYSTEM_BATTERY = 0x80000000;
51      private static final int BATTERY_IS_SHORT_TERM = 0x20000000;
52      private static final int BATTERY_POWER_ON_LINE = 0x00000001;
53      private static final int BATTERY_DISCHARGING = 0x00000002;
54      private static final int BATTERY_CHARGING = 0x00000004;
55      private static final int BATTERY_CAPACITY_RELATIVE = 0x40000000;
56  
57      private static final int IOCTL_BATTERY_QUERY_TAG = 0x294040;
58      private static final int IOCTL_BATTERY_QUERY_STATUS = 0x29404c;
59      private static final int IOCTL_BATTERY_QUERY_INFORMATION = 0x294044;
60  
61      public WindowsPowerSource(String psName, String psDeviceName, double psRemainingCapacityPercent,
62              double psTimeRemainingEstimated, double psTimeRemainingInstant, double psPowerUsageRate, double psVoltage,
63              double psAmperage, boolean psPowerOnLine, boolean psCharging, boolean psDischarging,
64              CapacityUnits psCapacityUnits, int psCurrentCapacity, int psMaxCapacity, int psDesignCapacity,
65              int psCycleCount, String psChemistry, LocalDate psManufactureDate, String psManufacturer,
66              String psSerialNumber, double psTemperature) {
67          super(psName, psDeviceName, psRemainingCapacityPercent, psTimeRemainingEstimated, psTimeRemainingInstant,
68                  psPowerUsageRate, psVoltage, psAmperage, psPowerOnLine, psCharging, psDischarging, psCapacityUnits,
69                  psCurrentCapacity, psMaxCapacity, psDesignCapacity, psCycleCount, psChemistry, psManufactureDate,
70                  psManufacturer, psSerialNumber, psTemperature);
71      }
72  
73      /**
74       * Gets Battery Information.
75       *
76       * @return A list of PowerSource objects representing batteries, etc.
77       */
78      public static List<PowerSource> getPowerSources() {
79          return Arrays.asList(getPowerSource("System Battery"));
80      }
81  
82      private static WindowsPowerSource getPowerSource(String name) {
83          String psName = name;
84          String psDeviceName = Constants.UNKNOWN;
85          double psRemainingCapacityPercent = 1d;
86          double psTimeRemainingEstimated = -1d; // -1 = unknown, -2 = unlimited
87          double psTimeRemainingInstant = 0d;
88          int psPowerUsageRate = 0;
89          double psVoltage = -1d;
90          double psAmperage = 0d;
91          boolean psPowerOnLine = false;
92          boolean psCharging = false;
93          boolean psDischarging = false;
94          CapacityUnits psCapacityUnits = CapacityUnits.RELATIVE;
95          int psCurrentCapacity = 0;
96          int psMaxCapacity = 1;
97          int psDesignCapacity = 1;
98          int psCycleCount = -1;
99          String psChemistry = Constants.UNKNOWN;
100         LocalDate psManufactureDate = null;
101         String psManufacturer = Constants.UNKNOWN;
102         String psSerialNumber = Constants.UNKNOWN;
103         double psTemperature = 0d;
104 
105         // windows PowerSource information comes from two sources: the PowrProf's
106         // CallNTPowerInformation function which returns information for a single
107         // object, and DeviceIoControl with each battery's (if more than one) handle
108         // (which, in theory, return an array of objects but in most cases should return
109         // one).
110         //
111         // We start by fetching the PowrProf information, which will be replicated
112         // across all IOCTL entries if there are more than one.
113 
114         try (SystemBatteryState batteryState = new SystemBatteryState()) {
115             if (0 == PowrProf.INSTANCE.CallNtPowerInformation(POWER_INFORMATION_LEVEL.SystemBatteryState, null, 0,
116                     batteryState.getPointer(), batteryState.size()) && batteryState.batteryPresent > 0) {
117                 if (batteryState.acOnLine == 0 && batteryState.charging == 0 && batteryState.discharging > 0) {
118                     psTimeRemainingEstimated = batteryState.estimatedTime;
119                 } else if (batteryState.charging > 0) {
120                     psTimeRemainingEstimated = -2d;
121                 }
122                 psMaxCapacity = batteryState.maxCapacity;
123                 psCurrentCapacity = batteryState.remainingCapacity;
124                 psRemainingCapacityPercent = Math.min(1d, (double) psCurrentCapacity / psMaxCapacity);
125                 psPowerUsageRate = batteryState.rate;
126             }
127         }
128 
129         // Enumerate batteries and ask each one for information
130         // Ported from:
131         // https://docs.microsoft.com/en-us/windows/win32/power/enumerating-battery-devices
132 
133         HANDLE hdev = SetupApi.INSTANCE.SetupDiGetClassDevs(GUID_DEVCLASS_BATTERY, null, null,
134                 SetupApi.DIGCF_PRESENT | SetupApi.DIGCF_DEVICEINTERFACE);
135         if (!WinBase.INVALID_HANDLE_VALUE.equals(hdev)) {
136             boolean batteryFound = false;
137             // Limit search to 100 batteries max
138             for (int idev = 0; !batteryFound && idev < 100; idev++) {
139                 try (CloseableSpDeviceInterfaceData did = new CloseableSpDeviceInterfaceData();
140                         CloseableIntByReference requiredSize = new CloseableIntByReference();
141                         CloseableIntByReference dwWait = new CloseableIntByReference();
142                         CloseableIntByReference dwTag = new CloseableIntByReference();
143                         CloseableIntByReference dwOut = new CloseableIntByReference()) {
144                     did.cbSize = did.size();
145                     if (SetupApi.INSTANCE.SetupDiEnumDeviceInterfaces(hdev, null, GUID_DEVCLASS_BATTERY, idev, did)) {
146                         SetupApi.INSTANCE.SetupDiGetDeviceInterfaceDetail(hdev, did, null, 0, requiredSize, null);
147                         if (WinError.ERROR_INSUFFICIENT_BUFFER == Kernel32.INSTANCE.GetLastError()) {
148                             // PSP_DEVICE_INTERFACE_DETAIL_DATA: int size + TCHAR array
149                             try (Memory pdidd = new Memory(requiredSize.getValue())) {
150                                 // pdidd->cbSize is defined as sizeof(*pdidd)
151                                 // On 64 bit, cbSize is 8. On 32-bit it's 5 or 6 based on char size
152                                 // This must be set properly for the method to work but is otherwise ignored
153                                 pdidd.setInt(0, Integer.BYTES + (X64 ? 4 : CHAR_WIDTH));
154                                 // Regardless of this setting the string portion starts after one byte
155                                 if (SetupApi.INSTANCE.SetupDiGetDeviceInterfaceDetail(hdev, did, pdidd,
156                                         (int) pdidd.size(), requiredSize, null)) {
157                                     // Enumerated a battery. Ask it for information.
158                                     String devicePath = CHAR_WIDTH > 1 ? pdidd.getWideString(Integer.BYTES)
159                                             : pdidd.getString(Integer.BYTES);
160                                     HANDLE hBattery = Kernel32.INSTANCE.CreateFile(devicePath, // pdidd->DevicePath
161                                             WinNT.GENERIC_READ | WinNT.GENERIC_WRITE,
162                                             WinNT.FILE_SHARE_READ | WinNT.FILE_SHARE_WRITE, null, WinNT.OPEN_EXISTING,
163                                             WinNT.FILE_ATTRIBUTE_NORMAL, null);
164                                     if (!WinBase.INVALID_HANDLE_VALUE.equals(hBattery)) {
165                                         try (BATTERY_QUERY_INFORMATION bqi = new BATTERY_QUERY_INFORMATION();
166                                                 BATTERY_INFORMATION bi = new BATTERY_INFORMATION();
167                                                 BATTERY_WAIT_STATUS bws = new BATTERY_WAIT_STATUS();
168                                                 BATTERY_STATUS bs = new BATTERY_STATUS();
169                                                 BATTERY_MANUFACTURE_DATE bmd = new BATTERY_MANUFACTURE_DATE()) {
170                                             // Ask the battery for its tag.
171                                             if (Kernel32.INSTANCE.DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_TAG,
172                                                     dwWait.getPointer(), Integer.BYTES, dwTag.getPointer(),
173                                                     Integer.BYTES, dwOut, null)) {
174                                                 bqi.BatteryTag = dwTag.getValue();
175                                                 if (bqi.BatteryTag > 0) {
176                                                     // With the tag, you can query the battery info.
177                                                     bqi.InformationLevel = BATTERY_QUERY_INFORMATION_LEVEL.BatteryInformation
178                                                             .ordinal();
179                                                     bqi.write();
180 
181                                                     if (Kernel32.INSTANCE.DeviceIoControl(hBattery,
182                                                             IOCTL_BATTERY_QUERY_INFORMATION, bqi.getPointer(),
183                                                             bqi.size(), bi.getPointer(), bi.size(), dwOut, null)) {
184                                                         // Only non-UPS system batteries count
185                                                         bi.read();
186                                                         if (0 != (bi.Capabilities & BATTERY_SYSTEM_BATTERY)
187                                                                 && 0 == (bi.Capabilities & BATTERY_IS_SHORT_TERM)) {
188                                                             // Capabilities flags non-mWh units
189                                                             if (0 == (bi.Capabilities & BATTERY_CAPACITY_RELATIVE)) {
190                                                                 psCapacityUnits = CapacityUnits.MWH;
191                                                             }
192                                                             psChemistry = Native.toString(bi.Chemistry,
193                                                                     StandardCharsets.US_ASCII);
194                                                             psDesignCapacity = bi.DesignedCapacity;
195                                                             psMaxCapacity = bi.FullChargedCapacity;
196                                                             psCycleCount = bi.CycleCount;
197 
198                                                             // Query the battery status.
199                                                             bws.BatteryTag = bqi.BatteryTag;
200                                                             bws.write();
201                                                             if (Kernel32.INSTANCE.DeviceIoControl(hBattery,
202                                                                     IOCTL_BATTERY_QUERY_STATUS, bws.getPointer(),
203                                                                     bws.size(), bs.getPointer(), bs.size(), dwOut,
204                                                                     null)) {
205                                                                 bs.read();
206                                                                 if (0 != (bs.PowerState & BATTERY_POWER_ON_LINE)) {
207                                                                     psPowerOnLine = true;
208                                                                 }
209                                                                 if (0 != (bs.PowerState & BATTERY_DISCHARGING)) {
210                                                                     psDischarging = true;
211                                                                 }
212                                                                 if (0 != (bs.PowerState & BATTERY_CHARGING)) {
213                                                                     psCharging = true;
214                                                                 }
215                                                                 psCurrentCapacity = bs.Capacity;
216                                                                 psVoltage = bs.Voltage > 0 ? bs.Voltage / 1000d
217                                                                         : bs.Voltage;
218                                                                 psPowerUsageRate = bs.Rate;
219                                                                 if (psVoltage > 0) {
220                                                                     psAmperage = psPowerUsageRate / psVoltage;
221                                                                 }
222                                                             }
223                                                         }
224 
225                                                         psDeviceName = batteryQueryString(hBattery, dwTag.getValue(),
226                                                                 BATTERY_QUERY_INFORMATION_LEVEL.BatteryDeviceName
227                                                                         .ordinal());
228                                                         psManufacturer = batteryQueryString(hBattery, dwTag.getValue(),
229                                                                 BATTERY_QUERY_INFORMATION_LEVEL.BatteryManufactureName
230                                                                         .ordinal());
231                                                         psSerialNumber = batteryQueryString(hBattery, dwTag.getValue(),
232                                                                 BATTERY_QUERY_INFORMATION_LEVEL.BatterySerialNumber
233                                                                         .ordinal());
234 
235                                                         bqi.InformationLevel = BATTERY_QUERY_INFORMATION_LEVEL.BatteryManufactureDate
236                                                                 .ordinal();
237                                                         bqi.write();
238 
239                                                         if (Kernel32.INSTANCE.DeviceIoControl(hBattery,
240                                                                 IOCTL_BATTERY_QUERY_INFORMATION, bqi.getPointer(),
241                                                                 bqi.size(), bmd.getPointer(), bmd.size(), dwOut,
242                                                                 null)) {
243                                                             bmd.read();
244                                                             // If failed, returns -1 for each field
245                                                             if (bmd.Year > 1900 && bmd.Month > 0 && bmd.Day > 0) {
246                                                                 psManufactureDate = LocalDate.of(bmd.Year, bmd.Month,
247                                                                         bmd.Day);
248                                                             }
249                                                         }
250 
251                                                         bqi.InformationLevel = BATTERY_QUERY_INFORMATION_LEVEL.BatteryTemperature
252                                                                 .ordinal();
253                                                         bqi.write();
254                                                         try (CloseableIntByReference tempK = new CloseableIntByReference()) {
255                                                             // 1/10 degree K
256                                                             if (Kernel32.INSTANCE.DeviceIoControl(hBattery,
257                                                                     IOCTL_BATTERY_QUERY_INFORMATION, bqi.getPointer(),
258                                                                     bqi.size(), tempK.getPointer(), Integer.BYTES,
259                                                                     dwOut, null)) {
260                                                                 psTemperature = tempK.getValue() / 10d - 273.15;
261                                                             }
262                                                         }
263 
264                                                         // Put last because we change the AtRate field
265                                                         bqi.InformationLevel = BATTERY_QUERY_INFORMATION_LEVEL.BatteryEstimatedTime
266                                                                 .ordinal();
267                                                         if (psPowerUsageRate != 0) {
268                                                             bqi.AtRate = psPowerUsageRate;
269                                                         }
270                                                         bqi.write();
271                                                         try (CloseableIntByReference tr = new CloseableIntByReference()) {
272                                                             if (Kernel32.INSTANCE.DeviceIoControl(hBattery,
273                                                                     IOCTL_BATTERY_QUERY_INFORMATION, bqi.getPointer(),
274                                                                     bqi.size(), tr.getPointer(), Integer.BYTES, dwOut,
275                                                                     null)) {
276                                                                 psTimeRemainingInstant = tr.getValue();
277                                                             }
278                                                         }
279                                                         // Fallback
280                                                         if (psTimeRemainingInstant < 0 && psPowerUsageRate != 0) {
281                                                             psTimeRemainingInstant = (psMaxCapacity - psCurrentCapacity)
282                                                                     * 3600d / psPowerUsageRate;
283                                                             if (psTimeRemainingInstant < 0) {
284                                                                 psTimeRemainingInstant *= -1;
285                                                             }
286                                                         }
287                                                         // Exit loop
288                                                         batteryFound = true;
289                                                     }
290                                                 }
291                                             }
292                                         }
293                                         Kernel32.INSTANCE.CloseHandle(hBattery);
294                                     }
295                                 }
296                             }
297                         }
298                     } else if (WinError.ERROR_NO_MORE_ITEMS == Kernel32.INSTANCE.GetLastError()) {
299                         break; // Enumeration failed - perhaps we're out of items
300                     }
301                 }
302             }
303             SetupApi.INSTANCE.SetupDiDestroyDeviceInfoList(hdev);
304         }
305 
306         return new WindowsPowerSource(psName, psDeviceName, psRemainingCapacityPercent, psTimeRemainingEstimated,
307                 psTimeRemainingInstant, psPowerUsageRate, psVoltage, psAmperage, psPowerOnLine, psCharging,
308                 psDischarging, psCapacityUnits, psCurrentCapacity, psMaxCapacity, psDesignCapacity, psCycleCount,
309                 psChemistry, psManufactureDate, psManufacturer, psSerialNumber, psTemperature);
310     }
311 
312     private static String batteryQueryString(HANDLE hBattery, int tag, int infoLevel) {
313         try (BATTERY_QUERY_INFORMATION bqi = new BATTERY_QUERY_INFORMATION();
314                 CloseableIntByReference dwOut = new CloseableIntByReference()) {
315             bqi.BatteryTag = tag;
316             bqi.InformationLevel = infoLevel;
317             bqi.write();
318             boolean ret = false;
319             long bufSize = 256;
320             Memory nameBuf = new Memory(bufSize);
321             do {
322                 ret = Kernel32.INSTANCE.DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, bqi.getPointer(),
323                         bqi.size(), nameBuf, (int) nameBuf.size(), dwOut, null);
324                 if (!ret) {
325                     bufSize += 256;
326                     nameBuf.close();
327                     if (bufSize > 4096) {
328                         return "";
329                     }
330                     nameBuf = new Memory(bufSize);
331                 }
332             } while (!ret);
333             String name = CHAR_WIDTH > 1 ? nameBuf.getWideString(0) : nameBuf.getString(0);
334             nameBuf.close();
335             return name;
336         }
337     }
338 }