first commit

This commit is contained in:
2025-04-07 07:44:27 -07:00
commit d6cde0c05e
512 changed files with 142392 additions and 0 deletions

View File

@@ -0,0 +1,186 @@
// 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;
using System.Collections.Generic;
using System.Threading;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.Nzxt;
/// <summary>
/// Support for the NZXT GRID+ V3 devices.
/// </summary>
internal sealed class GridV3 : Hardware
{
private const int FANS_COUNT = 6;
// Some initialization messages to send to the controller. No visible effects but NZXT CAM send them.
private static readonly byte[] _initialize1 = { 0x01, 0x5c };
private static readonly byte[] _initialize2 = { 0x01, 0x5d };
private static readonly byte[] _initialize3 = { 0x01, 0x59 };
private readonly Sensor[] _currents = new Sensor[FANS_COUNT];
private readonly Control[] _fanControls = new Control[FANS_COUNT];
private readonly Sensor _noise;
private readonly Sensor[] _powers = new Sensor[FANS_COUNT];
private readonly Sensor[] _pwmControls = new Sensor[FANS_COUNT];
private readonly Dictionary<int, byte[]> _rawData = new();
private readonly Sensor[] _rpmSensors = new Sensor[FANS_COUNT];
private readonly byte[] _setFanSpeedMsg;
private readonly HidStream _stream;
private readonly Sensor[] _voltages = new Sensor[FANS_COUNT];
public GridV3(HidDevice dev, ISettings settings) : base("NZXT GRID+ V3", new Identifier(dev), settings)
{
if (dev.TryOpen(out _stream))
{
for (int fanId = 0; fanId < FANS_COUNT; fanId++)
_rawData[fanId] = new byte[21];
_setFanSpeedMsg = new byte[65];
_setFanSpeedMsg[0] = 0x02;
_setFanSpeedMsg[1] = 0x4d;
_setFanSpeedMsg[3] = 0x00;
_stream.Write(_initialize1);
_stream.Write(_initialize2);
_stream.Write(_initialize3);
do
{
_stream.Read(_rawData[0]);
if (_rawData[0][0] == 0x04)
{
FirmwareVersion = $"{_rawData[0][11]}.{_rawData[0][14]}";
}
}
while (FirmwareVersion == null);
Name = "NZXT GRID+ V3";
// Initialize all sensors and controls for all fans
for (int i = 0; i < FANS_COUNT; i++)
{
_rpmSensors[i] = new Sensor($"GRID Fan #{i + 1}", i, SensorType.Fan, this, Array.Empty<ParameterDescription>(), settings);
_voltages[i] = new Sensor($"GRID Fan #{i + 1}", i, SensorType.Voltage, this, Array.Empty<ParameterDescription>(), settings);
_currents[i] = new Sensor($"GRID Fan #{i + 1}", i, SensorType.Current, this, Array.Empty<ParameterDescription>(), settings);
_powers[i] = new Sensor($"GRID Fan #{i + 1}", i, SensorType.Power, this, Array.Empty<ParameterDescription>(), settings);
_pwmControls[i] = new Sensor($"GRID Fan #{i + 1}", i, SensorType.Control, this, Array.Empty<ParameterDescription>(), settings);
_fanControls[i] = new Control(_pwmControls[i], settings, 0, 100);
_pwmControls[i].Control = _fanControls[i];
_fanControls[i].ControlModeChanged += SoftwareControlValueChanged;
_fanControls[i].SoftwareControlValueChanged += SoftwareControlValueChanged;
SoftwareControlValueChanged(_fanControls[i]);
ActivateSensor(_rpmSensors[i]);
ActivateSensor(_voltages[i]);
ActivateSensor(_currents[i]);
ActivateSensor(_powers[i]);
ActivateSensor(_pwmControls[i]);
// NZXT GRID does not report current PWM value. So we need to initialize it with some value to keep GUI and device values in sync.
_fanControls[i].SetDefault();
}
_noise = new Sensor("GRID Noise", 0, SensorType.Noise, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_noise);
Thread readGridReports = new(ContinuousRead) { IsBackground = true };
readGridReports.Start(_rawData);
IsValid = true;
}
}
public string FirmwareVersion { get; }
public override HardwareType HardwareType => HardwareType.Cooler;
public bool IsValid { get; }
private void SoftwareControlValueChanged(Control control)
{
switch (control.ControlMode)
{
case ControlMode.Software:
float value = control.SoftwareValue;
byte fanSpeed = (byte)(value > 100 ? 100 : value < 0 ? 0 : value); // Clamp the value, anything out of range will fail
//_controlling = true;
_setFanSpeedMsg[2] = (byte)control.Sensor.Index;
_setFanSpeedMsg[4] = fanSpeed;
_stream.Write(_setFanSpeedMsg);
_pwmControls[control.Sensor.Index].Value = value;
break;
case ControlMode.Default:
// There isn't a "default" mode, but let's say a safe setting is 40%
_setFanSpeedMsg[2] = (byte)control.Sensor.Index;
_setFanSpeedMsg[4] = 40;
_stream.Write(_setFanSpeedMsg);
_pwmControls[control.Sensor.Index].Value = 40;
break;
}
}
public override void Close()
{
_stream?.Close();
base.Close();
}
private void ContinuousRead(object state)
{
byte[] buffer = new byte[_rawData[0].Length];
while (_stream.CanRead)
{
try
{
_stream.Read(buffer); // This is a blocking call, will wait for bytes to become available
if (buffer[0] == 0x04)
{
lock (_rawData)
{
int fanId = (buffer[15] >> 4) & 0x0f;
Array.Copy(buffer, _rawData[fanId], buffer.Length);
}
}
}
catch (TimeoutException)
{
// Don't care, just make sure the stream is still open
Thread.Sleep(500);
}
catch (ObjectDisposedException)
{
// Could be unplugged, or the app is stopping...
return;
}
}
}
public override void Update()
{
// The NZXT GRID+ V3 series sends updates periodically. We have to read it in a separate thread, this call just reads that data.
lock (_rawData)
{
for (int fanId = 0; fanId < FANS_COUNT; fanId++)
{
_rpmSensors[fanId].Value = (_rawData[fanId][3] << 8) | _rawData[fanId][4];
_voltages[fanId].Value = _rawData[fanId][7] + _rawData[fanId][8] / 100.0f;
_currents[fanId].Value = _rawData[fanId][9] + _rawData[fanId][10] / 100.0f;
_powers[fanId].Value = _currents[fanId].Value * _voltages[fanId].Value;
}
_noise.Value = _rawData[2][1];
}
}
}

