1
2
3
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
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
75
76
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;
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
106
107
108
109
110
111
112
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
130
131
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
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
149 try (Memory pdidd = new Memory(requiredSize.getValue())) {
150
151
152
153 pdidd.setInt(0, Integer.BYTES + (X64 ? 4 : CHAR_WIDTH));
154
155 if (SetupApi.INSTANCE.SetupDiGetDeviceInterfaceDetail(hdev, did, pdidd,
156 (int) pdidd.size(), requiredSize, null)) {
157
158 String devicePath = CHAR_WIDTH > 1 ? pdidd.getWideString(Integer.BYTES)
159 : pdidd.getString(Integer.BYTES);
160 HANDLE hBattery = Kernel32.INSTANCE.CreateFile(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
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
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
185 bi.read();
186 if (0 != (bi.Capabilities & BATTERY_SYSTEM_BATTERY)
187 && 0 == (bi.Capabilities & BATTERY_IS_SHORT_TERM)) {
188
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
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
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
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
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
280 if (psTimeRemainingInstant < 0 && psPowerUsageRate != 0) {
281 psTimeRemainingInstant = (psMaxCapacity - psCurrentCapacity)
282 * 3600d / psPowerUsageRate;
283 if (psTimeRemainingInstant < 0) {
284 psTimeRemainingInstant *= -1;
285 }
286 }
287
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;
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 }