Skip to content

Commit

Permalink
CPU Temperature (and similar values) support for Windows (#1238)
Browse files Browse the repository at this point in the history
* Add basic CpuTemperature implementation for Windows

Downside: Requires elevated permissions

* Clean init sequence

Properties should not have side effects

* Add application manifest for Windows

On Windows, the example requires elevated permissions. It
may still not find a suitable sensor, dependent on the hardware.

* Update readme

* Add new binding for OpenHardwareMonitor connection

* Clean up

* Add documentation

* Better documentation for TimeSpan constants

* Allow arbitrary thread cycle times

* Value must not be negative

* More generic binding name

Also changed namespace, to prevent namespace to class name collisions

* Use HardwareMonitor on Windows, if available

And add missing Dispose

* Use more recent version of System.Management

Latest Version not available on official sources yet

* Minor documentation improvements

* Updating dependency package versions from dotnet/runtime and adding new package subscription

* Fixing build break due to dependencies

* Removing restore sources so that NuGet.Config is used

* Rebasing and addressing nullability changes

* Addressing feedback

Co-authored-by: Jose Perez Rodriguez <joperezr@microsoft.com>
  • Loading branch information
pgrawehr and joperezr authored Nov 9, 2020
1 parent de328ca commit 17d497e
Show file tree
Hide file tree
Showing 32 changed files with 1,323 additions and 61 deletions.
3 changes: 3 additions & 0 deletions NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
<configuration>
<packageSources>
<clear />
<!--Begin: Package sources managed by Dependency Flow automation. Do not edit the sources below.-->
<add key="darc-pub-dotnet-runtime-7ef6d50" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-runtime-7ef6d50b/nuget/v3/index.json" />
<!--End: Package sources managed by Dependency Flow automation. Do not edit the sources above.-->
<add key="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
<add key="dotnet-tools" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" />
<add key="dotnet5" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json" />
Expand Down
22 changes: 13 additions & 9 deletions eng/Version.Details.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,25 @@
</ToolsetDependencies>
<!-- ProductDependencies -->
<ProductDependencies>
<Dependency Name="System.Drawing.Common" Version="4.6.0">
<Uri>https://github.com/dotnet/corefx</Uri>
<Sha>4ac4c0367003fe3973a3648eb0715ddb0e3bbcea</Sha>
<Dependency Name="System.Drawing.Common" Version="5.0.0">
<Uri>https://github.com/dotnet/runtime</Uri>
<Sha>7ef6d50b312217d2f7c17b9697891fa8ab98a19d</Sha>
</Dependency>
<Dependency Name="System.IO.Ports" Version="4.6.0">
<Uri>https://github.com/dotnet/corefx</Uri>
<Sha>4ac4c0367003fe3973a3648eb0715ddb0e3bbcea</Sha>
<Dependency Name="System.IO.Ports" Version="5.0.0">
<Uri>https://github.com/dotnet/runtime</Uri>
<Sha>7ef6d50b312217d2f7c17b9697891fa8ab98a19d</Sha>
</Dependency>
<Dependency Name="Microsoft.Win32.Registry" Version="4.6.0">
<Uri>https://github.com/dotnet/corefx</Uri>
<Sha>4ac4c0367003fe3973a3648eb0715ddb0e3bbcea</Sha>
<Dependency Name="Microsoft.Win32.Registry" Version="5.0.0">
<Uri>https://github.com/dotnet/runtime</Uri>
<Sha>7ef6d50b312217d2f7c17b9697891fa8ab98a19d</Sha>
</Dependency>
<Dependency Name="System.Runtime.WindowsRuntime" Version="4.6.0">
<Uri>https://github.com/dotnet/corefx</Uri>
<Sha>4ac4c0367003fe3973a3648eb0715ddb0e3bbcea</Sha>
</Dependency>
<Dependency Name="System.Management" Version="5.0.0">
<Uri>https://github.com/dotnet/runtime</Uri>
<Sha>7ef6d50b312217d2f7c17b9697891fa8ab98a19d</Sha>
</Dependency>
</ProductDependencies>
</Dependencies>
21 changes: 7 additions & 14 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,14 @@
<PreReleaseVersionLabel>prerelease</PreReleaseVersionLabel>
<MicrosoftDotNetGenAPIPackageVersion>6.0.0-beta.20552.5</MicrosoftDotNetGenAPIPackageVersion>
<!-- dotnet/corefx dependencies -->
<SystemDrawingCommonPackageVersion>4.6.0</SystemDrawingCommonPackageVersion>
<SystemIOPortsPackageVersion>4.6.0</SystemIOPortsPackageVersion>
<MicrosoftWin32RegistryPackageVersion>4.6.0</MicrosoftWin32RegistryPackageVersion>
<SystemDrawingCommonPackageVersion>5.0.0</SystemDrawingCommonPackageVersion>
<SystemIOPortsPackageVersion>5.0.0</SystemIOPortsPackageVersion>
<MicrosoftWin32RegistryPackageVersion>5.0.0</MicrosoftWin32RegistryPackageVersion>
<SystemRuntimeWindowsRuntimePackageVersion>4.6.0</SystemRuntimeWindowsRuntimePackageVersion>
<SystemManagementPackageVersion>5.0.0</SystemManagementPackageVersion>
<SystemThreadingTasksExtensionsPackageVersion>4.5.4</SystemThreadingTasksExtensionsPackageVersion>
<SystemMemoryPackageVersion>4.5.4</SystemMemoryPackageVersion>
<SystemRuntimeInteropServicesWindowsRuntimePackageVersion>4.3.0</SystemRuntimeInteropServicesWindowsRuntimePackageVersion>
<UnitsNetPackageVersion>4.58.0</UnitsNetPackageVersion>
</PropertyGroup>
<!-- Restore sources -->
<PropertyGroup>
<RestoreSources>
$(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;
</RestoreSources>
</PropertyGroup>
</Project>
1 change: 1 addition & 0 deletions src/Iot.Device.Bindings/Iot.Device.Bindings.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<PackageReference Include="System.IO.Ports" Version="$(SystemIOPortsPackageVersion)" />
<ProjectReference Include="$(MainLibraryPath)System.Device.Gpio.csproj" />
<PackageReference Include="UnitsNet" Version="$(UnitsNetPackageVersion)" />
<PackageReference Include="System.Management" Version="$(SystemManagementPackageVersion)" />
</ItemGroup>

</Project>
6 changes: 3 additions & 3 deletions src/System.Device.Gpio/System.Device.Gpio.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@
<None Remove="winmd\**" />
<None Include="buildTransitive\net5.0\System.Device.Gpio.targets" Pack="true" PackagePath="\buildTransitive\net5.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="$(MicrosoftWin32RegistryPackageVersion)" /> <!-- This is Windows specific -->
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.3" />
<PackageReference Include="System.Memory" Version="4.5.3" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="$(SystemThreadingTasksExtensionsPackageVersion)" />
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
<PackageReference Include="Microsoft.DotNet.GenAPI" Version="$(MicrosoftDotNetGenApiPackageVersion)">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="System.Runtime.WindowsRuntime" Version="$(SystemRuntimeWindowsRuntimePackageVersion)" />
<PackageReference Include="System.Runtime.InteropServices.WindowsRuntime" Version="4.3.0" />
<PackageReference Include="System.Runtime.InteropServices.WindowsRuntime" Version="$(SystemRuntimeInteropServicesWindowsRuntimePackageVersion)" />

<Reference Include="Windows.Devices.DevicesLowLevelContract">
<HintPath>winmd\Windows.Devices.DevicesLowLevelContract.winmd</HintPath>
Expand Down
6 changes: 3 additions & 3 deletions src/System.Device.Gpio/System/Device/Gpio/GpioController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -402,7 +400,9 @@ private static GpioDriver GetBestDriverForBoardOnLinux()
/// </remarks>
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)
{
Expand Down
2 changes: 1 addition & 1 deletion src/devices/Ak8963/Ak8963.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<ItemGroup>
<Compile Include="*.cs" />
<ProjectReference Include="$(MainLibraryPath)System.Device.Gpio.csproj" />
<PackageReference Include="System.Memory" Version="4.5.3" />
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
<None Include="README.md" />
</ItemGroup>

Expand Down
2 changes: 1 addition & 1 deletion src/devices/Bno055/Bno055.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<Compile Include="*.cs" />
<Compile Include="Models/*.cs" />
<ProjectReference Include="$(MainLibraryPath)System.Device.Gpio.csproj" />
<PackageReference Include="System.Memory" Version="4.5.3" />
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion src/devices/Card/CreditCard/CreditCardProcessing.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Memory" Version="4.5.3" />
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
<Compile Include="..\..\Common\Iot\Device\Common\NumberHelper.cs" />
</ItemGroup>

Expand Down
161 changes: 153 additions & 8 deletions src/devices/CpuTemperature/CpuTemperature.cs
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
/// <summary>
/// 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.
/// </summary>
public class CpuTemperature
public sealed class CpuTemperature : IDisposable
{
private bool _isAvalable;
private bool _isAvailable;
private bool _checkedIfAvailable;
private bool _windows;
private List<ManagementObjectSearcher> _managementObjectSearchers;
private OpenHardwareMonitor? _hardwareMonitorInUse;

/// <summary>
/// Creates an instance of the CpuTemperature class
/// </summary>
public CpuTemperature()
{
_isAvailable = false;
_checkedIfAvailable = false;
_windows = false;
_managementObjectSearchers = new List<ManagementObjectSearcher>();
_hardwareMonitorInUse = null;

CheckAvailable();
}

/// <summary>
/// Gets CPU temperature
/// </summary>
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;
}
}
}

/// <summary>
/// Is CPU temperature available
/// </summary>
public bool IsAvailable => CheckAvailable();
public bool IsAvailable => _isAvailable;

private bool CheckAvailable()
{
Expand All @@ -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()
/// <summary>
/// Returns all known temperature sensor values.
/// </summary>
/// <returns>A list of name/value pairs for temperature sensors</returns>
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;

Expand All @@ -63,5 +188,25 @@ private double ReadTemperature()

return temperature;
}

/// <inheritdoc />
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;
}
}
}
11 changes: 9 additions & 2 deletions src/devices/CpuTemperature/CpuTemperature.csproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net5.0;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net5.0;netcoreapp2.1</TargetFrameworks>
<EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>

<ItemGroup>
<Compile Include="CpuTemperature.cs" />
<None Include="README.md" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../HardwareMonitor/HardwareMonitor.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Management" Version="$(SystemManagementPackageVersion)" />
</ItemGroup>

</Project>
Loading

0 comments on commit 17d497e

Please sign in to comment.