Skip to content

Commit

Permalink
- Added support for fan controllers that return multiple fan entries …
Browse files Browse the repository at this point in the history
…("Fan 1 speed", "Fan 2 speed", etc)

- I probably violated a dozen of best practices though
- Fixed a bug where status values that are not numbers/floats prevented the plugin from working
- Replaced the hardcoded location of the liquidctl.exe to match the directory where the FanControl.Liquidctl.dll is located. So you can now also install the plugin over the Fan Control interface
  • Loading branch information
sp00n committed May 30, 2024
1 parent fb5f8db commit 1dbb10d
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 14 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
*.git-credentials
FanControl.Liquidctl.zip
liquidctl/
liquidctl.exe

include/


## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
Expand Down
4 changes: 2 additions & 2 deletions FanControl.Liquidctl.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="FanControl.Plugins">
<HintPath>..\..\..\..\Documents\FanControl\FanControl.Plugins.dll</HintPath>
<HintPath>include\FanControl.Plugins.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>..\..\..\..\Documents\FanControl\Newtonsoft.Json.dll</HintPath>
<HintPath>include\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
Expand Down
46 changes: 43 additions & 3 deletions LiquidctlCLIWrapper.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,41 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using Newtonsoft.Json;
using FanControl.Plugins;
using Newtonsoft.Json.Linq;


namespace FanControl.Liquidctl
{
internal static class LiquidctlCLIWrapper
{
public static string liquidctlexe = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "liquidctl.exe"); // This should always resolve to the same directory as the FanControl.Liquidctl.dll //TODO extract path to executable to config
internal static void Initialize()
internal static IPluginLogger logger;

internal static void Initialize(IPluginLogger pluginLogger)
{
logger = pluginLogger;

LiquidctlCall($"--json initialize all");
}

internal static List<LiquidctlStatusJSON> ReadStatus()
{
Process process = LiquidctlCall($"--json status");
return JsonConvert.DeserializeObject<List<LiquidctlStatusJSON>>(process.StandardOutput.ReadToEnd());
//return JsonConvert.DeserializeObject<List<LiquidctlStatusJSON>>(process.StandardOutput.ReadToEnd());
return ParseStatuses(process.StandardOutput.ReadToEnd());
}

internal static List<LiquidctlStatusJSON> ReadStatus(string address)
{
Process process = LiquidctlCall($"--json --address {address} status");
return JsonConvert.DeserializeObject<List<LiquidctlStatusJSON>>(process.StandardOutput.ReadToEnd());
//return JsonConvert.DeserializeObject<List<LiquidctlStatusJSON>>(process.StandardOutput.ReadToEnd());
return ParseStatuses(process.StandardOutput.ReadToEnd());
}

internal static void SetPump(string address, int value)
{
LiquidctlCall($"--address {address} set pump speed {(value)}");
Expand All @@ -33,6 +46,11 @@ internal static void SetFan(string address, int value)
LiquidctlCall($"--address {address} set fan speed {(value)}");
}

internal static void SetFanNumber(string address, int index, int value)
{
LiquidctlCall($"--address {address} set fan{index} speed {(value)}");
}

private static Process LiquidctlCall(string arguments)
{
Process process = new Process();
Expand All @@ -56,5 +74,27 @@ private static Process LiquidctlCall(string arguments)

return process;
}


// Code by akotulu
// See https://github.com/jmarucha/FanControl.Liquidctl/pull/29/commits/145978bdf1c2d1a464b2a036b4fc26f559bb77dc#diff-d7a2c0cf4c270870ed263c55d2cd4fc41258347085a3cded3a78b48e73f78092
private static List<LiquidctlStatusJSON> ParseStatuses(string json) {
JArray statusArray = JArray.Parse(json);
List<LiquidctlStatusJSON> statuses = new List<LiquidctlStatusJSON>();

foreach (JObject statusObject in statusArray) {
try
{
LiquidctlStatusJSON status = statusObject.ToObject<LiquidctlStatusJSON>();
statuses.Add(status);
}
catch (Exception e)
{
logger.Log($"Unable to parse {statusObject}\n{e.Message}");
}
}

return statuses;
}
}
}
151 changes: 146 additions & 5 deletions LiquidctlDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public LiquidTemperature(LiquidctlStatusJSON output)
_name = $"Liquid Temp. - {output.description}";
UpdateFromJSON(output);
}

public void UpdateFromJSON(LiquidctlStatusJSON output)
{
_value = (float)output.status.Single(entry => entry.key == KEY).value;
Expand All @@ -33,6 +34,7 @@ public void UpdateFromJSON(LiquidctlStatusJSON output)
public void Update()
{ } // plugin updates sensors
}

public class PumpSpeed : IPluginSensor
{
public PumpSpeed(LiquidctlStatusJSON output)
Expand All @@ -41,6 +43,7 @@ public PumpSpeed(LiquidctlStatusJSON output)
_name = $"Pump - {output.description}";
UpdateFromJSON(output);
}