View File

@@ -0,0 +1,186 @@
using System;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.Nzxt;
/// <summary>
/// Support for the Kraken X (X42, X52, X62 or X72) devices.
/// </summary>
internal sealed class KrakenV2 : Hardware
{
private static readonly byte[] _getFirmwareInfo = [0x10, 0x01];
private readonly HidDevice _device;
private readonly Sensor _fan;
private readonly byte _fanChannel;
private readonly bool _fanControl;
private readonly Sensor _fanRpm;
private readonly TimeSpan _interval = TimeSpan.FromMilliseconds(5000);
private readonly Sensor _liquidTemperature;
private readonly Sensor _pump;
private readonly byte _pumpChannel;
private readonly Sensor _pumpRpm;
private readonly byte[] _rawData = new byte[64];
private readonly string _supportedFirmware;
private DateTime _lastUpdate = DateTime.MinValue;
public KrakenV2(HidDevice dev, ISettings settings) : base("Nzxt Kraken X", new Identifier(dev), settings)
{
_device = dev;
switch (dev.ProductID)
{
case 0x170e:
default:
Name = "NZXT Kraken X";
_fanControl = true;
_fanChannel = 0x00;
_pumpChannel = 0x40;
_supportedFirmware = "6.2.0";
break;
}
try
{
using HidStream stream = dev.Open();
stream.Write(_getFirmwareInfo);
int tries = 0;
while (FirmwareVersion == null && tries++ < 10)
{
stream.Read(_rawData);
if (_rawData[0] == 0x11 && _rawData[1] == 0x01)
FirmwareVersion = $"{_rawData[0x11]}.{_rawData[0x12]}.{_rawData[0x13]}";
}
if (FirmwareVersion == null)
return;
// Liquid temperature
_liquidTemperature = new Sensor("Liquid", 0, SensorType.Temperature, this, [], settings);
ActivateSensor(_liquidTemperature);
// Pump Control
_pump = new Sensor("Pump Control", 0, SensorType.Control, this, [], settings);
Control pumpControl = new(_pump, settings, 60, 100);
_pump.Control = pumpControl;
pumpControl.ControlModeChanged += c => ControlValueChanged(_pump, c);
pumpControl.SoftwareControlValueChanged += c => ControlValueChanged(_pump, c);
ControlValueChanged(_pump, pumpControl);
ActivateSensor(_pump);
// Pump RPM
_pumpRpm = new Sensor("Pump", 0, SensorType.Fan, this, [], settings);
ActivateSensor(_pumpRpm);
if (_fanControl)
{
// Fan Control
_fan = new Sensor("Fans Control", 1, SensorType.Control, this, [], settings);
Control fanControl = new(_fan, settings, 0, 100);
_fan.Control = fanControl;
fanControl.ControlModeChanged += c => ControlValueChanged(_fan, c);
fanControl.SoftwareControlValueChanged += c => ControlValueChanged(_fan, c);
ControlValueChanged(_fan, fanControl);
ActivateSensor(_fan);
// Fan RPM
_fanRpm = new Sensor("Fans", 1, SensorType.Fan, this, [], settings);
ActivateSensor(_fanRpm);
}
IsValid = true;
}
catch
{ }
}
public string FirmwareVersion { get; }
public override HardwareType HardwareType => HardwareType.Cooler;
public bool IsValid { get; }
public string Status => FirmwareVersion != _supportedFirmware ? $"Status: Untested firmware version {FirmwareVersion}! Please consider updating to version {_supportedFirmware}" : "Status: OK";
private void ControlValueChanged(Sensor sensor, IControl control)
{
try
{
if (control.ControlMode == ControlMode.Software)
{
//value will be updated at next Update()
sensor.Value = control.SoftwareValue;
_lastUpdate = DateTime.MinValue;
Update();
}
else
{
//will let the device handle the value
sensor.Value = null;
}
}
catch (ObjectDisposedException)
{
// Could be unplugged, or the app is stopping...
}
}
public override void Update()
{
try
{
using HidStream stream = _device.Open();
stream.Read(_rawData);
// if not 0x04, it is not temperature data
if (_rawData[0] != 0x04)
return;
// some packet may have 0 as temperature, don't know why just ignore it
if (_rawData[1] == 0x00)
return;
_liquidTemperature.Value = _rawData[1] + (_rawData[2] / 10.0f);
_fanRpm.Value = (_rawData[3] << 8) | _rawData[4];
_pumpRpm.Value = (_rawData[5] << 8) | _rawData[6];
// if we don't have control over the fan or pump, we don't need to update
if (!_pump.Value.HasValue && (!_fanControl || !_fan.Value.HasValue))
return;
//control value need to be updated every 5 seconds or it falls back to default
if (DateTime.Now - _lastUpdate < _interval)
return;
if (_fanControl && _fan.Value.HasValue)
SetDuty(stream, _fanChannel, (byte)_fan.Value);
if (_fanControl && _pump.Value.HasValue)
SetDuty(stream, _pumpChannel, (byte)_pump.Value);
}
catch (ObjectDisposedException)
{
// Could be unplugged, or the app is stopping...
}
}
private void SetDuty(HidStream stream, byte channel, byte duty)
{
SetDuty(stream, channel, 0, duty);
}
private void SetDuty(HidStream stream, byte channel, byte temperature, byte duty)
{
stream.Write([0x2, 0x4d, channel, temperature, duty]);
_lastUpdate = DateTime.Now;
}
}

