mirror of
https://github.com/hirschmann/nbfc
synced 2025-09-05 08:35:24 +00:00
[fix] Changed TargetFrameworkVersion of new projects from 4.5 to 4.0 CP. [new] Moved EmbeddedController.cs from OpenHardwareMonitorLib to new StagWare.Hardware library. [chg] Refactored and improved EmbeddedController.cs. [chg] Improved FanControl.Dispose(). [fix] Plugins are now compatible to updated OpenHardwareMonitorLib.
525 lines
14 KiB
C#
525 lines
14 KiB
C#
using StagWare.FanControl.Configurations;
|
|
using StagWare.FanControl.Plugins;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
|
|
namespace StagWare.FanControl
|
|
{
|
|
public class FanControl : IDisposable
|
|
{
|
|
#region Constants
|
|
|
|
private const int MinPollInterval = 100;
|
|
private const int DefaultPollInterval = 3000;
|
|
private const string PluginPath = "Plugins";
|
|
|
|
#endregion
|
|
|
|
#region Private Fields
|
|
|
|
private readonly AutoResetEvent autoEvent = new AutoResetEvent(false);
|
|
private Timer timer;
|
|
private AsyncOperation asyncOp;
|
|
|
|
private readonly int pollInterval;
|
|
private readonly FanControlConfigV2 config;
|
|
|
|
private readonly ITemperatureFilter tempFilter;
|
|
private readonly ITemperatureProvider tempProvider;
|
|
private readonly IEmbeddedController ec;
|
|
|
|
private readonly FanInformation[] fanInfo;
|
|
private readonly FanSpeedManager[] fanSpeeds;
|
|
|
|
private volatile float cpuTemperature;
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
public FanControl(FanControlConfigV2 config)
|
|
: this(PluginPath, config)
|
|
{
|
|
}
|
|
|
|
public FanControl(string pluginsPath, FanControlConfigV2 config) :
|
|
this(
|
|
config,
|
|
new ArithmeticMeanTemperatureFilter(DeliminatePollInterval(config.EcPollInterval)),
|
|
LoadTempProviderPlugin(pluginsPath),
|
|
LoadEcPlugin(pluginsPath))
|
|
{ }
|
|
|
|
public FanControl(
|
|
FanControlConfigV2 config,
|
|
ITemperatureFilter tempFilter,
|
|
ITemperatureProvider tempProvider,
|
|
IEmbeddedController ec)
|
|
{
|
|
if (config == null)
|
|
{
|
|
throw new ArgumentNullException("config");
|
|
}
|
|
|
|
if (tempFilter == null)
|
|
{
|
|
throw new ArgumentNullException("filter");
|
|
}
|
|
|
|
if (tempProvider == null)
|
|
{
|
|
throw new ArgumentNullException("tempProvider");
|
|
}
|
|
|
|
if (ec == null)
|
|
{
|
|
throw new ArgumentNullException("ec");
|
|
}
|
|
|
|
this.tempFilter = tempFilter;
|
|
this.tempProvider = tempProvider;
|
|
this.ec = ec;
|
|
this.config = (FanControlConfigV2)config.Clone();
|
|
this.pollInterval = config.EcPollInterval;
|
|
this.asyncOp = AsyncOperationManager.CreateOperation(null);
|
|
this.fanInfo = new FanInformation[config.FanConfigurations.Count];
|
|
this.fanSpeeds = new FanSpeedManager[config.FanConfigurations.Count];
|
|
|
|
for (int i = 0; i < this.fanInfo.Length; i++)
|
|
{
|
|
var cfg = config.FanConfigurations[i];
|
|
|
|
this.fanSpeeds[i] = new FanSpeedManager(cfg, config.CriticalTemperature);
|
|
this.fanInfo[i] = new FanInformation()
|
|
{
|
|
FanDisplayName = cfg.FanDisplayName
|
|
};
|
|
}
|
|
}
|
|
|
|
#region Contruction Helper Methods
|
|
|
|
private static int DeliminatePollInterval(int pollInterval)
|
|
{
|
|
#region limit poll intervall in release
|
|
|
|
#if !DEBUG
|
|
|
|
if (pollInterval < MinPollInterval)
|
|
{
|
|
return DefaultPollInterval;
|
|
}
|
|
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
return pollInterval;
|
|
}
|
|
|
|
private static IEmbeddedController LoadEcPlugin(string pluginsPath)
|
|
{
|
|
var loader = new EmbeddedControllerPluginLoader(pluginsPath);
|
|
return loader.FanControlPlugin;
|
|
}
|
|
|
|
private static ITemperatureProvider LoadTempProviderPlugin(string pluginsPath)
|
|
{
|
|
var loader = new TemperatureProviderPluginLoader(pluginsPath);
|
|
return loader.FanControlPlugin;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
#region Events
|
|
|
|
public event EventHandler ECUpdated;
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
public float CpuTemperature
|
|
{
|
|
get { return this.cpuTemperature; }
|
|
}
|
|
|
|
public bool Enabled
|
|
{
|
|
get { return this.timer != null; }
|
|
}
|
|
|
|
public IList<FanInformation> FanInformation
|
|
{
|
|
get
|
|
{
|
|
lock (this.fanInfo)
|
|
{
|
|
return this.fanInfo.Select(x => x.Clone() as FanInformation).ToList();
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
|
|
public void Start(int delay = 0)
|
|
{
|
|
if (!this.tempProvider.IsInitialized)
|
|
{
|
|
this.tempProvider.Initialize();
|
|
}
|
|
|
|
if (!this.ec.IsInitialized)
|
|
{
|
|
this.ec.Initialize();
|
|
}
|
|
|
|
if (this.ec.AquireLock(DefaultPollInterval))
|
|
{
|
|
try
|
|
{
|
|
ApplyRegisterWriteConfigurations(true);
|
|
}
|
|
finally
|
|
{
|
|
this.ec.ReleaseLock();
|
|
}
|
|
}
|
|
|
|
if (this.timer == null)
|
|
{
|
|
this.autoEvent.Set();
|
|
this.timer = new Timer(new TimerCallback(TimerCallback), null, delay, this.pollInterval);
|
|
}
|
|
}
|
|
|
|
public void SetTargetFanSpeed(double speed, int fanIndex)
|
|
{
|
|
lock (this.fanSpeeds)
|
|
{
|
|
this.fanSpeeds[fanIndex].UpdateFanSpeed(speed, this.cpuTemperature);
|
|
|
|
lock (this.fanInfo)
|
|
{
|
|
UpdateFanInformation();
|
|
}
|
|
}
|
|
|
|
UpdateEcAsync();
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
if (this.autoEvent != null && !this.autoEvent.SafeWaitHandle.IsClosed)
|
|
{
|
|
this.autoEvent.Reset();
|
|
}
|
|
|
|
if (timer != null)
|
|
{
|
|
using (var handle = new EventWaitHandle(false, EventResetMode.ManualReset))
|
|
{
|
|
timer.Dispose(handle);
|
|
|
|
if (handle.WaitOne())
|
|
{
|
|
timer = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
ResetEc();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Protected Methods
|
|
|
|
protected void OnECUpdated()
|
|
{
|
|
if (ECUpdated != null)
|
|
{
|
|
ECUpdated(this, new EventArgs());
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
#region Update EC
|
|
|
|
private void TimerCallback(object state)
|
|
{
|
|
if (this.autoEvent.WaitOne(pollInterval / 2))
|
|
{
|
|
try
|
|
{
|
|
// Read CPU temperature before the call to UpdateEc(),
|
|
// because both methods try to aquire ISA bus mutex
|
|
this.cpuTemperature = (float)GetTemperature();
|
|
UpdateEc();
|
|
}
|
|
finally
|
|
{
|
|
this.autoEvent.Set();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateEc()
|
|
{
|
|
if (this.ec.AquireLock(pollInterval / 2))
|
|
{
|
|
try
|
|
{
|
|
ApplyRegisterWriteConfigurations();
|
|
UpdateAndApplyFanSpeeds();
|
|
}
|
|
finally
|
|
{
|
|
this.ec.ReleaseLock();
|
|
}
|
|
|
|
asyncOp.Post((object args) => OnECUpdated(), null);
|
|
}
|
|
}
|
|
|
|
private void UpdateAndApplyFanSpeeds()
|
|
{
|
|
lock (this.fanSpeeds)
|
|
{
|
|
int idx = 0;
|
|
|
|
foreach (FanSpeedManager fsm in this.fanSpeeds)
|
|
{
|
|
fsm.UpdateFanSpeed(this.cpuTemperature, fsm.AutoControlEnabled);
|
|
|
|
WriteValue(
|
|
this.config.FanConfigurations[idx].WriteRegister,
|
|
fsm.FanSpeedValue,
|
|
this.config.ReadWriteWords);
|
|
|
|
idx++;
|
|
}
|
|
|
|
lock (this.fanInfo)
|
|
{
|
|
UpdateFanInformation(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateEcAsync()
|
|
{
|
|
var action = new Action<object>(TimerCallback);
|
|
action.BeginInvoke(null, null, null);
|
|
}
|
|
|
|
#region Helper Methods
|
|
|
|
private void UpdateFanInformation(bool isaBusMutexAquired = false)
|
|
{
|
|
for (int i = 0; i < this.fanSpeeds.Length; i++)
|
|
{
|
|
var info = this.fanInfo[i];
|
|
var speed = this.fanSpeeds[i];
|
|
var fanConfig = this.config.FanConfigurations[i];
|
|
|
|
info.CriticalModeEnabled = speed.CriticalModeEnabled;
|
|
info.AutoFanControlEnabled = speed.AutoControlEnabled;
|
|
info.TargetFanSpeed = speed.FanSpeedPercentage;
|
|
|
|
if (isaBusMutexAquired)
|
|
{
|
|
info.CurrentFanSpeed = speed.FanSpeedToPercentage(
|
|
GetFanSpeedValue(fanConfig, this.config.ReadWriteWords));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ApplyRegisterWriteConfigurations(bool initializing = false)
|
|
{
|
|
if (this.config.RegisterWriteConfigurations != null)
|
|
{
|
|
foreach (RegisterWriteConfiguration cfg in this.config.RegisterWriteConfigurations)
|
|
{
|
|
if (initializing || cfg.WriteOccasion == RegisterWriteOccasion.OnWriteFanSpeed)
|
|
{
|
|
ApplyRegisterWriteConfig(cfg.Value, cfg.Register, cfg.WriteMode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ApplyRegisterWriteConfig(int value, int register, RegisterWriteMode mode)
|
|
{
|
|
switch (mode)
|
|
{
|
|
case RegisterWriteMode.And:
|
|
value &= ReadValue(register, config.ReadWriteWords);
|
|
goto case RegisterWriteMode.Set;
|
|
|
|
case RegisterWriteMode.Or:
|
|
value |= ReadValue(register, config.ReadWriteWords);
|
|
goto case RegisterWriteMode.Set;
|
|
|
|
case RegisterWriteMode.Set:
|
|
WriteValue(register, value, config.ReadWriteWords);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
#region R/W EC
|
|
|
|
private void WriteValue(int register, int value, bool writeWord)
|
|
{
|
|
if (writeWord)
|
|
{
|
|
this.ec.WriteWord((byte)register, (ushort)value);
|
|
}
|
|
else
|
|
{
|
|
this.ec.WriteByte((byte)register, (byte)value);
|
|
}
|
|
}
|
|
|
|
private int ReadValue(int register, bool readWord)
|
|
{
|
|
return readWord
|
|
? this.ec.ReadWord((byte)register)
|
|
: this.ec.ReadByte((byte)register);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Reset EC
|
|
|
|
private void ResetEc()
|
|
{
|
|
if (this.config.RegisterWriteConfigurations.Any(x => x.ResetRequired)
|
|
|| this.config.FanConfigurations.Any(x => x.ResetRequired))
|
|
{
|
|
bool mutexAquired = this.ec.AquireLock(DefaultPollInterval / 2);
|
|
|
|
try
|
|
{
|
|
if (config != null)
|
|
{
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
ResetRegisterWriteConfigs();
|
|
ResetFans();
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (mutexAquired)
|
|
{
|
|
this.ec.ReleaseLock();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ResetFans()
|
|
{
|
|
foreach (FanConfiguration cfg in this.config.FanConfigurations)
|
|
{
|
|
if (cfg.ResetRequired)
|
|
{
|
|
WriteValue(cfg.WriteRegister, cfg.FanSpeedResetValue, this.config.ReadWriteWords);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ResetRegisterWriteConfigs()
|
|
{
|
|
foreach (RegisterWriteConfiguration cfg in this.config.RegisterWriteConfigurations)
|
|
{
|
|
if (cfg.ResetRequired)
|
|
{
|
|
ApplyRegisterWriteConfig(cfg.ResetValue, cfg.Register, cfg.ResetWriteMode);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Get Hardware Infos
|
|
|
|
private int GetFanSpeedValue(FanConfiguration cfg, bool readWriteWords)
|
|
{
|
|
int fanSpeed = 0;
|
|
int min = Math.Min(cfg.MinSpeedValue, cfg.MaxSpeedValue);
|
|
int max = Math.Max(cfg.MinSpeedValue, cfg.MaxSpeedValue);
|
|
|
|
// If the value is out of range 3 or more times,
|
|
// minFanSpeed and/or maxFanSpeed are probably wrong.
|
|
for (int i = 0; i <= 2; i++)
|
|
{
|
|
fanSpeed = ReadValue(cfg.ReadRegister, readWriteWords);
|
|
|
|
if ((fanSpeed >= min) && (fanSpeed <= max))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return fanSpeed;
|
|
}
|
|
|
|
private double GetTemperature()
|
|
{
|
|
return this.tempFilter.FilterTemperature(this.tempProvider.GetTemperature());
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
#region IDisposable implementation
|
|
|
|
public void Dispose()
|
|
{
|
|
Stop();
|
|
|
|
if (this.autoEvent != null)
|
|
{
|
|
this.autoEvent.Dispose();
|
|
}
|
|
|
|
if (this.asyncOp != null)
|
|
{
|
|
this.asyncOp.OperationCompleted();
|
|
}
|
|
|
|
if (this.ec != null)
|
|
{
|
|
this.ec.Dispose();
|
|
}
|
|
|
|
if (this.tempProvider != null)
|
|
{
|
|
this.tempProvider.Dispose();
|
|
}
|
|
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|