Introduction
I have a homebuilt desktop with an ASUS P6T motherboard. It's about 7-8 years old, but still quite powerful and functions beautifully. ASUS originally provided PC Probe II to monitor temperatures and fan speeds, but stopped supporting it for the P6T when Windows 10 came out. And since then, I've been operating without a satisfactory sensor app. Unfortunately, the 3rd party sensor apps available are a bit quirky, or non-intuitive, or can't be configured the way I'd like. Then again, perhaps I'm just overly picky.
Some years ago, I considered OpenHardwardMonitor, an open source DLL that seems to cover everything needed. Unfortunately, the documentation they provide is minimal at best, and as of this writing, clearly out of date. And then Howard wrote a great article here on CodeProject that provided enough information to get me going. Unfortunately, he was unable to fully exploit OpenHardwareMonitor
's capabilities. But after some experimentation, I think I've managed to do so.
Using the Code
The code is implemented as a Visual Studio 2015 self-contained project. It includes OpenHardwareMonitorLib.dll
version 0.8.0 Beta. You should be able to download the code file, unzip it, load it into VS, and compile and execute it.
Implementation
The key to the implementation is a public
wrapper class I created named ohmDataTree
. It implements:
- the
IDisposable
interface to ensure that OHM's resources are properly shut down regardless - a timer set to update all sensor data every 1.0 second
- properties
MainboardEnabled
, FanControllerEnabled
, CPUEnabled
, GPUEnabled
, RAMEnabled
and HDDEnabled
to expose OHM's comparable native properties, which allow the user to disable unneeded aspects of OHM; I assume that speeds up the sensor scan, but I haven't done any tests to confirm that - variable
hWareList
, which gives access to the hardware and sensor information in the form of a linked-list binary tree of ohmHwNode
classes—more on that later - method
UpdateSensorData
, which updates the sensor values Value
, Min
and Max
, and their associated formatted strings, in each instance of the ohmSensor
class contained in hWareList
- variable
configChanged
, which indicates that the hardware configuration has changed since the last call to UpdateSensorData
; strictly for external information purposes; it's up to the user to act upon this information, and reset configChanged
if needed - method
SensorFromID
, which searches the linked list for an instance of ohmSensor
with a particular unique sensor Identifier
- method
SensorFromIndex
, which searches the linked list to find a sensor by the linear order in which it appears in the tree - public
string[]
arrays sensorFormats
and sensorUnits
which provide format strings and units strings for each SensorType
- method
getValueString
which returns a complete, formatted value string with units appended
Note that configChanged
is set by monitoring OHM's HardwareAdded
and HardwareRemoved
events. Through testing, I determined that these events are fired when one of the xxxEnabled
properties is set or reset. I also experimented by plugging in and removing old (6-8 years) and new (<6 months) USB hard drives and USB stick drives. In all cases, neither of the events fired, and the added drives did not immediately show up in the scan. They only showed up if I shut the application down and restarted it. They did show up immediately if I called the Open()
and Close()
methods in the OpenHardwareMonitor.Hardware.Computer
class on each update. But both methods appear to be CPU intensive, and frequently calling them that way bogged down my system, literally made it unusable.
I posted an issue on GitHub to notify the OHM developers. Dan Neel responded immediately and kudos to him for his quick reply. He recommended I catch the USB-connected event. I set up a WMI ManagementEventWatcher
class
and that worked, notifying me when a USB device was inserted. In response, I executed the Close()
and Open()
events to update OHM. Doing so in response to the event meant I didn't execute those methods on every timer tick and bog the system down. Unfortunately, it appears that OHM opens a handle to each device and keeps it open, or something like that. Windows would not allow me to eject the device until I stopped monitoring it with OHM. Because of that, I disabled that portion of the code by placing it in a #define TrapUSB
. If you want to experiment with it, just change TrapUSBNo
to TrapUSB
.
The information for each hardware node is stored in a class named ohmHwNode
. Each hardware node can contain a list of child hardware nodes, and there appears to be no limit on the depth of child nodes—each child node can contain a further list of child nodes. Hence, I implemented the sensor scan with a recursive call to the method ScanHardware
. It accepts an OHM IHardware
array of hardware nodes, and a pointer to the ohmHwNode
parent, which is null
in the case of the nodes in the uppermost IHardware
array. The ohmHwNode
class is structured as follows:
public class ohmHwNode
{
public IHardware hWare = null;
public ohmHwNode ohmParent = null;
public ohmHwNode[] ohmChildren = null;
public string name = "";
public HardwareType type;
public Identifier id;
public ohmSensor[] ohmSensors = null;
}
The ohmParent
and ohmChildren
pointers are the links that connect the linked-list binary tree. They allow me to walk the tree in the method AddItems
in the file ProbeForm.cs to retrieve the sensor data.
Each hardware node contains an OHM ISensor
array of data for its sensors, which can be empty for any given hardware node. I store the data for each sensor in a container class named ohmSensor
which is structured as follows:
public class ohmSensor
{
public ISensor sensor = null;
public string Name = "";
public string Identifier = "";
public SensorType sType;
public float? Value;
public float? Min;
public float? Max;
public string stValue = "";
public string stMin = "";
public string stMax = "";
public int Index = 0;
}
The stValue
, stMin
and stMax
string
s are formatted from the returned nullable values by the ohmDataTree
method getValueString
, which is implemented as follows:
private string SensorUnits(SensorType type)
{
switch (type)
{
case SensorType.Voltage:
return "v";
case SensorType.Clock:
return "MHz";
case SensorType.Temperature:
return "°C";
case SensorType.Level:
case SensorType.Control:
case SensorType.Load:
return "%";
case SensorType.Fan:
return "rpm";
case SensorType.Flow:
return "L/h";
case SensorType.SmallData:
return "MB";
case SensorType.Data:
return "GB";
case SensorType.Power:
return "W";
}
return "";
}
private string SensorFormat(SensorType type)
{
switch (type)
{
case SensorType.Voltage:
return "N3";
case SensorType.Clock:
return "N0";
case SensorType.Temperature:
return "N1";
case SensorType.Level:
case SensorType.Control:
case SensorType.Load:
return "N1";
case SensorType.Fan:
return "N0";
case SensorType.Flow:
return "N1";
case SensorType.SmallData:
return "N1";
case SensorType.Data:
return "N1";
case SensorType.Power:
return "N1";
}
return "";
}
public string getValueString(SensorType type, float? value)
{
if (value == null)
return "--- " + sensorUnits[(int)type];
else
return value.Value.ToString(sensorFormats[(int)type]) + " " + sensorUnits[(int)type];
}
Methods SensorUnits
and SensorFormat
are only used to construct the sensorsUnits
and sensorsFormats
string arrays at runtime.
Note that, as of this writing, the OHM documentation lists the sensor type enumeration as:
public enum SensorType
{
Voltage,
Clock,
Temperature,
Load,
Fan,
Flow,
Control,
Level
}
But in the present version (0.8.0 Beta) of OHM, SmallData
, Data
, Factor
and Power
have been added. OHM's outdated and insufficient documentation is its biggest problem, and probably the biggest hurdle to widespread implementation.
Run as Administrator
It is critically important to run the application with Administrator privileges. When I run the application without Administrator privileges on my desktop, I get the following:
Note the sensors with a value of "---". Those sensors returned a nullable (floart?
) value of null
.
On the other hand, when I run it with Administrator privileges, I get this:
Note that the "No Sensors" entry under the motherboard only means that the motherboard hardware node has no sensors. However, it does have a child hardware node that contains all the motherboard sensors.
If you run Visual Studio with Administrator privileges, it will automatically run both debug and release versions with Administrator privileges. If you run Visual Studio with normal privileges, the Probe program will prompt you to elevate privileges. If you want to see how your system responds without, answer No to the prompt.
I also ran this on my Sony Vaio SVS15125CXB notebook. It has built-in dual display adaptors: an Intel (R) HD Graphics 4000 and an NVIDIA GeForce GT 640M LE. The NVIDIA is for graphics intensive applications, and frequently shuts down when not needed. When that happens, it returns null
values for its sensors, so such results can be valid even when running with Adminstrator privileges.
Things To Do
- Create a nice, configurable, minimalist, floating desktop app to monitor fan speeds and temperatures, with alarms--Done, see App for CPU Temps, Fan Speeds, etc., Part II. Part II now includes history tracking and an alarm log.
- Try to understand many of the undocumented features of OHM.
History
- 2018.07.10: First implementation and publication
- 2018.07.22:
- Implemented
sensorFormats
and sensorUnits
arrays - Encapsulated the timer in
ohmDataTree
and added an UpdateSensors
event handler so external classes can hook into the update event chain - Added an
UpdateSensorData
method to update the sensor values in the ohmDataTree
without recreating the tree, eliminating the need to dispose and reconstruct each ohmHwNode
and each ohmSensor
class on each update
- 2018.10.16: Created a desktop app to monitor fan speeds, temeratures, etc. with alarms, history tracking, and an alarm log. See App for CPU Temps, Fan Speeds, etc., Part II.