View File

@@ -0,0 +1,250 @@
using System;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.Nzxt;
/// <summary>
/// Support for the KrakenZ devices.
/// </summary>
internal sealed class KrakenV3 : Hardware
{
private static readonly byte[] _getFirmwareInfo = [0x10, 0x01];
private static readonly byte[] _setFanTarget = new byte[64];
private static readonly byte[] _setPumpTarget = new byte[64];
private static readonly byte[] _statusRequest = [0x74, 0x01];
private readonly Sensor _fan;
private readonly bool _fanControl;
private readonly Sensor _fanRpm;
private readonly Sensor _pump;
private readonly Sensor _pumpRpm;
private readonly byte[] _rawData = new byte[64];
private readonly HidStream _stream;
private readonly string _supportedFirmware;
private readonly Sensor _temperature;
private volatile bool _controllingFans;
private volatile bool _controllingPump;
public KrakenV3(HidDevice dev, ISettings settings) : base("Nzxt Kraken Z", new Identifier(dev), settings)
{
switch (dev.ProductID)
{
case 0x3008:
Name = "NZXT Kraken Z3";
_fanControl = true;
_supportedFirmware = "5.7.0";
Array.Copy(new byte[] { 0x72, 0x01, 0x00, 0x00 }, 0, _setPumpTarget, 0, 4);
Array.Copy(new byte[] { 0x72, 0x02, 0x00, 0x00 }, 0, _setFanTarget, 0, 4);
break;
case 0x300C:
Name = "NZXT Kraken Elite";
_fanControl = true;
_supportedFirmware = "1.2.4";
Array.Copy(new byte[] { 0x72, 0x01, 0x01, 0x00 }, 0, _setPumpTarget, 0, 4);
Array.Copy(new byte[] { 0x72, 0x02, 0x01, 0x01 }, 0, _setFanTarget, 0, 4);
break;
case 0x300E:
Name = "NZXT Kraken";
_fanControl = true;
_supportedFirmware = "1.2.4"; // Firmware version to be confirmed
Array.Copy(new byte[] { 0x72, 0x01, 0x01, 0x00 }, 0, _setPumpTarget, 0, 4);
Array.Copy(new byte[] { 0x72, 0x02, 0x01, 0x01 }, 0, _setFanTarget, 0, 4);
break;
default:
Name = "NZXT Kraken X3";
_fanControl = false;
_supportedFirmware = "2.1.0";
Array.Copy(new byte[] { 0x72, 0x01, 0x00, 0x00 }, 0, _setPumpTarget, 0, 4);
break;
}
FillTargetArray(_setPumpTarget, 60);
FillTargetArray(_setFanTarget, 40);
if (dev.TryOpen(out _stream))
{
_stream.ReadTimeout = 5000;
_stream.Write(_getFirmwareInfo);
int tries = 0;
while (FirmwareVersion == null && tries++ < 10)
{
_stream.Read(_rawData);
if (_rawData[0] == 0x11 && _rawData[1] == 0x01)
FirmwareVersion = $"{_rawData[0x11]}.{_rawData[0x12]}.{_rawData[0x13]}";
}
if (FirmwareVersion == null)
return;
// Liquid temperature
_temperature = new Sensor("Liquid", 0, SensorType.Temperature, this, [], settings);
ActivateSensor(_temperature);
// Pump Control
_pump = new Sensor("Pump Control", 0, SensorType.Control, this, [], settings);
Control pumpControl = new(_pump, settings, 20, 100);
_pump.Control = pumpControl;
pumpControl.ControlModeChanged += PumpSoftwareControlValueChanged;
pumpControl.SoftwareControlValueChanged += PumpSoftwareControlValueChanged;
PumpSoftwareControlValueChanged(pumpControl);
ActivateSensor(_pump);
// Pump RPM
_pumpRpm = new Sensor("Pump", 0, SensorType.Fan, this, [], settings);
ActivateSensor(_pumpRpm);
if (_fanControl)
{
// Fan Control
_fan = new Sensor("Fans Control", 1, SensorType.Control, this, [], settings);
Control fanControl = new(_fan, settings, 20, 100);
_fan.Control = fanControl;
fanControl.ControlModeChanged += FanSoftwareControlValueChanged;
fanControl.SoftwareControlValueChanged += FanSoftwareControlValueChanged;
FanSoftwareControlValueChanged(fanControl);
ActivateSensor(_fan);
// Fan RPM
_fanRpm = new Sensor("Fans", 1, SensorType.Fan, this, [], settings);
ActivateSensor(_fanRpm);
}
IsValid = true;
}
}
public string FirmwareVersion { get; }
public override HardwareType HardwareType => HardwareType.Cooler;
public bool IsValid { get; }
public string Status => FirmwareVersion != _supportedFirmware ? $"Status: Untested firmware version {FirmwareVersion}! Please consider updating to version {_supportedFirmware}" : "Status: OK";
private static void FillTargetArray(byte[] targetArray, byte value)
{
for (byte i = 4; i < targetArray.Length; i++)
targetArray[i] = value;
}
private void PumpSoftwareControlValueChanged(Control control)
{
try
{
switch (control.ControlMode)
{
case ControlMode.Software:
float value = control.SoftwareValue;
FillTargetArray(_setPumpTarget, (byte)(value > 100 ? 100 : value < 0 ? 0 : value));
_controllingPump = true;
_stream.Write(_setPumpTarget);
_pump.Value = value;
break;
case ControlMode.Default:
// There isn't a "default" mode with this pump, but a safe setting is 60%
FillTargetArray(_setPumpTarget, 60);
_stream.Write(_setPumpTarget);
break;
}
}
catch (ObjectDisposedException)
{
// Could be unplugged, or the app is stopping...
}
}
private void FanSoftwareControlValueChanged(Control control)
{
try
{
switch (control.ControlMode)
{
case ControlMode.Software:
float value = control.SoftwareValue;
FillTargetArray(_setFanTarget, (byte)(value > 100 ? 100 : value < 0 ? 0 : value));
_controllingFans = true;
_stream.Write(_setFanTarget);
_fan.Value = value;
break;
case ControlMode.Default:
// There isn't a "default" mode with this fan, but a safe setting is 40%
FillTargetArray(_setFanTarget, 40);
_stream.Write(_setFanTarget);
break;
}
}
catch (ObjectDisposedException)
{
// Could be unplugged, or the app is stopping...
}
}
public override void Close()
{
base.Close();
_stream?.Close();
}
public override void Update()
{
try
{
_stream.Write(_statusRequest);
do
{
_stream.Read(_rawData);
}
while (_rawData[0] != 0x75 || _rawData[1] != 0x1);
_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 (!_controllingPump)
{
_pump.Value = _rawData[19];
}
else if (_pump.Value != _rawData[19])
{
float value = _pump.Value.GetValueOrDefault();
FillTargetArray(_setPumpTarget, (byte)value);
_stream.Write(_setPumpTarget);
}
else
{
_controllingPump = false;
}
if (_fanControl)
{
_fanRpm.Value = (_rawData[24] << 8) | _rawData[23];
if (!_controllingFans)
{
_fan.Value = _rawData[25];
}
else if (_fan.Value != _rawData[25])
{
float value = _fan.Value.GetValueOrDefault();
FillTargetArray(_setFanTarget, (byte)value);
_stream.Write(_setFanTarget);
}
else
{
_controllingFans = false;
}
}
}
catch (ObjectDisposedException)
{
// Could be unplugged, or the app is stopping...
}
}
}

