Added support for NZXT Kraken X3 devices. (#248)

Thanks to @NeoAcheron
This commit is contained in:
NeoAcheron
2020-06-02 13:14:55 +02:00
committed by GitHub
parent 5976a5a7c6
commit 1ebc639e74
15 changed files with 246 additions and 33 deletions
@@ -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;
}
}
}
}
}
}
@@ -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<IHardware> _hardware = new List<IHardware>();
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<IHardware> Hardware => _hardware;
public void Close()
{
foreach (IHardware iHardware in _hardware)
{
if (iHardware is Hardware hardware)
hardware.Close();
}
}
public string GetReport()
{
return _report.ToString();
}
}
}