public void UpdateFromJSON(LiquidctlStatusJSON output)
{
_value = (float)output.status.Single(entry => entry.key == KEY).value;
Expand All @@ -59,6 +62,7 @@ public void UpdateFromJSON(LiquidctlStatusJSON output)
public void Update()
{ } // plugin updates sensors
}

public class PumpDuty : IPluginControlSensor
{
public PumpDuty(LiquidctlStatusJSON output)
Expand Down Expand Up @@ -87,6 +91,7 @@ public void UpdateFromJSON(LiquidctlStatusJSON output)
{2780, 90}, {2789, 91}, {2798, 92}, {2807, 93}, {2816, 94}, {2825, 95}, {2834, 96}, {2843, 97}, {2852, 98}, {2861, 99},
{MAX_RPM, 100}
};

static readonly int MAX_RPM = 2870;

public string Id => _id;
Expand Down Expand Up @@ -122,6 +127,7 @@ public FanSpeed(LiquidctlStatusJSON output)
_name = $"Fan - {output.description}";
UpdateFromJSON(output);
}

public void UpdateFromJSON(LiquidctlStatusJSON output)
{
_value = (float)output.status.Single(entry => entry.key == KEY).value;
Expand Down Expand Up @@ -150,6 +156,7 @@ public FanControl(LiquidctlStatusJSON output)
_name = $"Fan Control - {output.description}";
UpdateFromJSON(output);
}

// We can only estimate, as it is not provided in any output
public void UpdateFromJSON(LiquidctlStatusJSON output)
{
Expand All @@ -171,6 +178,7 @@ public void UpdateFromJSON(LiquidctlStatusJSON output)
{1530, 90}, {1550, 91}, {1570, 92}, {1590, 93}, {1610, 94}, {1630, 95}, {1650, 96}, {1670, 97}, {1690, 98}, {1720, 99},
{MAX_RPM, 100}
};

static readonly int MAX_RPM = 1980;

public string Id => _id;
Expand All @@ -197,32 +205,145 @@ public void Update()
{ } // plugin updates sensors
}

// Try to get the speeds for multiple fans
public class FanSpeedMultiple : IPluginSensor
{
public FanSpeedMultiple(int index, LiquidctlStatusJSON output)
{
_id = $"{output.address}-fan{index}rpm";
_name = $"Fan {index} - {output.description}";
UpdateFromJSON(index, output);
}

public void UpdateFromJSON(int index, LiquidctlStatusJSON output)
{
string currentKey = KEY.Replace("###", index.ToString());
_value = (float) output.status.Single(entry => entry.key == currentKey).value;
}

public static string KEY = "Fan ### speed";

public string Id => _id;
readonly string _id;

public string Name => _name;
readonly string _name;

public float? Value => _value;
float _value;

public void Update() { } // plugin updates sensors
}

// Try to control multiple fans
public class FanControlMultiple : IPluginControlSensor
{
public FanControlMultiple(int index, LiquidctlStatusJSON output)
{
_address = output.address;
_id = $"{output.address}-fan{index}ctrl";
_name = $"Fan {index} Control - {output.description}";
_index = index;

UpdateFromJSON(index, output);
}

// We can only estimate, as it is not provided in any output
public void UpdateFromJSON(int index, LiquidctlStatusJSON output) {
string currentKey = FanSpeedMultiple.KEY.Replace("###", index.ToString());
float reading = (float)output.status.Single(entry => entry.key == currentKey).value;
//_value = reading > MAX_RPM ? 100.0f : (float)Math.Ceiling(100.0f * reading / MAX_RPM);
_value = RPM_LOOKUP.OrderBy(e => Math.Abs(e.Key - reading)).FirstOrDefault().Value;
}

public static string KEY = "Fan ### speed";
//public static string KEY = $"Fan {_index} speed";

static readonly Dictionary<int, int> RPM_LOOKUP = new Dictionary<int, int>
{ // We can only estimate, as it is not provided in any output. Hence I applied this ugly hack
{520, 20}, {521, 21}, {522, 22}, {523, 23}, {524, 24}, {525, 25}, {526, 26}, {527, 27}, {528, 28}, {529, 29},
{530, 30}, {532, 31}, {534, 32}, {536, 33}, {538, 34}, {540, 35}, {542, 36}, {544, 37}, {546, 38}, {548, 39},
{550, 40}, {571, 41}, {592, 42}, {613, 43}, {634, 44}, {655, 45}, {676, 46}, {697, 47}, {718, 48}, {739, 49},
{760, 50}, {781, 51}, {802, 52}, {823, 53}, {844, 54}, {865, 55}, {886, 56}, {907, 57}, {928, 58}, {949, 59},
{970, 60}, {989, 61}, {1008, 62}, {1027, 63}, {1046, 64}, {1065, 65}, {1084, 66}, {1103, 67}, {1122, 68}, {1141, 69},
{1160, 70}, {1180, 71}, {1200, 72}, {1220, 73}, {1240, 74}, {1260, 75}, {1280, 76}, {1300, 77}, {1320, 78}, {1340, 79},
{1360, 80}, {1377, 81}, {1394, 82}, {1411, 83}, {1428, 84}, {1445, 85}, {1462, 86}, {1479, 87}, {1496, 88}, {1513, 89},
{1530, 90}, {1550, 91}, {1570, 92}, {1590, 93}, {1610, 94}, {1630, 95}, {1650, 96}, {1670, 97}, {1690, 98}, {1720, 99},
{MAX_RPM, 100}
};

static readonly int MAX_RPM = 1980;

public string Id => _id;
readonly string _id;
string _address;

public string Name => _name;
readonly string _name;

public float? Value => _value;
float _value;

public int Index => _index;
int _index;

public void Reset()
{
Set(50.0f);
}

public void Set(float val)
{
LiquidctlCLIWrapper.SetFanNumber(_address, _index, (int) val);
}

public void Update() { } // plugin updates sensors
}

