diff --git a/NuGet.config b/NuGet.config index eab6ca9f38..dbf474c948 100644 --- a/NuGet.config +++ b/NuGet.config @@ -2,6 +2,9 @@ + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 483bfa6dd6..f6710b025f 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -16,21 +16,25 @@ - - https://github.com/dotnet/corefx - 4ac4c0367003fe3973a3648eb0715ddb0e3bbcea + + https://github.com/dotnet/runtime + 7ef6d50b312217d2f7c17b9697891fa8ab98a19d - - https://github.com/dotnet/corefx - 4ac4c0367003fe3973a3648eb0715ddb0e3bbcea + + https://github.com/dotnet/runtime + 7ef6d50b312217d2f7c17b9697891fa8ab98a19d - - https://github.com/dotnet/corefx - 4ac4c0367003fe3973a3648eb0715ddb0e3bbcea + + https://github.com/dotnet/runtime + 7ef6d50b312217d2f7c17b9697891fa8ab98a19d https://github.com/dotnet/corefx 4ac4c0367003fe3973a3648eb0715ddb0e3bbcea + + https://github.com/dotnet/runtime + 7ef6d50b312217d2f7c17b9697891fa8ab98a19d + diff --git a/eng/Versions.props b/eng/Versions.props index 52633b3ad5..2642987c13 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -4,21 +4,14 @@ prerelease 6.0.0-beta.20552.5 - 4.6.0 - 4.6.0 - 4.6.0 + 5.0.0 + 5.0.0 + 5.0.0 4.6.0 + 5.0.0 + 4.5.4 + 4.5.4 + 4.3.0 4.58.0 - - - - $(RestoreSources); - https://dotnetfeed.blob.core.windows.net/dotnet-tools-internal/index.json; - https://dotnetfeed.blob.core.windows.net/dotnet-iot/index.json; - https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json; - https://dotnet.myget.org/F/dotnet-core/api/v3/index.json; - https://api.nuget.org/v3/index.json; - - diff --git a/src/Iot.Device.Bindings/Iot.Device.Bindings.csproj b/src/Iot.Device.Bindings/Iot.Device.Bindings.csproj index a852715ab0..c4a1cc7fe1 100644 --- a/src/Iot.Device.Bindings/Iot.Device.Bindings.csproj +++ b/src/Iot.Device.Bindings/Iot.Device.Bindings.csproj @@ -26,6 +26,7 @@ + diff --git a/src/System.Device.Gpio/System.Device.Gpio.csproj b/src/System.Device.Gpio/System.Device.Gpio.csproj index b4dd15eb94..c71cc6d660 100644 --- a/src/System.Device.Gpio/System.Device.Gpio.csproj +++ b/src/System.Device.Gpio/System.Device.Gpio.csproj @@ -14,8 +14,8 @@ - - + + all @@ -23,7 +23,7 @@ - + winmd\Windows.Devices.DevicesLowLevelContract.winmd diff --git a/src/System.Device.Gpio/System/Device/Gpio/GpioController.cs b/src/System.Device.Gpio/System/Device/Gpio/GpioController.cs index f176fd56ee..360b3dd9d5 100644 --- a/src/System.Device.Gpio/System/Device/Gpio/GpioController.cs +++ b/src/System.Device.Gpio/System/Device/Gpio/GpioController.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using System.Device.Gpio.Drivers; -using System.IO; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.Win32; @@ -402,7 +400,9 @@ private static GpioDriver GetBestDriverForBoardOnLinux() /// private static GpioDriver GetBestDriverForBoardOnWindows() { - string? baseBoardProduct = Registry.LocalMachine.GetValue(BaseBoardProductRegistryValue, string.Empty).ToString(); +#pragma warning disable CA1416 // Registry.LocalMachine is only supported on Windows, but we will only hit this method if we are on Windows. + string? baseBoardProduct = Registry.LocalMachine.GetValue(BaseBoardProductRegistryValue, string.Empty)?.ToString(); +#pragma warning restore CA1416 if (baseBoardProduct is null) { diff --git a/src/devices/Ak8963/Ak8963.csproj b/src/devices/Ak8963/Ak8963.csproj index d1f4e657a6..0c888d0ca2 100644 --- a/src/devices/Ak8963/Ak8963.csproj +++ b/src/devices/Ak8963/Ak8963.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/devices/Bno055/Bno055.csproj b/src/devices/Bno055/Bno055.csproj index af801645a7..521bf82214 100644 --- a/src/devices/Bno055/Bno055.csproj +++ b/src/devices/Bno055/Bno055.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/devices/Card/CreditCard/CreditCardProcessing.csproj b/src/devices/Card/CreditCard/CreditCardProcessing.csproj index 7e6344f2cc..6a45c3f39a 100644 --- a/src/devices/Card/CreditCard/CreditCardProcessing.csproj +++ b/src/devices/Card/CreditCard/CreditCardProcessing.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/devices/CpuTemperature/CpuTemperature.cs b/src/devices/CpuTemperature/CpuTemperature.cs old mode 100755 new mode 100644 index ab038049cb..a47281cb06 --- a/src/devices/CpuTemperature/CpuTemperature.cs +++ b/src/devices/CpuTemperature/CpuTemperature.cs @@ -2,29 +2,67 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; +using System.Globalization; using System.IO; +using System.Linq; +using System.Management; using System.Runtime.InteropServices; +using Iot.Device.HardwareMonitor; using UnitsNet; namespace Iot.Device.CpuTemperature { /// - /// CPU temperature + /// CPU temperature. + /// On Windows, the value returned is driver dependent and may not represent actual CPU temperature, but more one + /// of the case sensors. Use OpenHardwareMonitor for better environmental representation in Windows. /// - public class CpuTemperature + public sealed class CpuTemperature : IDisposable { - private bool _isAvalable; + private bool _isAvailable; private bool _checkedIfAvailable; + private bool _windows; + private List _managementObjectSearchers; + private OpenHardwareMonitor? _hardwareMonitorInUse; + + /// + /// Creates an instance of the CpuTemperature class + /// + public CpuTemperature() + { + _isAvailable = false; + _checkedIfAvailable = false; + _windows = false; + _managementObjectSearchers = new List(); + _hardwareMonitorInUse = null; + + CheckAvailable(); + } /// /// Gets CPU temperature /// - public Temperature Temperature => Temperature.FromDegreesCelsius(ReadTemperature()); + public Temperature Temperature + { + get + { + if (!_windows) + { + return Temperature.FromDegreesCelsius(ReadTemperatureUnix()); + } + else + { + List<(string, Temperature)> tempList = ReadTemperatures(); + return tempList.FirstOrDefault().Item2; + } + } + } /// /// Is CPU temperature available /// - public bool IsAvailable => CheckAvailable(); + public bool IsAvailable => _isAvailable; private bool CheckAvailable() { @@ -33,14 +71,101 @@ private bool CheckAvailable() _checkedIfAvailable = true; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && File.Exists("/sys/class/thermal/thermal_zone0/temp")) { - _isAvalable = true; + _isAvailable = true; + _windows = false; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + OpenHardwareMonitor ohw = new OpenHardwareMonitor(); + if (ohw.TryGetAverageCpuTemperature(out _)) + { + _windows = true; + _isAvailable = true; + _hardwareMonitorInUse = ohw; + return true; + } + + try + { + ManagementObjectSearcher searcher = new ManagementObjectSearcher(@"root\WMI", "SELECT * FROM MSAcpi_ThermalZoneTemperature"); + if (searcher.Get().Count > 0) + { + _managementObjectSearchers.Add(searcher); + _isAvailable = true; + _windows = true; + } + } + catch (Exception x) when (x is IOException || x is UnauthorizedAccessException || x is ManagementException) + { + // Nothing to do - WMI not available for this element or missing permissions. + // WMI enumeration may require elevated rights. + } + + try + { + ManagementObjectSearcher searcher = new ManagementObjectSearcher(@"root\WMI", "SELECT * FROM Win32_TemperatureProbe"); + if (searcher.Get().Count > 0) + { + _managementObjectSearchers.Add(searcher); + _isAvailable = true; + _windows = true; + } + } + catch (Exception x) when (x is IOException || x is UnauthorizedAccessException || x is ManagementException) + { + // Nothing to do - WMI not available for this element or missing permissions. + // WMI enumeration may require elevated rights. + } + } } - return _isAvalable; + return _isAvailable; } - private double ReadTemperature() + /// + /// Returns all known temperature sensor values. + /// + /// A list of name/value pairs for temperature sensors + public List<(string, Temperature)> ReadTemperatures() + { + if (!_windows) + { + var ret = new List<(string, Temperature)>(); + ret.Add(("CPU", Temperature.FromDegreesCelsius(ReadTemperatureUnix()))); + return ret; + } + + // Windows code below + List<(string, Temperature)> result = new List<(string, Temperature)>(); + + if (_hardwareMonitorInUse != null) + { + if (_hardwareMonitorInUse.TryGetAverageCpuTemperature(out Temperature temp)) + { + result.Add(("CPU", temp)); + } + + return result; + } + + foreach (var searcher in _managementObjectSearchers) + { +// This code will only be executed when on Windows. +#pragma warning disable CA1416 // Validate platform compatibility + foreach (ManagementObject obj in searcher.Get()) + { + Double temp = Convert.ToDouble(string.Format(CultureInfo.InvariantCulture, "{0}", obj["CurrentTemperature"]), CultureInfo.InvariantCulture); + temp = (temp - 2732) / 10.0; + result.Add((obj["InstanceName"].ToString() ?? string.Empty, Temperature.FromDegreesCelsius(temp))); + } +#pragma warning restore CA1416 // Validate platform compatibility + } + + return result; + } + + private double ReadTemperatureUnix() { double temperature = double.NaN; @@ -63,5 +188,25 @@ private double ReadTemperature() return temperature; } + + /// + public void Dispose() + { + if (_hardwareMonitorInUse != null) + { + _hardwareMonitorInUse.Dispose(); + _hardwareMonitorInUse = null; + } + + foreach (var elem in _managementObjectSearchers) + { + elem.Dispose(); + } + + _managementObjectSearchers.Clear(); + + // Any further calls will fail + _isAvailable = false; + } } } diff --git a/src/devices/CpuTemperature/CpuTemperature.csproj b/src/devices/CpuTemperature/CpuTemperature.csproj index 97b89bf13c..196ee76784 100755 --- a/src/devices/CpuTemperature/CpuTemperature.csproj +++ b/src/devices/CpuTemperature/CpuTemperature.csproj @@ -1,7 +1,7 @@ - + - net5.0;netstandard2.0 + net5.0;netcoreapp2.1 false @@ -9,5 +9,12 @@ + + + + + + + diff --git a/src/devices/CpuTemperature/CpuTemperature.sln b/src/devices/CpuTemperature/CpuTemperature.sln index 6a327340c1..76a1da30a2 100644 --- a/src/devices/CpuTemperature/CpuTemperature.sln +++ b/src/devices/CpuTemperature/CpuTemperature.sln @@ -1,13 +1,15 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26124.0 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30626.31 MinimumVisualStudioVersion = 15.0.26124.0 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{45EC0518-E2FF-4278-ABDB-E0A2D79E71BE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CpuTemperature.Samples", "samples\CpuTemperature.Samples.csproj", "{356BCA51-E8A9-4190-A814-70A6F87E33E1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CpuTemperature.Samples", "samples\CpuTemperature.Samples.csproj", "{356BCA51-E8A9-4190-A814-70A6F87E33E1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CpuTemperature", "CpuTemperature.csproj", "{B9CCBDB6-FAE3-49BF-B654-30698EF8131D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CpuTemperature", "CpuTemperature.csproj", "{B9CCBDB6-FAE3-49BF-B654-30698EF8131D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HardwareMonitor", "..\HardwareMonitor\HardwareMonitor.csproj", "{91BF18B9-1511-4910-8E30-FA0D2EADF18E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -18,9 +20,6 @@ Global Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {356BCA51-E8A9-4190-A814-70A6F87E33E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {356BCA51-E8A9-4190-A814-70A6F87E33E1}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -46,8 +45,26 @@ Global {B9CCBDB6-FAE3-49BF-B654-30698EF8131D}.Release|x64.Build.0 = Release|Any CPU {B9CCBDB6-FAE3-49BF-B654-30698EF8131D}.Release|x86.ActiveCfg = Release|Any CPU {B9CCBDB6-FAE3-49BF-B654-30698EF8131D}.Release|x86.Build.0 = Release|Any CPU + {91BF18B9-1511-4910-8E30-FA0D2EADF18E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {91BF18B9-1511-4910-8E30-FA0D2EADF18E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91BF18B9-1511-4910-8E30-FA0D2EADF18E}.Debug|x64.ActiveCfg = Debug|Any CPU + {91BF18B9-1511-4910-8E30-FA0D2EADF18E}.Debug|x64.Build.0 = Debug|Any CPU + {91BF18B9-1511-4910-8E30-FA0D2EADF18E}.Debug|x86.ActiveCfg = Debug|Any CPU + {91BF18B9-1511-4910-8E30-FA0D2EADF18E}.Debug|x86.Build.0 = Debug|Any CPU + {91BF18B9-1511-4910-8E30-FA0D2EADF18E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {91BF18B9-1511-4910-8E30-FA0D2EADF18E}.Release|Any CPU.Build.0 = Release|Any CPU + {91BF18B9-1511-4910-8E30-FA0D2EADF18E}.Release|x64.ActiveCfg = Release|Any CPU + {91BF18B9-1511-4910-8E30-FA0D2EADF18E}.Release|x64.Build.0 = Release|Any CPU + {91BF18B9-1511-4910-8E30-FA0D2EADF18E}.Release|x86.ActiveCfg = Release|Any CPU + {91BF18B9-1511-4910-8E30-FA0D2EADF18E}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {356BCA51-E8A9-4190-A814-70A6F87E33E1} = {45EC0518-E2FF-4278-ABDB-E0A2D79E71BE} EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {822036DB-5CBC-45E9-B5B4-07FB884DF248} + EndGlobalSection EndGlobal diff --git a/src/devices/CpuTemperature/README.md b/src/devices/CpuTemperature/README.md index 09962ea079..645c5bf130 100755 --- a/src/devices/CpuTemperature/README.md +++ b/src/devices/CpuTemperature/README.md @@ -8,4 +8,7 @@ Returns the current temperature of the CPU Temperature Sensor. Useful telemetry ## Binding Notes +On Windows, this tries to use the OpenHardwareMonitor binding (see there for details). +If it is not available, some guesswork is done to get a temperature sensor. However, the temperature returned by this binding may not be the actual CPU temperature, but one of the mainboard sensors instead. Therefore, depending on the mainboard, no data may be available. Unless OpenHardwareMonitor can be used, elevated permissions ("Admin rights") are required. + ## References diff --git a/src/devices/CpuTemperature/samples/CpuTemperature.Samples.csproj b/src/devices/CpuTemperature/samples/CpuTemperature.Samples.csproj old mode 100755 new mode 100644 index 7382823ffc..4ba5b3144e --- a/src/devices/CpuTemperature/samples/CpuTemperature.Samples.csproj +++ b/src/devices/CpuTemperature/samples/CpuTemperature.Samples.csproj @@ -3,6 +3,7 @@ Exe net5.0 + app.manifest diff --git a/src/devices/CpuTemperature/samples/Program.cs b/src/devices/CpuTemperature/samples/Program.cs index 79e8579308..3a54df267a 100755 --- a/src/devices/CpuTemperature/samples/Program.cs +++ b/src/devices/CpuTemperature/samples/Program.cs @@ -6,17 +6,31 @@ using Iot.Device.CpuTemperature; CpuTemperature cpuTemperature = new CpuTemperature(); +Console.WriteLine("Press any key to quit"); -while (true) +while (!Console.KeyAvailable) { if (cpuTemperature.IsAvailable) { - double temperature = cpuTemperature.Temperature.DegreesCelsius; - if (!double.IsNaN(temperature)) + var temperature = cpuTemperature.ReadTemperatures(); + foreach (var entry in temperature) { - Console.WriteLine($"CPU Temperature: {temperature} C"); + if (!double.IsNaN(entry.Item2.DegreesCelsius)) + { + Console.WriteLine($"Temperature from {entry.Item1.ToString()}: {entry.Item2.DegreesCelsius} °C"); + } + else + { + Console.WriteLine("Unable to read Temperature."); + } } } + else + { + Console.WriteLine($"CPU temperature is not available"); + } Thread.Sleep(1000); } + +cpuTemperature.Dispose(); diff --git a/src/devices/CpuTemperature/samples/app.manifest b/src/devices/CpuTemperature/samples/app.manifest new file mode 100644 index 0000000000..b6c4554265 --- /dev/null +++ b/src/devices/CpuTemperature/samples/app.manifest @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/devices/Ft4222/Ft4222.csproj b/src/devices/Ft4222/Ft4222.csproj index dfc8175809..5c253e1e01 100644 --- a/src/devices/Ft4222/Ft4222.csproj +++ b/src/devices/Ft4222/Ft4222.csproj @@ -11,6 +11,6 @@ - + diff --git a/src/devices/GoPiGo3/GoPiGo3.csproj b/src/devices/GoPiGo3/GoPiGo3.csproj index c1324f4494..b6a5218110 100644 --- a/src/devices/GoPiGo3/GoPiGo3.csproj +++ b/src/devices/GoPiGo3/GoPiGo3.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/devices/GrovePi/GrovePiDevice.csproj b/src/devices/GrovePi/GrovePiDevice.csproj index db539d541e..9181ddd0ef 100644 --- a/src/devices/GrovePi/GrovePiDevice.csproj +++ b/src/devices/GrovePi/GrovePiDevice.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/devices/HardwareMonitor/HardwareMonitor.csproj b/src/devices/HardwareMonitor/HardwareMonitor.csproj new file mode 100644 index 0000000000..4f15806eae --- /dev/null +++ b/src/devices/HardwareMonitor/HardwareMonitor.csproj @@ -0,0 +1,18 @@ + + + + netcoreapp2.1;net5.0 + false + + + + + + + + + + + + + diff --git a/src/devices/HardwareMonitor/HardwareMonitor.sln b/src/devices/HardwareMonitor/HardwareMonitor.sln new file mode 100644 index 0000000000..e231fe917e --- /dev/null +++ b/src/devices/HardwareMonitor/HardwareMonitor.sln @@ -0,0 +1,53 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{45EC0518-E2FF-4278-ABDB-E0A2D79E71BE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenHardwareMonitor.Samples", "samples\OpenHardwareMonitor.Samples.csproj", "{356BCA51-E8A9-4190-A814-70A6F87E33E1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HardwareMonitor", "HardwareMonitor.csproj", "{B9CCBDB6-FAE3-49BF-B654-30698EF8131D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {356BCA51-E8A9-4190-A814-70A6F87E33E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {356BCA51-E8A9-4190-A814-70A6F87E33E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {356BCA51-E8A9-4190-A814-70A6F87E33E1}.Debug|x64.ActiveCfg = Debug|Any CPU + {356BCA51-E8A9-4190-A814-70A6F87E33E1}.Debug|x64.Build.0 = Debug|Any CPU + {356BCA51-E8A9-4190-A814-70A6F87E33E1}.Debug|x86.ActiveCfg = Debug|Any CPU + {356BCA51-E8A9-4190-A814-70A6F87E33E1}.Debug|x86.Build.0 = Debug|Any CPU + {356BCA51-E8A9-4190-A814-70A6F87E33E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {356BCA51-E8A9-4190-A814-70A6F87E33E1}.Release|Any CPU.Build.0 = Release|Any CPU + {356BCA51-E8A9-4190-A814-70A6F87E33E1}.Release|x64.ActiveCfg = Release|Any CPU + {356BCA51-E8A9-4190-A814-70A6F87E33E1}.Release|x64.Build.0 = Release|Any CPU + {356BCA51-E8A9-4190-A814-70A6F87E33E1}.Release|x86.ActiveCfg = Release|Any CPU + {356BCA51-E8A9-4190-A814-70A6F87E33E1}.Release|x86.Build.0 = Release|Any CPU + {B9CCBDB6-FAE3-49BF-B654-30698EF8131D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9CCBDB6-FAE3-49BF-B654-30698EF8131D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9CCBDB6-FAE3-49BF-B654-30698EF8131D}.Debug|x64.ActiveCfg = Debug|Any CPU + {B9CCBDB6-FAE3-49BF-B654-30698EF8131D}.Debug|x64.Build.0 = Debug|Any CPU + {B9CCBDB6-FAE3-49BF-B654-30698EF8131D}.Debug|x86.ActiveCfg = Debug|Any CPU + {B9CCBDB6-FAE3-49BF-B654-30698EF8131D}.Debug|x86.Build.0 = Debug|Any CPU + {B9CCBDB6-FAE3-49BF-B654-30698EF8131D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9CCBDB6-FAE3-49BF-B654-30698EF8131D}.Release|Any CPU.Build.0 = Release|Any CPU + {B9CCBDB6-FAE3-49BF-B654-30698EF8131D}.Release|x64.ActiveCfg = Release|Any CPU + {B9CCBDB6-FAE3-49BF-B654-30698EF8131D}.Release|x64.Build.0 = Release|Any CPU + {B9CCBDB6-FAE3-49BF-B654-30698EF8131D}.Release|x86.ActiveCfg = Release|Any CPU + {B9CCBDB6-FAE3-49BF-B654-30698EF8131D}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {356BCA51-E8A9-4190-A814-70A6F87E33E1} = {45EC0518-E2FF-4278-ABDB-E0A2D79E71BE} + EndGlobalSection +EndGlobal diff --git a/src/devices/HardwareMonitor/OpenHardwareMonitor.cs b/src/devices/HardwareMonitor/OpenHardwareMonitor.cs new file mode 100644 index 0000000000..ca5a89df0c --- /dev/null +++ b/src/devices/HardwareMonitor/OpenHardwareMonitor.cs @@ -0,0 +1,834 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Management; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using UnitsNet; + +// We have a check on all constructors to ensure that we throw when not on Windows, so disabling warning on calling windows-only apis. +#pragma warning disable CA1416 // Validate platform compatibility + +namespace Iot.Device.HardwareMonitor +{ + /// + /// This class connects to a running instance of OpenHardwareMonitor and reads out all available values. + /// This works only if OpenHardwareMonitor (https://openhardwaremonitor.org/) is currently running. + /// While the tool needs to be run with elevated permissions, the application using this binding does not. + /// + public sealed class OpenHardwareMonitor : IDisposable + { + /// + /// This is the monitoring thread interval. All updates will be done in a multiple of this value. + /// + private static readonly TimeSpan DefaultMonitorInterval = TimeSpan.FromMilliseconds(100); + private static readonly TimeSpan DefaultDerivedSensorsInterval = TimeSpan.FromMilliseconds(500); + + private delegate IQuantity UnitCreator(float value); + + /// + /// Event that gets invoked when a value is updated + /// + /// Sensor that has an updated value + /// New value for the sensor + /// Time since the last update of this sensor + public delegate void OnNewValue(Sensor sensor, IQuantity value, TimeSpan timeSinceUpdate); + + private static Dictionary _typeMap; + private Hardware? _cpu; + private Hardware? _gpu; + + private Thread? _monitorThread; + private object _lock; + private List _monitoredElements; + private List _derivedSensors; + private DateTimeOffset _lastMonitorLoop; + private TimeSpan _monitoringInterval; + + static OpenHardwareMonitor() + { + _typeMap = new Dictionary(); + _typeMap.Add(SensorType.Temperature, (typeof(Temperature), (x) => Temperature.FromDegreesCelsius(x))); + _typeMap.Add(SensorType.Voltage, (typeof(ElectricPotential), x => ElectricPotential.FromVolts(x))); + _typeMap.Add(SensorType.Load, (typeof(Ratio), x => Ratio.FromPercent(x))); + _typeMap.Add(SensorType.Fan, (typeof(RotationalSpeed), x => RotationalSpeed.FromRevolutionsPerMinute(x))); + _typeMap.Add(SensorType.Flow, (typeof(VolumeFlow), x => VolumeFlow.FromLitersPerHour(x))); + _typeMap.Add(SensorType.Control, (typeof(Ratio), x => Ratio.FromPercent(x))); + _typeMap.Add(SensorType.Level, (typeof(Ratio), x => Ratio.FromPercent(x))); + _typeMap.Add(SensorType.Power, (typeof(Power), x => Power.FromWatts(x))); + _typeMap.Add(SensorType.Clock, (typeof(Frequency), x => Frequency.FromMegahertz(x))); + _typeMap.Add(SensorType.Energy, (typeof(Energy), x => Energy.FromWattHours(x))); + _typeMap.Add(SensorType.HeatFlux, (typeof(HeatFlux), x => HeatFlux.FromKilowattsPerSquareMeter(x))); + _typeMap.Add(SensorType.Current, (typeof(ElectricCurrent), x => ElectricCurrent.FromAmperes(x))); + } + + /// + /// Constructs a new instance of this class. + /// The class can be constructed even if no sensors are available or OpenHardwareMonitor is not running (yet). + /// + /// The operating system is not Windows. + public OpenHardwareMonitor() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + throw new PlatformNotSupportedException("This class is only supported on Windows operating systems"); + } + + InitHardwareMonitor(); + + _derivedSensors = new List(); + + _monitorThread = null; + _lock = new object(); + _monitoredElements = new List(); + _lastMonitorLoop = DateTimeOffset.UtcNow; + MonitoringInterval = DefaultMonitorInterval; + } + + /// + /// The minimum monitoring interval. + /// + public TimeSpan MonitoringInterval + { + get + { + return _monitoringInterval; + } + + set + { + if (_monitorThread != null) + { + throw new InvalidOperationException($"{nameof(MonitoringInterval)} can only be changed while monitoring is disabled."); + } + + if (value <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(MonitoringInterval)); + } + + _monitoringInterval = value; + } + } + + private void InitHardwareMonitor() + { + try + { + ManagementObjectSearcher searcher = new ManagementObjectSearcher(@"root\OpenHardwareMonitor", "SELECT * FROM Sensor"); + if (searcher.Get().Count > 0) + { + foreach (var hardware in GetHardwareComponents()) + { + if (hardware.Type != null && hardware.Type.Equals("CPU", StringComparison.OrdinalIgnoreCase)) + { + _cpu = hardware; + } + else if (hardware.Type != null && hardware.Type.StartsWith("GPU", StringComparison.OrdinalIgnoreCase)) + { + _gpu = hardware; + } + } + } + + searcher.Dispose(); + } + catch (Exception x) when (x is IOException || x is UnauthorizedAccessException || x is ManagementException) + { + // Nothing to do - WMI not available for this element or missing permissions. + // WMI enumeration may require elevated rights. + } + } + + /// + /// Number of logical processors in the system + /// + public int LogicalProcessors + { + get + { + return Environment.ProcessorCount; + } + } + + /// + /// Query the list of all available sensors. + /// + /// A list of instances. May be empty. + /// The WMI objects required are not available. Is OpenHardwareMonitor running? + public IList GetSensorList() + { + List ret = new List(); + ManagementObjectSearcher searcher = new ManagementObjectSearcher(@"root\OpenHardwareMonitor", "SELECT * FROM Sensor"); + if (searcher.Get().Count > 0) + { + foreach (ManagementObject sensor in searcher.Get()) + { + string? name = Convert.ToString(sensor["Name"]); + string? identifier = Convert.ToString(sensor["Identifier"]); + string? parent = Convert.ToString(sensor["Parent"]); + string? type = Convert.ToString(sensor["SensorType"]); + SensorType typeEnum; + if (!Enum.TryParse(type, true, out typeEnum)) + { + typeEnum = SensorType.Unknown; + } + + ret.Add(new Sensor(sensor, name, identifier, parent, typeEnum)); + } + } + + ret.AddRange(_derivedSensors); + + return ret; + } + + /// + /// Returns a list of hardware components, such as "CPU", "GPU" or "Mainboard" + /// + public IList GetHardwareComponents() + { + IList ret = new List(); + ManagementObjectSearcher searcher = new ManagementObjectSearcher(@"root\OpenHardwareMonitor", "SELECT * FROM Hardware"); + if (searcher.Get().Count > 0) + { + foreach (ManagementObject sensor in searcher.Get()) + { + string? name = Convert.ToString(sensor["Name"]); + string? identifier = Convert.ToString(sensor["Identifier"]); + string? parent = Convert.ToString(sensor["Parent"]); + string? type = Convert.ToString(sensor["HardwareType"]); + ret.Add(new Hardware(name, identifier, parent, type)); + } + } + + return ret; + } + + /// + /// Get the list of sensors for a specific piece of hardware + /// + /// The module that should be queried + /// A list of sensors + public IEnumerable GetSensorList(Hardware? forHardware) + { + if (forHardware == null) + { + throw new ArgumentNullException(nameof(forHardware)); + } + + return GetSensorList().Where(x => x.Identifier != null && x.Identifier.StartsWith(forHardware.Identifier ?? string.Empty)).OrderBy(y => y.Identifier); + } + + // Some well-known properties have their own method + + /// + /// Gets the average CPU temperature (averaged over all CPU sensors / cores) + /// + public bool TryGetAverageCpuTemperature(out Temperature temperature) + { + if (_cpu == null) + { + InitHardwareMonitor(); + } + + if (_cpu == null) + { + temperature = default; + return false; + } + + if (TryGetAverage(_cpu, out temperature)) + { + return true; + } + + return false; + } + + /// + /// Gets the average GPU temperature (averaged over all GPU sensors / cores) + /// + /// The average GPU temperature + public bool TryGetAverageGpuTemperature(out Temperature temperature) + { + if (_gpu == null) + { + InitHardwareMonitor(); + } + + if (_gpu == null) + { + temperature = default; + return false; + } + + if (TryGetAverage(_gpu, out temperature)) + { + return true; + } + + return false; + } + + /// + /// Gets the overall CPU Load + /// + public Ratio GetCpuLoad() + { + foreach (var s in GetSensorList(_cpu).OrderBy(x => x.Identifier)) + { + if (s.SensorType == SensorType.Load && s.TryGetValue(out Ratio load)) + { + return load; + } + } + + return default(Ratio); + } + + /// + /// Tries to calculate the average of a set of sensors. + /// + /// Type of value to query (i.e. Load, Power) + /// The hardware type (i.e. CPU) + /// Gets the returned quantity + /// True if at least one matching quantity was found + /// There were multiple sensors found, but they return different units (i.e. CPU temperature is + /// reported as Celsius for some cores and Fahrenheit for others) + public bool TryGetAverage(Hardware hardware, +#if !NETCOREAPP2_1 + [NotNullWhen(true)] +#endif + out T? average) + where T : IQuantity + { + double value = 0; + int count = 0; + Enum? unitThatWasUsed = null; + foreach (var s in GetSensorList(hardware)) + { + if (s.TryGetValue(out T singleValue)) + { + if (unitThatWasUsed == null) + { +#if NETCOREAPP2_1 + unitThatWasUsed = singleValue!.Unit; +#else + unitThatWasUsed = singleValue.Unit; +#endif + } +#if NETCOREAPP2_1 + else if (!unitThatWasUsed.Equals(singleValue!.Unit)) +#else + else if (!unitThatWasUsed.Equals(singleValue.Unit)) +#endif + { + throw new NotSupportedException($"The different sensors for {hardware.Name} deliver values in different units"); + } + + value += singleValue.Value; + count++; + } + } + + if (count == 0) + { + average = default(T); + return false; + } + + value = value / count; + + average = (T)Quantity.From(value, unitThatWasUsed); + return true; + } + + /// + /// Starts monitoring a sensor. + /// This will internally start a thread that calls the provided action each time the TimeSpan elapses. + /// + /// The sensor to monitor. The same sensor may be registered multiple times + /// The monitoring interval. Will be rounded to the next 0.1s. + /// Action to perform each time + /// An identifier for the monitoring job + public MonitoringJob StartMonitoring(Sensor sensorToMonitor, TimeSpan monitoringInterval, OnNewValue onNewValue) + { + if (sensorToMonitor == null) + { + throw new ArgumentNullException(nameof(sensorToMonitor)); + } + + if (onNewValue == null) + { + throw new ArgumentNullException(nameof(onNewValue)); + } + + if (_monitorThread == null || _monitorThread.IsAlive == false) + { + _monitorThread = new Thread(MonitorThread); + _monitorThread.IsBackground = true; + _monitorThread.Start(); + } + + double roundedInterval = monitoringInterval.TotalSeconds; + // round to the nearest multiple of the thread interval + double fract = roundedInterval % MonitoringInterval.TotalSeconds; + if (fract > 0) + { + // If fract is > 0, step to the next multiple (+ make sure we're really greater) + roundedInterval = roundedInterval - fract + MonitoringInterval.TotalSeconds + 1E-6; + } + + if (roundedInterval < MonitoringInterval.TotalSeconds) + { + roundedInterval = MonitoringInterval.TotalSeconds; + } + + monitoringInterval = TimeSpan.FromSeconds(roundedInterval); + lock (_lock) + { + MonitoringJob job = new MonitoringJob(sensorToMonitor, monitoringInterval, onNewValue); + _monitoredElements.Add(job); + return job; + } + } + + /// + /// Stops monitoring of the given job. + /// + /// Monitoring job + public void StopMonitoring(MonitoringJob job) + { + if (job == null) + { + throw new ArgumentNullException(nameof(job)); + } + + lock (_lock) + { + _monitoredElements.Remove(job); + } + } + + /// + /// Stops all monitoring. + /// + public void StopAllMonitoring() + { + lock (_lock) + { + _monitoredElements.Clear(); + } + + if (_monitorThread != null) + { + _monitorThread.Join(); + _monitorThread = null; + } + } + + private void MonitorThread() + { + // Stops the thread when the list of monitored elements becomes empty + bool running = true; + while (running) + { + lock (_lock) + { + DateTimeOffset now = DateTimeOffset.UtcNow; + if (_lastMonitorLoop < now - TimeSpan.FromMinutes(5)) + { + // We were apparently in sleep mode - skip this loop, because it will falsify results + // (i.e. integrating power usage over a time where the computer was off will be incorrect) + foreach (var elem in _monitoredElements) + { + elem.LastUpdated = now; + } + + _lastMonitorLoop = now; + continue; + } + + foreach (var elem in _monitoredElements) + { + TimeSpan timeSinceLastUpdate = now - elem.LastUpdated; + if (timeSinceLastUpdate > elem.Interval) + { + if (elem.Sensor.TryGetValue(out IQuantity? value)) + { +#if NETCOREAPP2_1 + elem.OnNewValue(elem.Sensor, value!, timeSinceLastUpdate); +#else + elem.OnNewValue(elem.Sensor, value, timeSinceLastUpdate); +#endif + } + + elem.LastUpdated = now; + } + } + + _lastMonitorLoop = now; + running = _monitoredElements.Count > 0; + } + + if (running) + { + Thread.Sleep(MonitoringInterval); + } + } + } + + /// + /// Adds some special derived sensors. + /// - For each power sensor, this adds another sensor that integrates power over time and so generated the energy used in W/h or + /// more conveniently, Kilowatthours (this is the unit the electricity bill bases on) + /// - Gives the heat flux for the primary CPU, using the given CPU die size (or a default value) + /// + /// Die size of your CPU, optional. Find your CPU on https://en.wikichip.org/ to find out. Note: This + /// value is usually much smaller than the size of the physical CPU. + /// Monitoring interval for the derived sensors. Defaults to 500ms. + /// is less than 0. + public void EnableDerivedSensors(Area cpuDieSize = default, TimeSpan monitoringInterval = default) + { + if (cpuDieSize == default) + { + // Values for some recent intel chips (coffee lake) + if (LogicalProcessors <= 4) + { + cpuDieSize = Area.FromSquareMillimeters(126); + } + else + { + cpuDieSize = Area.FromSquareMillimeters(149.6); + } + } + + if (_derivedSensors.Count != 0) + { + // Already set up + return; + } + + Sensor? cpuPower = null; + + TimeSpan interval = monitoringInterval; + if (interval == default) + { + interval = DefaultDerivedSensorsInterval; + } + else if (interval.TotalSeconds < 0) + { + throw new ArgumentOutOfRangeException(nameof(monitoringInterval)); + } + + foreach (var sensor in GetSensorList()) + { + if (sensor.SensorType == SensorType.Power) + { + // Energy usage (integration of power over time) + var managementInstance = new EnergyManagementInstance(); + Sensor newSensor = new Sensor(managementInstance, sensor.Name + " Energy", sensor.Identifier + "/energy", sensor.Identifier, SensorType.Energy); + newSensor.Job = StartMonitoring(sensor, interval, (s, value, timeSinceUpdate) => + { + double previousEnergy = managementInstance.Value; + // Value is in watts, so increment is in watts-hours, which is an unit we can later convert from + double increment = value.Value * timeSinceUpdate.TotalHours; + double newEnergy = previousEnergy + increment; + managementInstance.Value = newEnergy; + }); + _derivedSensors.Add(newSensor); + } + + // For CPU package, calculate heat flux + if (sensor.SensorType == SensorType.Power && sensor.Name != null && sensor.Name.IndexOf("CPU Package", StringComparison.OrdinalIgnoreCase) >= 0) + { + var managementInstance = new EnergyManagementInstance(); + var newSensor = new Sensor(managementInstance, sensor.Name + " HeatFlux", sensor.Identifier + "/heatflux", sensor.Identifier, SensorType.HeatFlux); + newSensor.Job = StartMonitoring(sensor, interval, (s, value, timeSinceUpdate) => + { + Power p = Power.FromWatts(value.Value); // Current power usage in Watts + HeatFlux hf = p / cpuDieSize; + managementInstance.Value = hf.KilowattsPerSquareMeter; + }); + _derivedSensors.Add(newSensor); + cpuPower = sensor; + } + } + + Sensor? vcore = GetSensorList().FirstOrDefault(x => x.SensorType == SensorType.Voltage && x.Name != null && x.Name.IndexOf("CPU VCore", StringComparison.OrdinalIgnoreCase) >= 0); + // From VCore and CPU package power calculate amperes into CPU + if (cpuPower != null && vcore != null) + { + var managementInstance = new EnergyManagementInstance(); + var newSensor = new Sensor(managementInstance, vcore.Name + " Current", vcore.Identifier + "/current", vcore.Identifier, SensorType.Current); + newSensor.Job = StartMonitoring(vcore, interval, (s, value, timeSinceUpdate) => + { + if (cpuPower.TryGetValue(out Power power)) + { + ElectricPotential potential = ElectricPotential.FromVolts(value.Value); + // This function is missing in the library + ElectricCurrent current = ElectricCurrent.FromAmperes(power.Watts / potential.Volts); + managementInstance.Value = current.Amperes; + } + }); + _derivedSensors.Add(newSensor); + } + } + + /// + /// Remove the derived sensors from the active list. + /// + public void DisableDerivedSensors() + { + foreach (var s in _derivedSensors) + { + if (s.Job is object) + { + StopMonitoring(s.Job); + } + + s.Dispose(); + } + + _derivedSensors.Clear(); + } + + /// + public void Dispose() + { + StopAllMonitoring(); + _gpu = null; + _cpu = null; + } + + /// + /// Represents a single sensor + /// + public sealed class Sensor : IDisposable + { + private readonly ManagementObject _instance; + + /// + /// Creates a sensor instance + /// + public Sensor(ManagementObject instance, string? name, string? identifier, string? parent, SensorType typeEnum) + { + _instance = instance; + Name = name; + Identifier = identifier; + Parent = parent; + SensorType = typeEnum; + } + + /// + /// Name of the sensor + /// + public string? Name { get; } + + /// + /// Sensor identifier (device path) + /// + public string? Identifier { get; } + + /// + /// Sensor parent + /// + public string? Parent { get; } + + /// + /// Kind of sensor + /// + public SensorType SensorType { get; } + + /// + /// Job associated with updating this value + /// + internal MonitoringJob? Job + { + get; + set; + } + + /// + /// Attempt to query a value for the sensor + /// + /// Returned value + /// True if a value was available + public bool TryGetValue( +#if !NETCOREAPP2_1 + [NotNullWhen(true)] +#endif + out IQuantity? value) + { + if (!_typeMap.TryGetValue(SensorType, out var elem)) + { + value = null; + return false; + } + + float newValue = Convert.ToSingle(_instance["Value"]); + IQuantity newValueAsUnitInstance = elem.Creator(newValue); + + value = newValueAsUnitInstance; + return true; + } + + /// + /// Attempt to get a value of the provided type + /// + /// The type of the quantity to return + /// The returned value + /// True if a value of type T could be retrieved + public bool TryGetValue( +#if !NETCOREAPP2_1 + [NotNullWhen(true)] +#endif + out T? value) + where T : IQuantity + { + if (!_typeMap.TryGetValue(SensorType, out var elem)) + { + value = default(T); + return false; + } + + if (typeof(T) != elem.Type) + { + value = default(T); + return false; + } + + float newValue = Convert.ToSingle(_instance["Value"]); + object newValueAsUnitInstance = elem.Creator(newValue); + + value = (T)newValueAsUnitInstance; + return true; + } + + /// + public override string? ToString() + { + return Name ?? base.ToString(); + } + + /// + public void Dispose() + { + _instance.Dispose(); + } + } + + /// + /// Represents a piece of hardware + /// + public sealed class Hardware + { + /// + /// Create an instance of this class + /// + public Hardware(string? name, string? identifier, string? parent, string? type) + { + Name = name; + Identifier = identifier; + Parent = parent; + Type = type; + } + + /// + /// Name of the object + /// + public string? Name { get; } + + /// + /// Device path + /// + public string? Identifier { get; } + + /// + /// Parent in device path + /// + public string? Parent { get; } + + /// + /// Type of resource + /// + public string? Type { get; } + + /// + /// Name of this instance + /// + public override string? ToString() + { + return Name ?? base.ToString(); + } + } + + /// + /// A job that monitors a particular sensor + /// + public sealed class MonitoringJob + { + internal MonitoringJob(Sensor sensor, TimeSpan timeSpan, OnNewValue onNewValue) + { + Sensor = sensor; + Interval = timeSpan; + OnNewValue = onNewValue; + LastUpdated = DateTimeOffset.UtcNow; + } + + /// + /// Sensor this job operates on + /// + public Sensor Sensor { get; } + + /// + /// Update interval + /// + public TimeSpan Interval { get; } + internal OnNewValue OnNewValue { get; } + + internal DateTimeOffset LastUpdated + { + get; + set; + } + } + + private sealed class EnergyManagementInstance : ManagementObject + { + // Current value, high precision + private double _value; + + public EnergyManagementInstance() + { + _value = 0; + Properties.Add("Value", 0.0f); + } + + public double Value + { + get + { + return _value; + } + set + { + SetPropertyValue("Value", (float)value); + _value = value; + } + } + } + } +} + +#pragma warning restore CA1416 // Validate platform compatibility diff --git a/src/devices/HardwareMonitor/README.md b/src/devices/HardwareMonitor/README.md new file mode 100644 index 0000000000..17a53d88bb --- /dev/null +++ b/src/devices/HardwareMonitor/README.md @@ -0,0 +1,20 @@ +# OpenHardwareMonitor + +Client binding for OpenHardwareMonitor. + +## Summary + +Returns a set of sensor measurements for the current hardware. The values include CPU temperature(s), Fan Speed(s), GPU Temperature(s) and Hard Disk states. The set of values is hardware dependent. + +## Binding Notes + +This binding works on Windows only. It requires that OpenHardwareMonitor (https://openhardwaremonitor.org/) is running in the background. While that tool requires elevated permissions to work, +the binding (and the application using it) does not. + +The binding supports some additional, "virtual" sensor measuments that are derived from other values. The following extra values are provided: + +- For each sensor returning power, another is generated which calculates energy consumed (by integrating the values over time). +- From the CPU Package power sensor, the CPU heat flux is calculated (estimating the size of the die). +- If both a power and a voltage sensor are available for CPU Package, the current is calculated. + +## References diff --git a/src/devices/HardwareMonitor/SensorType.cs b/src/devices/HardwareMonitor/SensorType.cs new file mode 100644 index 0000000000..cef8609ce1 --- /dev/null +++ b/src/devices/HardwareMonitor/SensorType.cs @@ -0,0 +1,73 @@ +namespace Iot.Device.HardwareMonitor +{ + /// + /// Designates a sensor type + /// + public enum SensorType + { + /// + /// Unknown sensor type + /// + Unknown, + + /// + /// The sensor delivers a voltage + /// + Voltage, + + /// + /// The sensor delivers a clock speed (frequency) + /// + Clock, + + /// + /// The sensor delivers a temperature + /// + Temperature, + + /// + /// The sensor delivers the load percentage of a component + /// + Load, + + /// + /// The sensor is a fan + /// + Fan, + + /// + /// The sensor measures flow (typically in a water cooling system) + /// + Flow, + + /// + /// The sensor is used to control a device (i.e. fan speed) + /// + Control, + + /// + /// The sensor measures the usage level (i.e. disk usage) + /// + Level, + + /// + /// The sensor reports power + /// + Power, + + /// + /// The sensor reports energy used + /// + Energy, + + /// + /// The sensor reports heat flux (thermal heat dissipation) + /// + HeatFlux, + + /// + /// The sensor reports electric current + /// + Current, + } +} diff --git a/src/devices/HardwareMonitor/category.txt b/src/devices/HardwareMonitor/category.txt new file mode 100644 index 0000000000..8d4a8f7ceb --- /dev/null +++ b/src/devices/HardwareMonitor/category.txt @@ -0,0 +1 @@ +thermometer diff --git a/src/devices/HardwareMonitor/samples/OpenHardwareMonitor.Samples.csproj b/src/devices/HardwareMonitor/samples/OpenHardwareMonitor.Samples.csproj new file mode 100644 index 0000000000..562b858a5f --- /dev/null +++ b/src/devices/HardwareMonitor/samples/OpenHardwareMonitor.Samples.csproj @@ -0,0 +1,12 @@ + + + + Exe + net5.0 + + + + + + + diff --git a/src/devices/HardwareMonitor/samples/Program.cs b/src/devices/HardwareMonitor/samples/Program.cs new file mode 100644 index 0000000000..3d9137d3a6 --- /dev/null +++ b/src/devices/HardwareMonitor/samples/Program.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Globalization; +using System.Threading; +using Iot.Device.HardwareMonitor; +using UnitsNet; + +Console.WriteLine("Press any key to quit"); + +OpenHardwareMonitor hw = new OpenHardwareMonitor(); +if (hw.GetSensorList().Count == 0) +{ + Console.WriteLine("OpenHardwareMonitor is not running"); + return; +} + +hw.EnableDerivedSensors(); + +while (!Console.KeyAvailable) +{ + Console.Clear(); + Console.WriteLine("Showing all available sensors (press any key to quit)"); + var components = hw.GetHardwareComponents(); + foreach (var component in components) + { + Console.WriteLine("--------------------------------------------------------------------"); + Console.WriteLine($"{component.Name} Type {component.Type}, Path {component.Identifier}"); + Console.WriteLine("--------------------------------------------------------------------"); + foreach (var sensor in hw.GetSensorList(component)) + { + Console.Write($"{sensor.Name}: Path {sensor.Identifier}, Parent {sensor.Parent} "); + if (sensor.TryGetValue(out IQuantity? quantity)) + { + Console.WriteLine(string.Format(CultureInfo.CurrentCulture, "{0}: {1:g}", quantity.Type, quantity)); + } + else + { + Console.WriteLine($"No data"); + } + } + } + + if (hw.TryGetAverageGpuTemperature(out Temperature gpuTemp) && + hw.TryGetAverageCpuTemperature(out Temperature cpuTemp)) + { + Console.WriteLine($"Averages: CPU temp {cpuTemp:s2}, GPU temp {gpuTemp:s2}, CPU Load {hw.GetCpuLoad()}"); + } + + Thread.Sleep(1000); +} diff --git a/src/devices/Mhz19b/Mhz19b.csproj b/src/devices/Mhz19b/Mhz19b.csproj index c689144081..147dd9744f 100644 --- a/src/devices/Mhz19b/Mhz19b.csproj +++ b/src/devices/Mhz19b/Mhz19b.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/devices/Mpu9250/Mpu9250.csproj b/src/devices/Mpu9250/Mpu9250.csproj index a5f2b0fee1..4a80b8c198 100644 --- a/src/devices/Mpu9250/Mpu9250.csproj +++ b/src/devices/Mpu9250/Mpu9250.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/devices/Pn532/Pn532.csproj b/src/devices/Pn532/Pn532.csproj index f0a255dd7d..3ca1df8ead 100644 --- a/src/devices/Pn532/Pn532.csproj +++ b/src/devices/Pn532/Pn532.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/devices/Tcs3472x/Tcs3472x.csproj b/src/devices/Tcs3472x/Tcs3472x.csproj index 5084bfcb96..ca8af27b22 100644 --- a/src/devices/Tcs3472x/Tcs3472x.csproj +++ b/src/devices/Tcs3472x/Tcs3472x.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/devices/Vl53L0X/Vl53L0X.csproj b/src/devices/Vl53L0X/Vl53L0X.csproj index 5084bfcb96..ca8af27b22 100644 --- a/src/devices/Vl53L0X/Vl53L0X.csproj +++ b/src/devices/Vl53L0X/Vl53L0X.csproj @@ -8,7 +8,7 @@ - +