first commit
This commit is contained in:
@@ -0,0 +1,408 @@
|
||||
// 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.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using HidSharp;
|
||||
|
||||
namespace LibreHardwareMonitor.Hardware.Controller.Razer;
|
||||
|
||||
internal sealed class RazerFanController : Hardware
|
||||
{
|
||||
private const int DEFAULT_SPEED_CHANNEL_POWER = 50;
|
||||
private const byte PERCENT_MIN = 0;
|
||||
private const byte PERCENT_MAX = 100;
|
||||
private const int DEVICE_READ_DELAY_MS = 5;
|
||||
private const int DEVICE_READ_TIMEOUT_MS = 500;
|
||||
private const int CHANNEL_COUNT = 8;
|
||||
//private const int FORCE_WRITE_SPEEDS_INTERVAL_MS = 2500; // TODO: Add timer
|
||||
|
||||
private HidStream _stream;
|
||||
private readonly HidDevice _device;
|
||||
private readonly SequenceCounter _sequenceCounter = new();
|
||||
|
||||
private readonly float?[] _pwm = new float?[CHANNEL_COUNT];
|
||||
private readonly Sensor[] _pwmControls = new Sensor[CHANNEL_COUNT];
|
||||
private readonly Sensor[] _rpmSensors = new Sensor[CHANNEL_COUNT];
|
||||
|
||||
public RazerFanController(HidDevice dev, ISettings settings) : base("Razer PWM PC Fan Controller", new Identifier(dev), settings)
|
||||
{
|
||||
_device = dev;
|
||||
|
||||
if (_device.TryOpen(out _stream))
|
||||
{
|
||||
_stream.ReadTimeout = 5000;
|
||||
|
||||
Packet packet = new Packet
|
||||
{
|
||||
SequenceNumber = _sequenceCounter.Next(),
|
||||
DataLength = 0,
|
||||
CommandClass = CommandClass.Info,
|
||||
Command = 0x87,
|
||||
};
|
||||
|
||||
if (Mutexes.WaitRazer(250))
|
||||
{
|
||||
while (FirmwareVersion == null)
|
||||
{
|
||||
Thread.Sleep(DEVICE_READ_DELAY_MS);
|
||||
|
||||
try
|
||||
{
|
||||
Packet response = TryWriteAndRead(packet);
|
||||
FirmwareVersion = $"{response.Data[0]:D}.{response.Data[1]:D2}.{response.Data[2]:D2}";
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
Mutexes.ReleaseRazer();
|
||||
}
|
||||
|
||||
Name = "Razer PWM PC Fan Controller";
|
||||
|
||||
for (int i = 0; i < CHANNEL_COUNT; i++)
|
||||
{
|
||||
// Fan Control
|
||||
_pwmControls[i] = new Sensor("Fan Control #" + (i + 1), i, SensorType.Control, this, Array.Empty<ParameterDescription>(), settings);
|
||||
Control fanControl = new(_pwmControls[i], settings, PERCENT_MIN, PERCENT_MAX);
|
||||
_pwmControls[i].Control = fanControl;
|
||||
fanControl.ControlModeChanged += FanSoftwareControlValueChanged;
|
||||
fanControl.SoftwareControlValueChanged += FanSoftwareControlValueChanged;
|
||||
//fanControl.SetDefault();
|
||||
FanSoftwareControlValueChanged(fanControl);
|
||||
ActivateSensor(_pwmControls[i]);
|
||||
|
||||
// Fan RPM
|
||||
_rpmSensors[i] = new Sensor("Fan #" + (i + 1), i, SensorType.Fan, this, Array.Empty<ParameterDescription>(), settings);
|
||||
ActivateSensor(_rpmSensors[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string FirmwareVersion { get; }
|
||||
|
||||
public override HardwareType HardwareType => HardwareType.Cooler;
|
||||
|
||||
public string Status => FirmwareVersion != "1.01.00" ? $"Status: Untested Firmware Version {FirmwareVersion}! Please consider Updating to Version 1.01.00" : "Status: OK";
|
||||
|
||||
private void FanSoftwareControlValueChanged(Control control) // TODO: Add timer here
|
||||
{
|
||||
if (control.ControlMode == ControlMode.Undefined || !Mutexes.WaitRazer(250))
|
||||
return;
|
||||
|
||||
if (control.ControlMode == ControlMode.Software)
|
||||
{
|
||||
SetChannelModeToManual(control.Sensor.Index);
|
||||
|
||||
float value = control.SoftwareValue;
|
||||
byte fanSpeed = (byte)(value > 100 ? 100 : value < 0 ? 0 : value);
|
||||
|
||||
var packet = new Packet
|
||||
{
|
||||
SequenceNumber = _sequenceCounter.Next(),
|
||||
DataLength = 3,
|
||||
CommandClass = CommandClass.Pwm,
|
||||
Command = PwmCommand.SetChannelPercent,
|
||||
};
|
||||
|
||||
packet.Data[0] = 0x01;
|
||||
packet.Data[1] = (byte)(0x05 + control.Sensor.Index);
|
||||
packet.Data[2] = fanSpeed;
|
||||
|
||||
TryWriteAndRead(packet);
|
||||
|
||||
_pwm[control.Sensor.Index] = value;
|
||||
}
|
||||
else if (control.ControlMode == ControlMode.Default)
|
||||
{
|
||||
SetChannelModeToManual(control.Sensor.Index); // TODO: switch to auto mode here if it enabled before
|
||||
|
||||
var packet = new Packet
|
||||
{
|
||||
SequenceNumber = _sequenceCounter.Next(),
|
||||
DataLength = 3,
|
||||
CommandClass = CommandClass.Pwm,
|
||||
Command = PwmCommand.SetChannelPercent,
|
||||
};
|
||||
|
||||
packet.Data[0] = 0x01;
|
||||
packet.Data[1] = (byte)(0x05 + control.Sensor.Index);
|
||||
packet.Data[2] = DEFAULT_SPEED_CHANNEL_POWER;
|
||||
|
||||
TryWriteAndRead(packet);
|
||||
|
||||
_pwm[control.Sensor.Index] = DEFAULT_SPEED_CHANNEL_POWER;
|
||||
}
|
||||
|
||||
Mutexes.ReleaseRazer();
|
||||
}
|
||||
|
||||
private int GetChannelSpeed(int channel)
|
||||
{
|
||||
Packet packet = new Packet
|
||||
{
|
||||
SequenceNumber = _sequenceCounter.Next(),
|
||||
DataLength = 6,
|
||||
CommandClass = CommandClass.Pwm,
|
||||
Command = PwmCommand.GetChannelSpeed,
|
||||
};
|
||||
|
||||
packet.Data[0] = 0x01;
|
||||
packet.Data[1] = (byte)(0x05 + channel);
|
||||
|
||||
Packet response = TryWriteAndRead(packet);
|
||||
return (response.Data[4] << 8) | response.Data[5];
|
||||
}
|
||||
|
||||
private void SetChannelModeToManual(int channel)
|
||||
{
|
||||
Packet packet = new Packet
|
||||
{
|
||||
SequenceNumber = _sequenceCounter.Next(),
|
||||
DataLength = 3,
|
||||
CommandClass = CommandClass.Pwm,
|
||||
Command = PwmCommand.SetChannelMode,
|
||||
};
|
||||
|
||||
packet.Data[0] = 0x01;
|
||||
packet.Data[1] = (byte)(0x05 + channel);
|
||||
packet.Data[2] = 0x04;
|
||||
|
||||
TryWriteAndRead(packet);
|
||||
}
|
||||
|
||||
private void ThrowIfNotReady()
|
||||
{
|
||||
bool @throw;
|
||||
try
|
||||
{
|
||||
@throw = _stream is null;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
@throw = true;
|
||||
}
|
||||
|
||||
if (@throw)
|
||||
{
|
||||
throw new InvalidOperationException("The device is not ready.");
|
||||
}
|
||||
}
|
||||
|
||||
private Packet TryWriteAndRead(Packet packet)
|
||||
{
|
||||
Packet readPacket = null;
|
||||
int devTimeout = 400;
|
||||
int devReconnectTimeout = 3000;
|
||||
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] response = Packet.CreateBuffer();
|
||||
byte[] buffer = packet.ToBuffer();
|
||||
|
||||
ThrowIfNotReady();
|
||||
_stream?.SetFeature(buffer, 0, buffer.Length);
|
||||
Thread.Sleep(DEVICE_READ_DELAY_MS);
|
||||
ThrowIfNotReady();
|
||||
_stream?.GetFeature(response, 0, response.Length);
|
||||
readPacket = Packet.FromBuffer(response);
|
||||
|
||||
if (readPacket.Status == DeviceStatus.Busy)
|
||||
{
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
while (stopwatch.ElapsedMilliseconds < DEVICE_READ_TIMEOUT_MS && readPacket.Status == DeviceStatus.Busy)
|
||||
{
|
||||
Thread.Sleep(DEVICE_READ_DELAY_MS);
|
||||
ThrowIfNotReady();
|
||||
_stream?.GetFeature(response, 0, response.Length);
|
||||
readPacket = Packet.FromBuffer(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException) // Unexpected device disconnect or fan plug/unplug
|
||||
{
|
||||
if (devTimeout <= 0)
|
||||
{
|
||||
while (devReconnectTimeout > 0)
|
||||
{
|
||||
_stream?.Close();
|
||||
if (_device.TryOpen(out _stream))
|
||||
break;
|
||||
|
||||
Thread.Sleep(1000);
|
||||
devReconnectTimeout -= 500;
|
||||
}
|
||||
|
||||
if (devReconnectTimeout <= 0) // Device disconnected
|
||||
{
|
||||
for (int i = 0; i < CHANNEL_COUNT; i++)
|
||||
{
|
||||
_pwmControls[i].Control = null;
|
||||
_pwmControls[i].Value = null;
|
||||
_rpmSensors[i].Value = null;
|
||||
_pwm[i] = null;
|
||||
|
||||
DeactivateSensor(_pwmControls[i]);
|
||||
DeactivateSensor(_rpmSensors[i]);
|
||||
}
|
||||
|
||||
Close();
|
||||
|
||||
Packet ret = new Packet();
|
||||
for (int i = 0; i < 80; i++)
|
||||
ret.Data[i] = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
devTimeout = 400;
|
||||
}
|
||||
|
||||
Thread.Sleep(DEVICE_READ_DELAY_MS);
|
||||
devTimeout -= DEVICE_READ_DELAY_MS;
|
||||
}
|
||||
} while (readPacket == null);
|
||||
|
||||
return readPacket;
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
base.Close();
|
||||
_stream?.Close();
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (!Mutexes.WaitRazer(250))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < CHANNEL_COUNT; i++)
|
||||
{
|
||||
_rpmSensors[i].Value = GetChannelSpeed(i);
|
||||
_pwmControls[i].Value = _pwm[i];
|
||||
}
|
||||
|
||||
Mutexes.ReleaseRazer();
|
||||
}
|
||||
|
||||
private enum DeviceStatus : byte
|
||||
{
|
||||
Default = 0x00,
|
||||
Busy = 0x01,
|
||||
Success = 0x02,
|
||||
Error = 0x03,
|
||||
Timeout = 0x04,
|
||||
Invalid = 0x05,
|
||||
}
|
||||
|
||||
private enum ProtocolType : byte
|
||||
{
|
||||
Default = 0x00,
|
||||
}
|
||||
|
||||
private static class CommandClass
|
||||
{
|
||||
public static readonly byte Info = 0x00;
|
||||
public static readonly byte Pwm = 0x0d;
|
||||
}
|
||||
|
||||
private static class PwmCommand
|
||||
{
|
||||
public static readonly byte SetChannelPercent = 0x0d;
|
||||
public static readonly byte SetChannelMode = 0x02;
|
||||
public static readonly byte GetChannelSpeed = 0x81;
|
||||
}
|
||||
|
||||
private sealed class Packet
|
||||
{
|
||||
public byte ReportId { get; set; }
|
||||
public DeviceStatus Status { get; set; }
|
||||
public byte SequenceNumber { get; set; }
|
||||
public short RemainingCount { get; set; }
|
||||
public ProtocolType ProtocolType { get; set; }
|
||||
public byte DataLength { get; set; }
|
||||
public byte CommandClass { get; set; }
|
||||
public byte Command { get; set; }
|
||||
public byte[] Data { get; } = new byte[80];
|
||||
public byte CRC { get; set; }
|
||||
public byte Reserved { get; set; }
|
||||
|
||||
public byte[] ToBuffer()
|
||||
{
|
||||
byte[] buffer = CreateBuffer();
|
||||
buffer[0] = ReportId;
|
||||
buffer[1] = (byte)Status;
|
||||
buffer[2] = SequenceNumber;
|
||||
buffer[3] = (byte)((RemainingCount >> 8) & 0xff);
|
||||
buffer[4] = (byte)(RemainingCount & 0xff);
|
||||
buffer[5] = (byte)ProtocolType;
|
||||
buffer[6] = DataLength;
|
||||
buffer[7] = CommandClass;
|
||||
buffer[8] = Command;
|
||||
|
||||
for (int i = 0; i < Data.Length; i++)
|
||||
buffer[9 + i] = Data[i];
|
||||
|
||||
buffer[89] = GenerateChecksum(buffer);
|
||||
buffer[90] = Reserved;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static Packet FromBuffer(byte[] buffer)
|
||||
{
|
||||
var packet = new Packet
|
||||
{
|
||||
ReportId = buffer[0],
|
||||
Status = (DeviceStatus)buffer[1],
|
||||
SequenceNumber = buffer[2],
|
||||
RemainingCount = (short)((buffer[3] << 8) | buffer[4]),
|
||||
ProtocolType = (ProtocolType)buffer[5],
|
||||
DataLength = buffer[6],
|
||||
CommandClass = buffer[7],
|
||||
Command = buffer[8],
|
||||
CRC = buffer[89],
|
||||
Reserved = buffer[90]
|
||||
};
|
||||
|
||||
for (int i = 0; i < packet.Data.Length; i++)
|
||||
packet.Data[i] = buffer[9 + i];
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
public static byte[] CreateBuffer() => new byte[91];
|
||||
|
||||
internal static byte GenerateChecksum(byte[] buffer)
|
||||
{
|
||||
byte result = 0;
|
||||
for (int i = 3; i < 89; i++)
|
||||
{
|
||||
result = (byte)(result ^ buffer[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class SequenceCounter
|
||||
{
|
||||
private byte _sequenceId = 0x00;
|
||||
|
||||
public byte Next()
|
||||
{
|
||||
while (_sequenceId == 0x00)
|
||||
{
|
||||
_sequenceId += 0x08;
|
||||
}
|
||||
|
||||
return _sequenceId;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user