Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 66 additions & 1 deletion RunCat365/TemperatureRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,22 +69,40 @@ internal sealed class TemperaturePerformanceCounters : InstancedPerformanceCount
}
}

internal sealed class TemperatureHighPrecisionPerformanceCounters : InstancedPerformanceCounters
{
protected override string CategoryName => "Thermal Zone Information";
protected override string CounterName => "High Precision Temperature";

internal static TemperatureHighPrecisionPerformanceCounters? TryCreate()
{
var instance = new TemperatureHighPrecisionPerformanceCounters();
return instance.TryInitialize() ? instance : null;
}
}

internal class TemperatureRepository
{
private const float KELVIN_TO_CELSIUS_OFFSET = 273.15f;
private const float MIN_VALID_TEMPERATURE_CELSIUS = -50.0f;
private const float MAX_VALID_TEMPERATURE_CELSIUS = 150.0f;
private const int REFRESH_INTERVAL_TICKS = 30;
private const int STALE_READ_THRESHOLD = 24;

private readonly TemperaturePerformanceCounters? counters;
private readonly TemperatureHighPrecisionPerformanceCounters? highPrecisionCounters;
private readonly List<float> baselineRawValues = [];
private TemperatureInfo? temperatureInfo;
private int ticksSinceLastRefresh;
private int identicalReadCount;
private bool hasEverVaried;

internal bool IsAvailable => counters is not null;

internal TemperatureRepository()
{
counters = TemperaturePerformanceCounters.TryCreate();
highPrecisionCounters = TemperatureHighPrecisionPerformanceCounters.TryCreate();
}

internal void Update()
Expand All @@ -96,9 +114,13 @@ internal void Update()
{
ticksSinceLastRefresh = 0;
counters.RefreshInstances();
highPrecisionCounters?.RefreshInstances();
}

var rawValues = counters.ReadValues();
var variationValues = highPrecisionCounters is null ? rawValues : highPrecisionCounters.ReadValues();
TrackValueVariation(variationValues);

var temperaturesCelsius = new List<float>(rawValues.Count);
foreach (var temperatureKelvin in rawValues)
{
Expand All @@ -108,7 +130,7 @@ internal void Update()
temperaturesCelsius.Add(temperatureCelsius);
}

temperatureInfo = temperaturesCelsius.Count == 0
temperatureInfo = temperaturesCelsius.Count == 0 || IsLikelyPlaceholder()
? null
: new TemperatureInfo
{
Expand All @@ -117,6 +139,48 @@ internal void Update()
};
}

// Some firmware exposes an ACPI thermal zone that is not wired to a real
// sensor and always reports the same fixed value (e.g. 301 K). Track whether
// the readings have ever changed so such zones can be treated as unavailable
// instead of showing a misleading constant temperature. Variation is tracked
// on the "High Precision Temperature" counter (0.1 K granularity) when it is
// available, because a live sensor jitters at that resolution even when the
// whole-Kelvin "Temperature" counter stays flat on an idle machine; the
// tracking source is fixed at construction so values are never compared
// across the two counters' different scales.
private void TrackValueVariation(List<float> rawValues)
{
if (hasEverVaried || rawValues.Count == 0) return;

var sortedValues = new List<float>(rawValues);
sortedValues.Sort();

if (baselineRawValues.Count != sortedValues.Count)
{
baselineRawValues.Clear();
baselineRawValues.AddRange(sortedValues);
identicalReadCount = 1;
return;
}

for (var i = 0; i < sortedValues.Count; i++)
{
if (baselineRawValues[i] != sortedValues[i])
{
hasEverVaried = true;
baselineRawValues.Clear();
return;
}
}

identicalReadCount += 1;
}

private bool IsLikelyPlaceholder()
{
return !hasEverVaried && STALE_READ_THRESHOLD <= identicalReadCount;
}

internal TemperatureInfo? Get()
{
return temperatureInfo;
Expand All @@ -125,6 +189,7 @@ internal void Update()
internal void Close()
{
counters?.Close();
highPrecisionCounters?.Close();
}
}
}