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,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;
}
}
}

View File

@@ -0,0 +1,69 @@
// 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.Razer;
internal class RazerGroup : IGroup
{
private readonly List<IHardware> _hardware = new();
private readonly StringBuilder _report = new();
public RazerGroup(ISettings settings)
{
_report.AppendLine("Razer Hardware");
_report.AppendLine();
foreach (HidDevice dev in DeviceList.Local.GetHidDevices(0x1532))
{
string productName = dev.GetProductName();
switch (dev.ProductID)
{
case 0x0F3C: // Razer PWM PC fan controller
if (dev.GetMaxFeatureReportLength() <= 0)
break;
var device = new RazerFanController(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 Razer 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();
}
}