Skip to content

Commit 17d497e

Browse files
pgrawehrjoperezr
andauthored
CPU Temperature (and similar values) support for Windows (#1238)
* 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 <[email protected]>
1 parent de328ca commit 17d497e

32 files changed

+1323
-61
lines changed

NuGet.config

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
<configuration>
33
<packageSources>
44
<clear />
5+
<!--Begin: Package sources managed by Dependency Flow automation. Do not edit the sources below.-->
6+
<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" />
7+
<!--End: Package sources managed by Dependency Flow automation. Do not edit the sources above.-->
58
<add key="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
69
<add key="dotnet-tools" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" />
710
<add key="dotnet5" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json" />

eng/Version.Details.xml

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,25 @@
1616
</ToolsetDependencies>
1717
<!-- ProductDependencies -->
1818
<ProductDependencies>
19-
<Dependency Name="System.Drawing.Common" Version="4.6.0">
20-
<Uri>https://github.com/dotnet/corefx</Uri>
21-
<Sha>4ac4c0367003fe3973a3648eb0715ddb0e3bbcea</Sha>
19+
<Dependency Name="System.Drawing.Common" Version="5.0.0">
20+
<Uri>https://github.com/dotnet/runtime</Uri>
21+
<Sha>7ef6d50b312217d2f7c17b9697891fa8ab98a19d</Sha>
2222
</Dependency>
23-
<Dependency Name="System.IO.Ports" Version="4.6.0">
24-
<Uri>https://github.com/dotnet/corefx</Uri>
25-
<Sha>4ac4c0367003fe3973a3648eb0715ddb0e3bbcea</Sha>
23+
<Dependency Name="System.IO.Ports" Version="5.0.0">
24+
<Uri>https://github.com/dotnet/runtime</Uri>
25+
<Sha>7ef6d50b312217d2f7c17b9697891fa8ab98a19d</Sha>
2626
</Dependency>
27-
<Dependency Name="Microsoft.Win32.Registry" Version="4.6.0">
28-
<Uri>https://github.com/dotnet/corefx</Uri>
29-
<Sha>4ac4c0367003fe3973a3648eb0715ddb0e3bbcea</Sha>
27+
<Dependency Name="Microsoft.Win32.Registry" Version="5.0.0">
28+
<Uri>https://github.com/dotnet/runtime</Uri>
29+
<Sha>7ef6d50b312217d2f7c17b9697891fa8ab98a19d</Sha>
3030
</Dependency>
3131
<Dependency Name="System.Runtime.WindowsRuntime" Version="4.6.0">
3232
<Uri>https://github.com/dotnet/corefx</Uri>
3333
<Sha>4ac4c0367003fe3973a3648eb0715ddb0e3bbcea</Sha>
3434
</Dependency>
35+
<Dependency Name="System.Management" Version="5.0.0">
36+
<Uri>https://github.com/dotnet/runtime</Uri>
37+
<Sha>7ef6d50b312217d2f7c17b9697891fa8ab98a19d</Sha>
38+
</Dependency>
3539
</ProductDependencies>
3640
</Dependencies>

eng/Versions.props

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,14 @@
44
<PreReleaseVersionLabel>prerelease</PreReleaseVersionLabel>
55
<MicrosoftDotNetGenAPIPackageVersion>6.0.0-beta.20552.5</MicrosoftDotNetGenAPIPackageVersion>
66
<!-- dotnet/corefx dependencies -->
7-
<SystemDrawingCommonPackageVersion>4.6.0</SystemDrawingCommonPackageVersion>
8-
<SystemIOPortsPackageVersion>4.6.0</SystemIOPortsPackageVersion>
9-
<MicrosoftWin32RegistryPackageVersion>4.6.0</MicrosoftWin32RegistryPackageVersion>
7+
<SystemDrawingCommonPackageVersion>5.0.0</SystemDrawingCommonPackageVersion>
8+
<SystemIOPortsPackageVersion>5.0.0</SystemIOPortsPackageVersion>
9+
<MicrosoftWin32RegistryPackageVersion>5.0.0</MicrosoftWin32RegistryPackageVersion>
1010
<SystemRuntimeWindowsRuntimePackageVersion>4.6.0</SystemRuntimeWindowsRuntimePackageVersion>
11+
<SystemManagementPackageVersion>5.0.0</SystemManagementPackageVersion>
12+
<SystemThreadingTasksExtensionsPackageVersion>4.5.4</SystemThreadingTasksExtensionsPackageVersion>
13+
<SystemMemoryPackageVersion>4.5.4</SystemMemoryPackageVersion>
14+
<SystemRuntimeInteropServicesWindowsRuntimePackageVersion>4.3.0</SystemRuntimeInteropServicesWindowsRuntimePackageVersion>
1115
<UnitsNetPackageVersion>4.58.0</UnitsNetPackageVersion>
1216
</PropertyGroup>
13-
<!-- Restore sources -->
14-
<PropertyGroup>
15-
<RestoreSources>
16-
$(RestoreSources);
17-
https://dotnetfeed.blob.core.windows.net/dotnet-tools-internal/index.json;
18-
https://dotnetfeed.blob.core.windows.net/dotnet-iot/index.json;
19-
https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json;
20-
https://dotnet.myget.org/F/dotnet-core/api/v3/index.json;
21-
https://api.nuget.org/v3/index.json;
22-
</RestoreSources>
23-
</PropertyGroup>
2417
</Project>

src/Iot.Device.Bindings/Iot.Device.Bindings.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<PackageReference Include="System.IO.Ports" Version="$(SystemIOPortsPackageVersion)" />
2727
<ProjectReference Include="$(MainLibraryPath)System.Device.Gpio.csproj" />
2828
<PackageReference Include="UnitsNet" Version="$(UnitsNetPackageVersion)" />
29+
<PackageReference Include="System.Management" Version="$(SystemManagementPackageVersion)" />
2930
</ItemGroup>
3031

3132
</Project>

src/System.Device.Gpio/System.Device.Gpio.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@
1414
<None Remove="winmd\**" />
1515
<None Include="buildTransitive\net5.0\System.Device.Gpio.targets" Pack="true" PackagePath="\buildTransitive\net5.0" />
1616
<PackageReference Include="Microsoft.Win32.Registry" Version="$(MicrosoftWin32RegistryPackageVersion)" /> <!-- This is Windows specific -->
17-
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.3" />
18-
<PackageReference Include="System.Memory" Version="4.5.3" />
17+
<PackageReference Include="System.Threading.Tasks.Extensions" Version="$(SystemThreadingTasksExtensionsPackageVersion)" />
18+
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
1919
<PackageReference Include="Microsoft.DotNet.GenAPI" Version="$(MicrosoftDotNetGenApiPackageVersion)">
2020
<PrivateAssets>all</PrivateAssets>
2121
</PackageReference>
2222
</ItemGroup>
2323

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

2828
<Reference Include="Windows.Devices.DevicesLowLevelContract">
2929
<HintPath>winmd\Windows.Devices.DevicesLowLevelContract.winmd</HintPath>

src/System.Device.Gpio/System/Device/Gpio/GpioController.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33

44
using System.Collections.Generic;
55
using System.Device.Gpio.Drivers;
6-
using System.IO;
7-
using System.Text.RegularExpressions;
86
using System.Threading;
97
using System.Threading.Tasks;
108
using Microsoft.Win32;
@@ -402,7 +400,9 @@ private static GpioDriver GetBestDriverForBoardOnLinux()
402400
/// </remarks>
403401
private static GpioDriver GetBestDriverForBoardOnWindows()
404402
{
405-
string? baseBoardProduct = Registry.LocalMachine.GetValue(BaseBoardProductRegistryValue, string.Empty).ToString();
403+
#pragma warning disable CA1416 // Registry.LocalMachine is only supported on Windows, but we will only hit this method if we are on Windows.
404+
string? baseBoardProduct = Registry.LocalMachine.GetValue(BaseBoardProductRegistryValue, string.Empty)?.ToString();
405+
#pragma warning restore CA1416
406406

407407
if (baseBoardProduct is null)
408408
{

src/devices/Ak8963/Ak8963.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<ItemGroup>
99
<Compile Include="*.cs" />
1010
<ProjectReference Include="$(MainLibraryPath)System.Device.Gpio.csproj" />
11-
<PackageReference Include="System.Memory" Version="4.5.3" />
11+
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
1212
<None Include="README.md" />
1313
</ItemGroup>
1414

src/devices/Bno055/Bno055.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<Compile Include="*.cs" />
1010
<Compile Include="Models/*.cs" />
1111
<ProjectReference Include="$(MainLibraryPath)System.Device.Gpio.csproj" />
12-
<PackageReference Include="System.Memory" Version="4.5.3" />
12+
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
1313
</ItemGroup>
1414

1515
</Project>

src/devices/Card/CreditCard/CreditCardProcessing.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
</PropertyGroup>
66

77
<ItemGroup>
8-
<PackageReference Include="System.Memory" Version="4.5.3" />
8+
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
99
<Compile Include="..\..\Common\Iot\Device\Common\NumberHelper.cs" />
1010
</ItemGroup>
1111

src/devices/CpuTemperature/CpuTemperature.cs

100755100644
Lines changed: 153 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,67 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5+
using System.Collections.Generic;
6+
using System.Globalization;
57
using System.IO;
8+
using System.Linq;
9+
using System.Management;
610
using System.Runtime.InteropServices;
11+
using Iot.Device.HardwareMonitor;
712
using UnitsNet;
813

914
namespace Iot.Device.CpuTemperature
1015
{
1116
/// <summary>
12-
/// CPU temperature
17+
/// CPU temperature.
18+
/// On Windows, the value returned is driver dependent and may not represent actual CPU temperature, but more one
19+
/// of the case sensors. Use OpenHardwareMonitor for better environmental representation in Windows.
1320
/// </summary>
14-
public class CpuTemperature
21+
public sealed class CpuTemperature : IDisposable
1522
{
16-
private bool _isAvalable;
23+
private bool _isAvailable;
1724
private bool _checkedIfAvailable;
25+
private bool _windows;
26+
private List<ManagementObjectSearcher> _managementObjectSearchers;
27+
private OpenHardwareMonitor? _hardwareMonitorInUse;
28+
29+
/// <summary>
30+
/// Creates an instance of the CpuTemperature class
31+
/// </summary>
32+
public CpuTemperature()
33+
{
34+
_isAvailable = false;
35+
_checkedIfAvailable = false;
36+
_windows = false;
37+
_managementObjectSearchers = new List<ManagementObjectSearcher>();
38+
_hardwareMonitorInUse = null;
39+
40+
CheckAvailable();
41+
}
1842

1943
/// <summary>
2044
/// Gets CPU temperature
2145
/// </summary>
22-
public Temperature Temperature => Temperature.FromDegreesCelsius(ReadTemperature());
46+
public Temperature Temperature
47+
{
48+
get
49+
{
50+
if (!_windows)
51+
{
52+
return Temperature.FromDegreesCelsius(ReadTemperatureUnix());
53+
}
54+
else
55+
{
56+
List<(string, Temperature)> tempList = ReadTemperatures();
57+
return tempList.FirstOrDefault().Item2;
58+
}
59+
}
60+
}
2361

2462
/// <summary>
2563
/// Is CPU temperature available
2664
/// </summary>
27-
public bool IsAvailable => CheckAvailable();
65+
public bool IsAvailable => _isAvailable;
2866

2967
private bool CheckAvailable()
3068
{
@@ -33,14 +71,101 @@ private bool CheckAvailable()
3371
_checkedIfAvailable = true;
3472
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && File.Exists("/sys/class/thermal/thermal_zone0/temp"))
3573
{
36-
_isAvalable = true;
74+
_isAvailable = true;
75+
_windows = false;
76+
}
77+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
78+
{
79+
OpenHardwareMonitor ohw = new OpenHardwareMonitor();
80+
if (ohw.TryGetAverageCpuTemperature(out _))
81+
{
82+
_windows = true;
83+
_isAvailable = true;
84+
_hardwareMonitorInUse = ohw;
85+
return true;
86+
}
87+
88+
try
89+
{
90+
ManagementObjectSearcher searcher = new ManagementObjectSearcher(@"root\WMI", "SELECT * FROM MSAcpi_ThermalZoneTemperature");
91+
if (searcher.Get().Count > 0)
92+
{
93+
_managementObjectSearchers.Add(searcher);
94+
_isAvailable = true;
95+
_windows = true;
96+
}
97+
}
98+
catch (Exception x) when (x is IOException || x is UnauthorizedAccessException || x is ManagementException)
99+
{
100+
// Nothing to do - WMI not available for this element or missing permissions.
101+
// WMI enumeration may require elevated rights.
102+
}
103+
104+
try
105+
{
106+
ManagementObjectSearcher searcher = new ManagementObjectSearcher(@"root\WMI", "SELECT * FROM Win32_TemperatureProbe");
107+
if (searcher.Get().Count > 0)
108+
{
109+
_managementObjectSearchers.Add(searcher);
110+
_isAvailable = true;
111+
_windows = true;
112+
}
113+
}
114+
catch (Exception x) when (x is IOException || x is UnauthorizedAccessException || x is ManagementException)
115+
{
116+
// Nothing to do - WMI not available for this element or missing permissions.
117+
// WMI enumeration may require elevated rights.
118+
}
119+
37120
}
38121
}
39122

40-
return _isAvalable;
123+
return _isAvailable;
41124
}
42125

43-
private double ReadTemperature()
126+
/// <summary>
127+
/// Returns all known temperature sensor values.
128+
/// </summary>
129+
/// <returns>A list of name/value pairs for temperature sensors</returns>
130+
public List<(string, Temperature)> ReadTemperatures()
131+
{
132+
if (!_windows)
133+
{
134+
var ret = new List<(string, Temperature)>();
135+
ret.Add(("CPU", Temperature.FromDegreesCelsius(ReadTemperatureUnix())));
136+
return ret;
137+
}
138+
139+
// Windows code below
140+
List<(string, Temperature)> result = new List<(string, Temperature)>();
141+
142+
if (_hardwareMonitorInUse != null)
143+
{
144+
if (_hardwareMonitorInUse.TryGetAverageCpuTemperature(out Temperature temp))
145+
{
146+
result.Add(("CPU", temp));
147+
}
148+
149+
return result;
150+
}
151+
152+
foreach (var searcher in _managementObjectSearchers)
153+
{
154+
// This code will only be executed when on Windows.
155+
#pragma warning disable CA1416 // Validate platform compatibility
156+
foreach (ManagementObject obj in searcher.Get())
157+
{
158+
Double temp = Convert.ToDouble(string.Format(CultureInfo.InvariantCulture, "{0}", obj["CurrentTemperature"]), CultureInfo.InvariantCulture);
159+
temp = (temp - 2732) / 10.0;
160+
result.Add((obj["InstanceName"].ToString() ?? string.Empty, Temperature.FromDegreesCelsius(temp)));
161+
}
162+
#pragma warning restore CA1416 // Validate platform compatibility
163+
}
164+
165+
return result;
166+
}
167+
168+
private double ReadTemperatureUnix()
44169
{
45170
double temperature = double.NaN;
46171

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

64189
return temperature;
65190
}
191+
192+
/// <inheritdoc />
193+
public void Dispose()
194+
{
195+
if (_hardwareMonitorInUse != null)
196+
{
197+
_hardwareMonitorInUse.Dispose();
198+
_hardwareMonitorInUse = null;
199+
}
200+
201+
foreach (var elem in _managementObjectSearchers)
202+
{
203+
elem.Dispose();
204+
}
205+
206+
_managementObjectSearchers.Clear();
207+
208+
// Any further calls will fail
209+
_isAvailable = false;
210+
}
66211
}
67212
}

0 commit comments

Comments
 (0)