public LiquidctlDevice(LiquidctlStatusJSON output, IPluginLogger pluginLogger)
{
logger = pluginLogger;
address = output.address;

hasPumpSpeed = output.status.Exists(entry => entry.key == PumpSpeed.KEY && !(entry.value is null));
if (hasPumpSpeed)
if (hasPumpSpeed) {
pumpSpeed = new PumpSpeed(output);
}

hasPumpDuty = output.status.Exists(entry => entry.key == PumpDuty.KEY && !(entry.value is null));
if (hasPumpDuty)
if (hasPumpDuty) {
pumpDuty = new PumpDuty(output);
}

hasFanSpeed = output.status.Exists(entry => entry.key == FanSpeed.KEY && !(entry.value is null));
if(hasFanSpeed)
{
if (hasFanSpeed) {
fanSpeed = new FanSpeed(output);
fanControl = new FanControl(output);
}

hasLiquidTemperature = output.status.Exists(entry => entry.key == LiquidTemperature.KEY && !(entry.value is null));
if (hasLiquidTemperature)
if (hasLiquidTemperature) {
liquidTemperature = new LiquidTemperature(output);
}


// Get the info for multiple fans
for (int i=0; i<20; i++) {
int index = i+1;
string currentKey = FanSpeedMultiple.KEY.Replace("###", index.ToString());
hasMultipleFanSpeed[i] = output.status.Exists(entry => entry.key == currentKey && !(entry.value is null));

if (hasMultipleFanSpeed[i]) {
fanSpeedMultiple[i] = new FanSpeedMultiple(index, output);
fanControlMultiple[i] = new FanControlMultiple(index, output);
}
}
}


public readonly bool hasPumpSpeed, hasPumpDuty, hasLiquidTemperature, hasFanSpeed;
public readonly bool[] hasMultipleFanSpeed = new bool[20];


public void UpdateFromJSON(LiquidctlStatusJSON output)
{
Expand All @@ -233,8 +354,16 @@ public void UpdateFromJSON(LiquidctlStatusJSON output)
fanSpeed.UpdateFromJSON(output);
fanControl.UpdateFromJSON(output);
}

for (int i = 0; i<20; i++) {
if (hasMultipleFanSpeed[i]) {
fanSpeedMultiple[i].UpdateFromJSON(i+1, output);
fanControlMultiple[i].UpdateFromJSON(i+1, output);
}
}
}


internal IPluginLogger logger;
public string address;
public LiquidTemperature liquidTemperature;
Expand All @@ -243,6 +372,10 @@ public void UpdateFromJSON(LiquidctlStatusJSON output)
public FanSpeed fanSpeed;
public FanControl fanControl;

public FanSpeedMultiple[] fanSpeedMultiple = new FanSpeedMultiple[20];
public FanControlMultiple[] fanControlMultiple = new FanControlMultiple[20];


public void LoadJSON()
{
try
Expand All @@ -256,12 +389,20 @@ public void LoadJSON()
}
}


public String GetDeviceInfo() {
String ret = $"Device @ {address}";
if (hasLiquidTemperature) ret += $", Liquid @ {liquidTemperature.Value}";
if (hasPumpSpeed) ret += $", Pump @ {pumpSpeed.Value}";
if (hasPumpDuty) ret += $"({pumpDuty.Value})";
if (hasFanSpeed) ret += $", Fan @ {fanSpeed.Value} ({fanControl.Value})";

for (int i = 0; i<20; i++) {
if (hasMultipleFanSpeed[i]) {
ret += $", Fan{i+1} @ {fanSpeedMultiple[i].Value} ({fanControlMultiple[i].Value})";
}
}

return ret;
}
}
Expand Down
Loading

0 comments on commit 1dbb10d

Please sign in to comment.