View Javadoc
1   /*
2    * Copyright 2016-2023 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.hardware.platform.windows;
6   
7   import java.util.Locale;
8   import java.util.Objects;
9   
10  import org.slf4j.Logger;
11  import org.slf4j.LoggerFactory;
12  
13  import com.sun.jna.platform.win32.COM.COMException;
14  import com.sun.jna.platform.win32.COM.WbemcliUtil.WmiResult;
15  
16  import oshi.annotation.concurrent.ThreadSafe;
17  import oshi.driver.windows.wmi.MSAcpiThermalZoneTemperature;
18  import oshi.driver.windows.wmi.MSAcpiThermalZoneTemperature.TemperatureProperty;
19  import oshi.driver.windows.wmi.OhmHardware;
20  import oshi.driver.windows.wmi.OhmHardware.IdentifierProperty;
21  import oshi.driver.windows.wmi.OhmSensor;
22  import oshi.driver.windows.wmi.OhmSensor.ValueProperty;
23  import oshi.driver.windows.wmi.Win32Fan;
24  import oshi.driver.windows.wmi.Win32Fan.SpeedProperty;
25  import oshi.driver.windows.wmi.Win32Processor;
26  import oshi.driver.windows.wmi.Win32Processor.VoltProperty;
27  import oshi.hardware.common.AbstractSensors;
28  import oshi.util.platform.windows.WmiQueryHandler;
29  import oshi.util.platform.windows.WmiUtil;
30  
31  /**
32   * Sensors from WMI or Open Hardware Monitor
33   */
34  @ThreadSafe
35  final class WindowsSensors extends AbstractSensors {
36  
37      private static final Logger LOG = LoggerFactory.getLogger(WindowsSensors.class);
38  
39      private static final String COM_EXCEPTION_MSG = "COM exception: {}";
40  
41      @Override
42      public double queryCpuTemperature() {
43          // Attempt to fetch value from Open Hardware Monitor if it is running,
44          // as it will give the most accurate results and the time to query (or
45          // attempt) is trivial
46          double tempC = getTempFromOHM();
47          if (tempC > 0d) {
48              return tempC;
49          }
50  
51          // If we get this far, OHM is not running. Try from WMI
52          tempC = getTempFromWMI();
53  
54          // Other fallbacks to WMI are unreliable so we omit them
55          // Win32_TemperatureProbe is the official location but is not currently
56          // populated and is "reserved for future use"
57          return tempC;
58      }
59  
60      private static double getTempFromOHM() {
61          WmiQueryHandler h = Objects.requireNonNull(WmiQueryHandler.createInstance());
62          boolean comInit = false;
63          try {
64              comInit = h.initCOM();
65              WmiResult<IdentifierProperty> ohmHardware = OhmHardware.queryHwIdentifier(h, "Hardware", "CPU");
66              if (ohmHardware.getResultCount() > 0) {
67                  LOG.debug("Found Temperature data in Open Hardware Monitor");
68                  String cpuIdentifier = WmiUtil.getString(ohmHardware, IdentifierProperty.IDENTIFIER, 0);
69                  if (cpuIdentifier.length() > 0) {
70                      WmiResult<ValueProperty> ohmSensors = OhmSensor.querySensorValue(h, cpuIdentifier, "Temperature");
71                      if (ohmSensors.getResultCount() > 0) {
72                          double sum = 0;
73                          for (int i = 0; i < ohmSensors.getResultCount(); i++) {
74                              sum += WmiUtil.getFloat(ohmSensors, ValueProperty.VALUE, i);
75                          }
76                          return sum / ohmSensors.getResultCount();
77                      }
78                  }
79              }
80          } catch (COMException e) {
81              LOG.warn(COM_EXCEPTION_MSG, e.getMessage());
82          } finally {
83              if (comInit) {
84                  h.unInitCOM();
85              }
86          }
87          return 0;
88      }
89  
90      private static double getTempFromWMI() {
91          double tempC = 0d;
92          long tempK = 0L;
93          WmiResult<TemperatureProperty> result = MSAcpiThermalZoneTemperature.queryCurrentTemperature();
94          if (result.getResultCount() > 0) {
95              LOG.debug("Found Temperature data in WMI");
96              tempK = WmiUtil.getUint32asLong(result, TemperatureProperty.CURRENTTEMPERATURE, 0);
97          }
98          if (tempK > 2732L) {
99              tempC = tempK / 10d - 273.15;
100         } else if (tempK > 274L) {
101             tempC = tempK - 273d;
102         }
103         return tempC < 0d ? 0d : tempC;
104     }
105 
106     @Override
107     public int[] queryFanSpeeds() {
108         // Attempt to fetch value from Open Hardware Monitor if it is running
109         int[] fanSpeeds = getFansFromOHM();
110         if (fanSpeeds.length > 0) {
111             return fanSpeeds;
112         }
113 
114         // If we get this far, OHM is not running.
115         // Try to get from conventional WMI
116         fanSpeeds = getFansFromWMI();
117         if (fanSpeeds.length > 0) {
118             return fanSpeeds;
119         }
120 
121         // Default
122         return new int[0];
123     }
124 
125     private static int[] getFansFromOHM() {
126         WmiQueryHandler h = Objects.requireNonNull(WmiQueryHandler.createInstance());
127         boolean comInit = false;
128         try {
129             comInit = h.initCOM();
130             WmiResult<IdentifierProperty> ohmHardware = OhmHardware.queryHwIdentifier(h, "Hardware", "CPU");
131             if (ohmHardware.getResultCount() > 0) {
132                 LOG.debug("Found Fan data in Open Hardware Monitor");
133                 String cpuIdentifier = WmiUtil.getString(ohmHardware, IdentifierProperty.IDENTIFIER, 0);
134                 if (cpuIdentifier.length() > 0) {
135                     WmiResult<ValueProperty> ohmSensors = OhmSensor.querySensorValue(h, cpuIdentifier, "Fan");
136                     if (ohmSensors.getResultCount() > 0) {
137                         int[] fanSpeeds = new int[ohmSensors.getResultCount()];
138                         for (int i = 0; i < ohmSensors.getResultCount(); i++) {
139                             fanSpeeds[i] = (int) WmiUtil.getFloat(ohmSensors, ValueProperty.VALUE, i);
140                         }
141                         return fanSpeeds;
142                     }
143                 }
144             }
145         } catch (COMException e) {
146             LOG.warn(COM_EXCEPTION_MSG, e.getMessage());
147         } finally {
148             if (comInit) {
149                 h.unInitCOM();
150             }
151         }
152         return new int[0];
153     }
154 
155     private static int[] getFansFromWMI() {
156         WmiResult<SpeedProperty> fan = Win32Fan.querySpeed();
157         if (fan.getResultCount() > 1) {
158             LOG.debug("Found Fan data in WMI");
159             int[] fanSpeeds = new int[fan.getResultCount()];
160             for (int i = 0; i < fan.getResultCount(); i++) {
161                 fanSpeeds[i] = (int) WmiUtil.getUint64(fan, SpeedProperty.DESIREDSPEED, i);
162             }
163             return fanSpeeds;
164         }
165         return new int[0];
166     }
167 
168     @Override
169     public double queryCpuVoltage() {
170         // Attempt to fetch value from Open Hardware Monitor if it is running
171         double volts = getVoltsFromOHM();
172         if (volts > 0d) {
173             return volts;
174         }
175 
176         // If we get this far, OHM is not running.
177         // Try to get from conventional WMI
178         volts = getVoltsFromWMI();
179 
180         return volts;
181     }
182 
183     private static double getVoltsFromOHM() {
184         WmiQueryHandler h = Objects.requireNonNull(WmiQueryHandler.createInstance());
185         boolean comInit = false;
186         try {
187             comInit = h.initCOM();
188             WmiResult<IdentifierProperty> ohmHardware = OhmHardware.queryHwIdentifier(h, "Sensor", "Voltage");
189             if (ohmHardware.getResultCount() > 0) {
190                 LOG.debug("Found Voltage data in Open Hardware Monitor");
191                 // Look for identifier containing "cpu"
192                 String cpuIdentifier = null;
193                 for (int i = 0; i < ohmHardware.getResultCount(); i++) {
194                     String id = WmiUtil.getString(ohmHardware, IdentifierProperty.IDENTIFIER, i);
195                     if (id.toLowerCase(Locale.ROOT).contains("cpu")) {
196                         cpuIdentifier = id;
197                         break;
198                     }
199                 }
200                 // If none found, just get the first one
201                 if (cpuIdentifier == null) {
202                     cpuIdentifier = WmiUtil.getString(ohmHardware, IdentifierProperty.IDENTIFIER, 0);
203                 }
204                 // Now fetch sensor
205                 WmiResult<ValueProperty> ohmSensors = OhmSensor.querySensorValue(h, cpuIdentifier, "Voltage");
206                 if (ohmSensors.getResultCount() > 0) {
207                     return WmiUtil.getFloat(ohmSensors, ValueProperty.VALUE, 0);
208                 }
209             }
210         } catch (COMException e) {
211             LOG.warn(COM_EXCEPTION_MSG, e.getMessage());
212         } finally {
213             if (comInit) {
214                 h.unInitCOM();
215             }
216         }
217         return 0d;
218     }
219 
220     private static double getVoltsFromWMI() {
221         WmiResult<VoltProperty> voltage = Win32Processor.queryVoltage();
222         if (voltage.getResultCount() > 1) {
223             LOG.debug("Found Voltage data in WMI");
224             int decivolts = WmiUtil.getUint16(voltage, VoltProperty.CURRENTVOLTAGE, 0);
225             // If the eighth bit is set, bits 0-6 contain the voltage
226             // multiplied by 10. If the eighth bit is not set, then the bit
227             // setting in VoltageCaps represents the voltage value.
228             if (decivolts > 0) {
229                 if ((decivolts & 0x80) == 0) {
230                     decivolts = WmiUtil.getUint32(voltage, VoltProperty.VOLTAGECAPS, 0);
231                     // This value is really a bit setting, not decivolts
232                     if ((decivolts & 0x1) > 0) {
233                         return 5.0;
234                     } else if ((decivolts & 0x2) > 0) {
235                         return 3.3;
236                     } else if ((decivolts & 0x4) > 0) {
237                         return 2.9;
238                     }
239                 } else {
240                     // Value from bits 0-6, divided by 10
241                     return (decivolts & 0x7F) / 10d;
242                 }
243             }
244         }
245         return 0d;
246     }
247 }