From 1ebc639e741c30372d70d6dbcbe33d31d2b0bac1 Mon Sep 17 00:00:00 2001 From: NeoAcheron <12508986+NeoAcheron@users.noreply.github.com> Date: Tue, 2 Jun 2020 13:14:55 +0200 Subject: [PATCH] Added support for NZXT Kraken X3 devices. (#248) Thanks to @NeoAcheron --- .../LibreHardwareMonitor.csproj | 4 - LibreHardwareMonitor/Resources/acicon.png | Bin 904 -> 0 bytes LibreHardwareMonitor/Resources/bigng.png | Bin 702 -> 0 bytes LibreHardwareMonitor/UI/HardwareTypeImage.cs | 15 +- LibreHardwareMonitor/Utilities/HttpServer.cs | 10 +- LibreHardwareMonitorLib/Hardware/Computer.cs | 4 + .../Hardware/Controller/AeroCool/P7-H1.cs | 2 +- .../Controller/AquaComputer/AquastreamXT.cs | 2 +- .../Hardware/Controller/AquaComputer/MPS.cs | 2 +- .../Controller/Heatmaster/Heatmaster.cs | 2 +- .../Hardware/Controller/Nzxt/KrakenX3.cs | 159 ++++++++++++++++++ .../Hardware/Controller/Nzxt/NzxtGroup.cs | 70 ++++++++ .../Controller/TBalancer/TBalancer.cs | 2 +- LibreHardwareMonitorLib/Hardware/IHardware.cs | 5 +- .../LibreHardwareMonitorLib.csproj | 2 + 15 files changed, 246 insertions(+), 33 deletions(-) delete mode 100644 LibreHardwareMonitor/Resources/acicon.png delete mode 100644 LibreHardwareMonitor/Resources/bigng.png create mode 100644 LibreHardwareMonitorLib/Hardware/Controller/Nzxt/KrakenX3.cs create mode 100644 LibreHardwareMonitorLib/Hardware/Controller/Nzxt/NzxtGroup.cs diff --git a/LibreHardwareMonitor/LibreHardwareMonitor.csproj b/LibreHardwareMonitor/LibreHardwareMonitor.csproj index f2dd072..25a9fce 100644 --- a/LibreHardwareMonitor/LibreHardwareMonitor.csproj +++ b/LibreHardwareMonitor/LibreHardwareMonitor.csproj @@ -169,7 +169,6 @@ PortForm.cs - @@ -303,9 +302,6 @@ - - - diff --git a/LibreHardwareMonitor/Resources/acicon.png b/LibreHardwareMonitor/Resources/acicon.png deleted file mode 100644 index 26ae763be72d3a20157a5fa42ea181b302acd363..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 904 zcmV;319$w1P)ogJyS@Mb0~JX` zK~y-)eUfcxRdpQ4KfnJu_niBD$JWeTW>>S?3aro>`obr2B{_tO0-=Y!NSI}?B9h6V z7mbi82SZ0tSg(pQGR;;PVFb3!4aIP_+U~N4m2EYB&-)MjejmPa zUB_!X=DYd@m98Cjs9GMbidBmz&!rm!KUupzZpplIXaY;5;A`_i&QS*@3sb{%-@y%U z{D7=@cJGDp`rEH+DvfX^dn;Nj64fG&rE~sN=yc`Rv>rHl{`cx1zG(=J*^XgrRdf9A z>*Jdb-h0hOS=s&G7wNFL`qR1%*rtj?qeUUrfCm33&ZqKNq7fJs*sX+PAg^j;Qu&O% z!M4SuhLX!@j)pKg@l34)FBaeHR22BHrWWDeCOTRT$4LYGF9R2 z?BlqxfQk<$ssO@V1n>gA3Ba0d?d%$=Hf1IYsboWDg3#EUom$MEZ?9@PImN)17zc?|f{~Ela&Ax+Q@pFSS(-aO((GFF|3`3u6<5es<`{sQ z+?FrcN?xi&)?WEC>Tnew^FIj8x{57eJ_AqyUVDCL!j>Kbp^vM3$B=%+dEosiENB84=ZTaNK6})Ycb(RroTR}_D=`V eDX)Er5AY`;icWfw&Zb2G0000Px#24YJ`L;x57FaROJ(US52000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igG% z5dk<8_CX>@2HM@dakSAh-}0006RNkl@3%2^<}xdnV=Hk(jmpa065Am<#15{ce+DIFL9q^9ItaTZv`YlR zckGgO@*reV9U_PfO;|w;(Prf$X6+OIkWAY5zHf(5caYh0c;S7X_j#YoBaS3405c;o zG9v*oBQX)1#K=qpNQ4ZCgbYXu7*ti6l+rhuuXR?r_tnR)-B>V;GsAHl`huMv0FVII z{F_?Tm`Ew9Y_@ENE7>AOoU;pt0RTd3w|lO_1{y0p@s3_V(%2-SNlhxL>@YARm5QFd z9PR4XgwR*VK!~EJ|J`FE(wOMsiCfLpUQKHD>j#AppfN}dKnUQ<(G}mg4FGo7w+)=T z2PTU{@o2dD*xc;g`LOiCo02oQ`Y_0F_zLWcwb^YRiKTxx~#_RQzIV(b;;Obib zXnU)p+<_5yLwVh$vh8>7P5t@4JepRFJk*saYLA}W z-*o*oJAZml-OG(); RemoveType(); RemoveType(); + RemoveType(); } } @@ -387,6 +390,7 @@ namespace LibreHardwareMonitor.Hardware Add(new HeatmasterGroup(_settings)); Add(new AquaComputerGroup(_settings)); Add(new AeroCoolGroup(_settings)); + Add(new NzxtGroup(_settings)); } if (_storageEnabled) diff --git a/LibreHardwareMonitorLib/Hardware/Controller/AeroCool/P7-H1.cs b/LibreHardwareMonitorLib/Hardware/Controller/AeroCool/P7-H1.cs index 5a49195..57cb955 100644 --- a/LibreHardwareMonitorLib/Hardware/Controller/AeroCool/P7-H1.cs +++ b/LibreHardwareMonitorLib/Hardware/Controller/AeroCool/P7-H1.cs @@ -41,7 +41,7 @@ namespace LibreHardwareMonitor.Hardware.Controller.AeroCool public override HardwareType HardwareType { - get { return HardwareType.AeroCool; } + get { return HardwareType.Cooler; } } public int HubNumber { get; } diff --git a/LibreHardwareMonitorLib/Hardware/Controller/AquaComputer/AquastreamXT.cs b/LibreHardwareMonitorLib/Hardware/Controller/AquaComputer/AquastreamXT.cs index 94b5a75..c79d9bb 100644 --- a/LibreHardwareMonitorLib/Hardware/Controller/AquaComputer/AquastreamXT.cs +++ b/LibreHardwareMonitorLib/Hardware/Controller/AquaComputer/AquastreamXT.cs @@ -79,7 +79,7 @@ namespace LibreHardwareMonitor.Hardware.Controller.AquaComputer public override HardwareType HardwareType { - get { return HardwareType.AquaComputer; } + get { return HardwareType.Cooler; } } public string Status diff --git a/LibreHardwareMonitorLib/Hardware/Controller/AquaComputer/MPS.cs b/LibreHardwareMonitorLib/Hardware/Controller/AquaComputer/MPS.cs index 0d5b0b5..e76f007 100644 --- a/LibreHardwareMonitorLib/Hardware/Controller/AquaComputer/MPS.cs +++ b/LibreHardwareMonitorLib/Hardware/Controller/AquaComputer/MPS.cs @@ -47,7 +47,7 @@ namespace LibreHardwareMonitor.Hardware.Controller.AquaComputer public override HardwareType HardwareType { - get { return HardwareType.AquaComputer; } + get { return HardwareType.Cooler; } } public string Status diff --git a/LibreHardwareMonitorLib/Hardware/Controller/Heatmaster/Heatmaster.cs b/LibreHardwareMonitorLib/Hardware/Controller/Heatmaster/Heatmaster.cs index 15727b5..a575660 100644 --- a/LibreHardwareMonitorLib/Hardware/Controller/Heatmaster/Heatmaster.cs +++ b/LibreHardwareMonitorLib/Hardware/Controller/Heatmaster/Heatmaster.cs @@ -102,7 +102,7 @@ namespace LibreHardwareMonitor.Hardware.Controller.Heatmaster public override HardwareType HardwareType { - get { return HardwareType.Heatmaster; } + get { return HardwareType.Cooler; } } public void Dispose() diff --git a/LibreHardwareMonitorLib/Hardware/Controller/Nzxt/KrakenX3.cs b/LibreHardwareMonitorLib/Hardware/Controller/Nzxt/KrakenX3.cs new file mode 100644 index 0000000..2711ea9 --- /dev/null +++ b/LibreHardwareMonitorLib/Hardware/Controller/Nzxt/KrakenX3.cs @@ -0,0 +1,159 @@ +using HidSharp; +using System; +using System.Linq; +using System.Threading; + +namespace LibreHardwareMonitor.Hardware.Controller.Nzxt +{ + /** + * Support for the Kraken X3 devices from NZXT + */ + internal sealed class KrakenX3 : Hardware + { + // Some fixed messages to send to the pump for basic monitoring and control + private static readonly byte[] _getFirmareInfo = { 0x10, 0x01 }; + private static readonly byte[] _initialize1 = { 0x70, 0x02, 0x01, 0xb8, 0x0b }; + private static readonly byte[] _initialize2 = { 0x70, 0x01 }; + private static readonly byte[][] _setPumpTargetMap = new byte[101][]; // Sacrifice memory to speed this up with a lookup instead of a copy operation + + static KrakenX3() + { + byte[] set_pump_speed_header = { 0x72, 0x01, 0x00, 0x00 }; + + for (byte speed = 0; speed < _setPumpTargetMap.Length; speed++) + { + _setPumpTargetMap[speed] = set_pump_speed_header.Concat(Enumerable.Repeat(speed, 40).Concat(new byte[20])).ToArray(); + } + } + + private volatile bool _controlling = false; + private readonly Sensor _pump; + private readonly Control _pumpControl; + private readonly Sensor _pumpRpm; + private readonly byte[] _rawData = new byte[64]; + private readonly HidStream _stream; + private readonly Sensor _temperature; + + public string FirmwareVersion { get; private set; } + + public KrakenX3(HidDevice dev, ISettings settings) : base("Nzxt Kraken X3", new Identifier("nzxt", "krakenx3", dev.GetSerialNumber().TrimStart('0')), settings) + { + if (dev.TryOpen(out _stream)) + { + _stream.ReadTimeout = 5000; // The NZXT device returns with data that we need periodically without writing... + _stream.Write(_initialize1); + _stream.Write(_initialize2); + + _stream.Write(_getFirmareInfo); + do + { + _stream.Read(_rawData); + if (_rawData[0] == 0x11 && _rawData[1] == 0x01) + { + FirmwareVersion = $"{_rawData[0x11]}.{_rawData[0x11]}.{_rawData[0x13]}"; + } + } while (FirmwareVersion == null); + + Name = $"Nzxt Kraken X3"; + + _pump = new Sensor("Pump Control", 0, SensorType.Control, this, new ParameterDescription[0], settings); + _pumpControl = new Control(_pump, settings, 0, 100); + _pump.Control = _pumpControl; + _pumpControl.ControlModeChanged += SoftwareControlValueChanged; + _pumpControl.SoftwareControlValueChanged += SoftwareControlValueChanged; + SoftwareControlValueChanged(_pumpControl); + ActivateSensor(_pump); + + _pumpRpm = new Sensor("Pump", 0, SensorType.Fan, this, new ParameterDescription[0], settings); + ActivateSensor(_pumpRpm); + + _temperature = new Sensor("Internal Water", 0, SensorType.Temperature, this, new ParameterDescription[0], settings); + ActivateSensor(_temperature); + + ThreadPool.UnsafeQueueUserWorkItem(ContinuousRead, _rawData); + } + } + + private void SoftwareControlValueChanged(Control control) + { + if (control.ControlMode == ControlMode.Software) + { + float value = control.SoftwareValue; + byte pump_speed_index = (byte)((value > 100) ? 100 : (value < 0) ? 0 : value); // Clamp the value, anything out of range will fail + + _controlling = true; + _stream.Write(_setPumpTargetMap[pump_speed_index]); + _pump.Value = value; + } + else if (control.ControlMode == ControlMode.Default) + { // There isn't a "default" mode with this pump, but a safe setting is 40% + _stream.Write(_setPumpTargetMap[40]); + } + } + + public override HardwareType HardwareType => HardwareType.Cooler; + + public string Status => (FirmwareVersion != "2.1.0" ? $"Status: Untested Firmware Version {FirmwareVersion}! Please consider Updating to Version 2.1.0" : "Status: OK"); + + public override void Close() + { + base.Close(); + _stream.Close(); + } + + private void ContinuousRead(object state) + { + byte[] buffer = new byte[_rawData.Length]; + while (_stream.CanRead) + { + try + { + _stream.Read(buffer); // This is a blocking call, will wait for bytes to become available + + lock (_rawData) + { + Array.Copy(buffer, _rawData, buffer.Length); + } + } + catch (TimeoutException ex) + { + // Don't care, just make sure the stream is still open + Thread.Sleep(500); + } + catch (ObjectDisposedException ex) + { + // Could be unplugged, or the app is stopping... + } + } + } + + public override void Update() + { + // The NZXT Kraken X3 series sends updates periodically. We have to read it in a seperate thread, this call just reads that data. + lock (_rawData) + { + + if (_rawData[0] == 0x75 && _rawData[1] == 0x02) + { + _temperature.Value = _rawData[15] + _rawData[16] / 10.0f; + _pumpRpm.Value = (_rawData[18] << 8) | _rawData[17]; + + // The following logic makes sure the pump is set to the controlling value. This pump sometimes sets itself to 0% when instructed to a value. + if (!_controlling) + { + _pump.Value = _rawData[19]; + } + else if (_pump.Value != _rawData[19]) + { + byte pump_speed_index = (byte)_pump.Value; + _stream.Write(_setPumpTargetMap[pump_speed_index]); + } + else + { + _controlling = false; + } + } + } + } + } +} diff --git a/LibreHardwareMonitorLib/Hardware/Controller/Nzxt/NzxtGroup.cs b/LibreHardwareMonitorLib/Hardware/Controller/Nzxt/NzxtGroup.cs new file mode 100644 index 0000000..5c9eec5 --- /dev/null +++ b/LibreHardwareMonitorLib/Hardware/Controller/Nzxt/NzxtGroup.cs @@ -0,0 +1,70 @@ +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// Copyright (C) LibreHardwareMonitor and Contributors. +// All Rights Reserved. + +using System.Collections.Generic; +using System.Text; +using HidSharp; + +namespace LibreHardwareMonitor.Hardware.Controller.Nzxt +{ + internal class NzxtGroup : IGroup + { + private readonly List _hardware = new List(); + private readonly StringBuilder _report = new StringBuilder(); + + public NzxtGroup(ISettings settings) + { + _report.AppendLine("Nzxt Hardware"); + _report.AppendLine(); + + foreach (HidDevice dev in DeviceList.Local.GetHidDevices(0x1e71)) + { + string productName = dev.GetProductName(); + + switch (dev.ProductID) + { + case 0x2007: + { + var device = new KrakenX3(dev, settings); + _report.AppendLine($"Device name: {productName}"); + _report.AppendLine($"Firmware version: {device.FirmwareVersion}"); + _report.AppendLine($"{device.Status}"); + _report.AppendLine(); + _hardware.Add(device); + break; + } + default: + { + _report.AppendLine($"Unknown Hardware PID: {dev.ProductID} Name: {productName}"); + _report.AppendLine(); + break; + } + } + } + + if (_hardware.Count == 0) + { + _report.AppendLine("No Nzxt Hardware found."); + _report.AppendLine(); + } + } + + public IEnumerable Hardware => _hardware; + + public void Close() + { + foreach (IHardware iHardware in _hardware) + { + if (iHardware is Hardware hardware) + hardware.Close(); + } + } + + public string GetReport() + { + return _report.ToString(); + } + } +} diff --git a/LibreHardwareMonitorLib/Hardware/Controller/TBalancer/TBalancer.cs b/LibreHardwareMonitorLib/Hardware/Controller/TBalancer/TBalancer.cs index aff562d..c3d306f 100644 --- a/LibreHardwareMonitorLib/Hardware/Controller/TBalancer/TBalancer.cs +++ b/LibreHardwareMonitorLib/Hardware/Controller/TBalancer/TBalancer.cs @@ -90,7 +90,7 @@ namespace LibreHardwareMonitor.Hardware.Controller.TBalancer public override HardwareType HardwareType { - get { return HardwareType.TBalancer; } + get { return HardwareType.Cooler; } } protected override void ActivateSensor(ISensor sensor) diff --git a/LibreHardwareMonitorLib/Hardware/IHardware.cs b/LibreHardwareMonitorLib/Hardware/IHardware.cs index 2ab1325..49d1af5 100644 --- a/LibreHardwareMonitorLib/Hardware/IHardware.cs +++ b/LibreHardwareMonitorLib/Hardware/IHardware.cs @@ -12,16 +12,13 @@ namespace LibreHardwareMonitor.Hardware { Motherboard, SuperIO, - AquaComputer, Cpu, Memory, GpuNvidia, GpuAmd, - TBalancer, - Heatmaster, Storage, Network, - AeroCool + Cooler } public interface IHardware : IElement diff --git a/LibreHardwareMonitorLib/LibreHardwareMonitorLib.csproj b/LibreHardwareMonitorLib/LibreHardwareMonitorLib.csproj index 877f8a7..ea8f96f 100644 --- a/LibreHardwareMonitorLib/LibreHardwareMonitorLib.csproj +++ b/LibreHardwareMonitorLib/LibreHardwareMonitorLib.csproj @@ -43,6 +43,8 @@ + +