View File

@@ -0,0 +1,94 @@
// 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();
private readonly StringBuilder _report = new();
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 0x170E: // NZXT Kraken X (X42, X52, X62 or X72)
var krakenV2 = new KrakenV2(dev, settings);
_report.AppendLine($"Device name: {productName}");
_report.AppendLine($"Firmware version: {krakenV2.FirmwareVersion}");
_report.AppendLine($"{krakenV2.Status}");
_report.AppendLine();
if (krakenV2.IsValid)
_hardware.Add(krakenV2);
break;
case 0x2007: // Kraken X3 original pid
case 0x2014: // Kraken X3 new pid
case 0x3008: // Kraken Z3
case 0x300C: // Kraken 2023 elite
case 0x300E: // Kraken 2023 standard
// NZXT KrakenV3 Devices
var krakenV3 = new KrakenV3(dev, settings);
_report.AppendLine($"Device name: {productName}");
_report.AppendLine($"Firmware version: {krakenV3.FirmwareVersion}");
_report.AppendLine($"{krakenV3.Status}");
_report.AppendLine();
if (krakenV3.IsValid)
_hardware.Add(krakenV3);
break;
case 0x1711:
var gridv3 = new GridV3(dev, settings);
_report.AppendLine($"Device name: {productName}");
_report.AppendLine($"Firmware version: {gridv3.FirmwareVersion}");
_report.AppendLine();
if (gridv3.IsValid)
_hardware.Add(gridv3);
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 IReadOnlyList<IHardware> Hardware => _hardware;
public void Close()
{
foreach (IHardware iHardware in _hardware)
{
if (iHardware is Hardware hardware)
hardware.Close();
}
}
public string GetReport()
{
return _report.ToString();
}
}