View Javadoc
1   /*
2    * Copyright 2016-2025 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.hardware.platform.windows;
6   
7   import java.lang.reflect.Method;
8   import java.util.Collections;
9   import java.util.List;
10  import java.util.Locale;
11  import java.util.Objects;
12  import java.util.function.BiFunction;
13  
14  import org.slf4j.Logger;
15  import org.slf4j.LoggerFactory;
16  
17  import com.sun.jna.platform.win32.COM.COMException;
18  import com.sun.jna.platform.win32.COM.WbemcliUtil.WmiResult;
19  
20  import oshi.annotation.concurrent.ThreadSafe;
21  import oshi.driver.windows.wmi.MSAcpiThermalZoneTemperature;
22  import oshi.driver.windows.wmi.MSAcpiThermalZoneTemperature.TemperatureProperty;
23  import oshi.driver.windows.wmi.OhmHardware;
24  import oshi.driver.windows.wmi.OhmHardware.IdentifierProperty;
25  import oshi.driver.windows.wmi.OhmSensor;
26  import oshi.driver.windows.wmi.OhmSensor.ValueProperty;
27  import oshi.driver.windows.wmi.Win32Fan;
28  import oshi.driver.windows.wmi.Win32Fan.SpeedProperty;
29  import oshi.driver.windows.wmi.Win32Processor;
30  import oshi.driver.windows.wmi.Win32Processor.VoltProperty;
31  import oshi.hardware.common.AbstractSensors;
32  import oshi.util.platform.windows.WmiQueryHandler;
33  import oshi.util.platform.windows.WmiUtil;
34  
35  /**
36   * Sensors from WMI or Open Hardware Monitor or Libre Hardware Monitor
37   */
38  @ThreadSafe
39  final class WindowsSensors extends AbstractSensors {
40  
41      private static final Logger LOG = LoggerFactory.getLogger(WindowsSensors.class);
42  
43      private static final String COM_EXCEPTION_MSG = "COM exception: {}";
44  
45      private static final String REFLECT_EXCEPTION_MSG = "Reflect exception: {}";
46  
47      private static final String JLIBREHARDWAREMONITOR_PACKAGE = "io.github.pandalxb.jlibrehardwaremonitor";
48  
49      @Override
50      public double queryCpuTemperature() {
51          // Attempt to fetch value from Open Hardware Monitor if it is running,
52          // as it will give the most accurate results and the time to query (or
53          // attempt) is trivial
54          double tempC = getTempFromOHM();
55          if (tempC > 0d) {
56              return tempC;
57          }
58  
59          // Fetch value from library LibreHardwareMonitorLib.dll(.NET 4.7.2 and above) or OpenHardwareMonitorLib.dll(.NET
60          // 2.0)
61          // without applications running
62          tempC = getTempFromLHM();
63          if (tempC > 0d) {
64              return tempC;
65          }
66  
67          // If we get this far, OHM is not running. Try from WMI
68          tempC = getTempFromWMI();
69  
70          // Other fallbacks to WMI are unreliable so we omit them
71          // Win32_TemperatureProbe is the official location but is not currently
72          // populated and is "reserved for future use"
73          return tempC;
74      }
75  
76      private static double getTempFromOHM() {
77          WmiResult<ValueProperty> ohmSensors = getOhmSensors("Hardware", "CPU", "Temperature", (h, ohmHardware) -> {
78              String cpuIdentifier = WmiUtil.getString(ohmHardware, IdentifierProperty.IDENTIFIER, 0);
79              if (!cpuIdentifier.isEmpty()) {
80                  return OhmSensor.querySensorValue(h, cpuIdentifier, "Temperature");
81              }
82              return null;
83          });
84          if (ohmSensors != null && ohmSensors.getResultCount() > 0) {
85              double sum = 0;
86              for (int i = 0; i < ohmSensors.getResultCount(); i++) {
87                  sum += WmiUtil.getFloat(ohmSensors, ValueProperty.VALUE, i);
88              }
89              return sum / ohmSensors.getResultCount();
90          }
91          return 0;
92      }
93  
94      private static double getTempFromLHM() {
95          return getAverageValueFromLHM("CPU", "Temperature",
96                  (name, value) -> !name.contains("Max") && !name.contains("Average") && value > 0);
97      }
98  
99      private static double getTempFromWMI() {
100         double tempC = 0d;
101         long tempK = 0L;
102         WmiResult<TemperatureProperty> result = MSAcpiThermalZoneTemperature.queryCurrentTemperature();
103         if (result.getResultCount() > 0) {
104             LOG.debug("Found Temperature data in WMI");
105             tempK = WmiUtil.getUint32asLong(result, TemperatureProperty.CURRENTTEMPERATURE, 0);
106         }
107         if (tempK > 2732L) {
108             tempC = tempK / 10d - 273.15;
109         } else if (tempK > 274L) {
110             tempC = tempK - 273d;
111         }
112         return Math.max(tempC, +0.0);
113     }
114 
115     @Override
116     public int[] queryFanSpeeds() {
117         // Attempt to fetch value from Open Hardware Monitor if it is running
118         int[] fanSpeeds = getFansFromOHM();
119         if (fanSpeeds.length > 0) {
120             return fanSpeeds;
121         }
122 
123         // Fetch value from library LibreHardwareMonitorLib.dll(.NET 4.7.2 and above) or OpenHardwareMonitorLib.dll(.NET
124         // 2.0)
125         // without applications running
126         fanSpeeds = getFansFromLHM();
127         if (fanSpeeds.length > 0) {
128             return fanSpeeds;
129         }
130 
131         // If we get this far, OHM is not running.
132         // Try to get from conventional WMI
133         fanSpeeds = getFansFromWMI();
134         if (fanSpeeds.length > 0) {
135             return fanSpeeds;
136         }
137 
138         // Default
139         return new int[0];
140     }
141 
142     private static int[] getFansFromOHM() {
143         WmiResult<ValueProperty> ohmSensors = getOhmSensors("Hardware", "CPU", "Fan", (h, ohmHardware) -> {
144             String cpuIdentifier = WmiUtil.getString(ohmHardware, IdentifierProperty.IDENTIFIER, 0);
145             if (!cpuIdentifier.isEmpty()) {
146                 return OhmSensor.querySensorValue(h, cpuIdentifier, "Fan");
147             }
148             return null;
149         });
150         if (ohmSensors != null && ohmSensors.getResultCount() > 0) {
151             int[] fanSpeeds = new int[ohmSensors.getResultCount()];
152             for (int i = 0; i < ohmSensors.getResultCount(); i++) {
153                 fanSpeeds[i] = (int) WmiUtil.getFloat(ohmSensors, ValueProperty.VALUE, i);
154             }
155             return fanSpeeds;
156         }
157         return new int[0];
158     }
159 
160     private static int[] getFansFromLHM() {
161         List<?> sensors = getLhmSensors("SuperIO", "Fan");
162         if (sensors == null || sensors.isEmpty()) {
163             return new int[0];
164         }
165 
166         try {
167             // The sensor object is confirmed to contain the getValue method.
168             Class<?> sensorClass = Class.forName(JLIBREHARDWAREMONITOR_PACKAGE + ".model.Sensor");
169             Method getValueMethod = sensorClass.getMethod("getValue");
170 
171             return sensors.stream().filter(sensor -> {
172                 try {
173                     double value = (double) getValueMethod.invoke(sensor);
174                     return value > 0;
175                 } catch (Exception e) {
176                     LOG.warn(REFLECT_EXCEPTION_MSG, e.getMessage());
177                     return false;
178                 }
179             }).mapToInt(sensor -> {
180                 try {
181                     return (int) (double) getValueMethod.invoke(sensor);
182                 } catch (Exception e) {
183                     LOG.warn(REFLECT_EXCEPTION_MSG, e.getMessage());
184                     return 0;
185                 }
186             }).toArray();
187         } catch (Exception e) {
188             LOG.warn(REFLECT_EXCEPTION_MSG, e.getMessage());
189         }
190         return new int[0];
191     }
192 
193     private static int[] getFansFromWMI() {
194         WmiResult<SpeedProperty> fan = Win32Fan.querySpeed();
195         if (fan.getResultCount() > 1) {
196             LOG.debug("Found Fan data in WMI");
197             int[] fanSpeeds = new int[fan.getResultCount()];
198             for (int i = 0; i < fan.getResultCount(); i++) {
199                 fanSpeeds[i] = (int) WmiUtil.getUint64(fan, SpeedProperty.DESIREDSPEED, i);
200             }
201             return fanSpeeds;
202         }
203         return new int[0];
204     }
205 
206     @Override
207     public double queryCpuVoltage() {
208         // Attempt to fetch value from Open Hardware Monitor if it is running
209         double volts = getVoltsFromOHM();
210         if (volts > 0d) {
211             return volts;
212         }
213 
214         // Fetch value from library LibreHardwareMonitorLib.dll(.NET 4.7.2 and above) or OpenHardwareMonitorLib.dll(.NET
215         // 2.0)
216         // without applications running
217         volts = getVoltsFromLHM();
218         if (volts > 0d) {
219             return volts;
220         }
221 
222         // If we get this far, OHM is not running.
223         // Try to get from conventional WMI
224         volts = getVoltsFromWMI();
225 
226         return volts;
227     }
228 
229     private static double getVoltsFromOHM() {
230         WmiResult<ValueProperty> ohmSensors = getOhmSensors("Sensor", "Voltage", "Voltage", (h, ohmHardware) -> {
231             // Look for identifier containing "cpu"
232             String cpuIdentifier = null;
233             for (int i = 0; i < ohmHardware.getResultCount(); i++) {
234                 String id = WmiUtil.getString(ohmHardware, IdentifierProperty.IDENTIFIER, i);
235                 if (id.toLowerCase(Locale.ROOT).contains("cpu")) {
236                     cpuIdentifier = id;
237                     break;
238                 }
239             }
240             // If none found, just get the first one
241             if (cpuIdentifier == null) {
242                 cpuIdentifier = WmiUtil.getString(ohmHardware, IdentifierProperty.IDENTIFIER, 0);
243             }
244             // Now fetch sensor
245             return OhmSensor.querySensorValue(h, cpuIdentifier, "Voltage");
246         });
247         if (ohmSensors != null && ohmSensors.getResultCount() > 0) {
248             return WmiUtil.getFloat(ohmSensors, ValueProperty.VALUE, 0);
249         }
250         return 0d;
251     }
252 
253     private static double getVoltsFromLHM() {
254         return getAverageValueFromLHM("SuperIO", "Voltage",
255                 (name, value) -> name.toLowerCase(Locale.ROOT).contains("vcore") && value > 0);
256     }
257 
258     private static double getVoltsFromWMI() {
259         WmiResult<VoltProperty> voltage = Win32Processor.queryVoltage();
260         if (voltage.getResultCount() > 1) {
261             LOG.debug("Found Voltage data in WMI");
262             int decivolts = WmiUtil.getUint16(voltage, VoltProperty.CURRENTVOLTAGE, 0);
263             // If the eighth bit is set, bits 0-6 contain the voltage
264             // multiplied by 10. If the eighth bit is not set, then the bit
265             // setting in VoltageCaps represents the voltage value.
266             if (decivolts > 0) {
267                 if ((decivolts & 0x80) == 0) {
268                     decivolts = WmiUtil.getUint32(voltage, VoltProperty.VOLTAGECAPS, 0);
269                     // This value is really a bit setting, not decivolts
270                     if ((decivolts & 0x1) > 0) {
271                         return 5.0;
272                     } else if ((decivolts & 0x2) > 0) {
273                         return 3.3;
274                     } else if ((decivolts & 0x4) > 0) {
275                         return 2.9;
276                     }
277                 } else {
278                     // Value from bits 0-6, divided by 10
279                     return (decivolts & 0x7F) / 10d;
280                 }
281             }
282         }
283         return 0d;
284     }
285 
286     private static WmiResult<ValueProperty> getOhmSensors(String typeToQuery, String typeName, String sensorType,
287             BiFunction<WmiQueryHandler, WmiResult<IdentifierProperty>, WmiResult<ValueProperty>> querySensorFunction) {
288         WmiQueryHandler h = Objects.requireNonNull(WmiQueryHandler.createInstance());
289         boolean comInit = false;
290         WmiResult<ValueProperty> ohmSensors = null;
291         try {
292             comInit = h.initCOM();
293             WmiResult<IdentifierProperty> ohmHardware = OhmHardware.queryHwIdentifier(h, typeToQuery, typeName);
294             if (ohmHardware.getResultCount() > 0) {
295                 LOG.debug("Found {} data in Open Hardware Monitor", sensorType);
296                 ohmSensors = querySensorFunction.apply(h, ohmHardware);
297             }
298         } catch (COMException e) {
299             LOG.warn(COM_EXCEPTION_MSG, e.getMessage());
300         } finally {
301             if (comInit) {
302                 h.unInitCOM();
303             }
304         }
305         return ohmSensors;
306     }
307 
308     private static double getAverageValueFromLHM(String hardwareType, String sensorType,
309             BiFunction<String, Double, Boolean> sensorValidFunction) {
310         List<?> sensors = getLhmSensors(hardwareType, sensorType);
311         if (sensors == null || sensors.isEmpty()) {
312             return 0;
313         }
314 
315         try {
316             // The sensor object is confirmed to contain the getName and getValue methods.
317             Class<?> sensorClass = Class.forName(JLIBREHARDWAREMONITOR_PACKAGE + ".model.Sensor");
318             Method getNameMethod = sensorClass.getMethod("getName");
319             Method getValueMethod = sensorClass.getMethod("getValue");
320 
321             double sum = 0;
322             int validCount = 0;
323             for (Object sensor : sensors) {
324                 String name = (String) getNameMethod.invoke(sensor);
325                 double value = (double) getValueMethod.invoke(sensor);
326                 if (sensorValidFunction.apply(name, value)) {
327                     sum += value;
328                     validCount++;
329                 }
330             }
331             return validCount > 0 ? sum / validCount : 0;
332         } catch (Exception e) {
333             LOG.warn(REFLECT_EXCEPTION_MSG, e.getMessage());
334         }
335         return 0;
336     }
337 
338     private static List<?> getLhmSensors(String hardwareType, String sensorType) {
339         try {
340             Class<?> computerConfigClass = Class.forName(JLIBREHARDWAREMONITOR_PACKAGE + ".config.ComputerConfig");
341             Class<?> libreHardwareManagerClass = Class
342                     .forName(JLIBREHARDWAREMONITOR_PACKAGE + ".manager.LibreHardwareManager");
343 
344             Method computerConfigGetInstanceMethod = computerConfigClass.getMethod("getInstance");
345             Object computerConfigInstance = computerConfigGetInstanceMethod.invoke(null);
346 
347             Method setEnabledMethod = computerConfigClass.getMethod("setCpuEnabled", boolean.class);
348             setEnabledMethod.invoke(computerConfigInstance, true);
349             setEnabledMethod = computerConfigClass.getMethod("setMotherboardEnabled", boolean.class);
350             setEnabledMethod.invoke(computerConfigInstance, true);
351 
352             Method libreHardwareManagerGetInstanceMethod = libreHardwareManagerClass.getMethod("getInstance",
353                     computerConfigClass);
354 
355             Object instance = libreHardwareManagerGetInstanceMethod.invoke(null, computerConfigInstance);
356 
357             Method querySensorsMethod = libreHardwareManagerClass.getMethod("querySensors", String.class, String.class);
358             return (List<?>) querySensorsMethod.invoke(instance, hardwareType, sensorType);
359         } catch (Exception e) {
360             LOG.warn(REFLECT_EXCEPTION_MSG, e.getMessage());
361         }
362         return Collections.emptyList();
363     }
364 }