Skip to content

Commit

Permalink
add razer mouse battery status node
Browse files Browse the repository at this point in the history
  • Loading branch information
Aytackydln committed Jul 11, 2024
1 parent a42a1f0 commit 635ab2b
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 69 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System;
using System.Linq;
using System.Threading;
using AuroraRgb.Modules;
using AuroraRgb.Modules.OnlineConfigs.Model;

namespace AuroraRgb.Nodes.Razer;

sealed class RazerBatteryPctFetcher : RazerFetcher
{
public double MouseBatteryPercentage { get; private set; }

private readonly Timer _timer;

public RazerBatteryPctFetcher()
{
_timer = new Timer(_ => TimerUpdate(), null, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(30));
}

private byte[] BatteryLevelMessage(RazerMouseHidInfo mouseHidInfo)
{
var tid = byte.Parse(mouseHidInfo.TransactionId.Split('x')[1], System.Globalization.NumberStyles.HexNumber);
var header = new byte[] { 0x00, tid, 0x00, 0x00, 0x00, 0x02, 0x07, 0x80 };

var crc = 0;
for (var i = 2; i < header.Length; i++)
{
crc ^= header[i];
}

var data = new byte[80];
var crcData = new byte[] { (byte)crc, 0 };

return header.Concat(data).Concat(crcData).ToArray();
}

private void TimerUpdate()
{
try
{
UpdateBatteryPct();
}
catch (Exception e)
{
Global.logger.Error(e, "RazerBatteryPctFetcher update error");
}
}

private void UpdateBatteryPct()
{
var usbDevice = GetUsbDevice();
if (usbDevice == null)
{
return;
}

try
{
if (!Mutex.WaitOne(TimeSpan.FromMilliseconds(2000), true))
{
return;
}
}
catch (AbandonedMutexException)
{
//continue
}

var mouseDictionary = OnlineSettings.RazerDeviceInfo.MouseHidInfos;
var mouseHidInfo = mouseDictionary[GetDeviceProductKeyString(usbDevice)];
usbDevice.Open();
var batteryLevel = GetReport(usbDevice, BatteryLevelMessage(mouseHidInfo));
Mutex.ReleaseMutex();
usbDevice.Close();
usbDevice.Dispose();

if (batteryLevel != null)
{
MouseBatteryPercentage = batteryLevel[9] / 255d;
}
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_timer.Dispose();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System;
using System.Linq;
using System.Threading;
using AuroraRgb.Modules;
using AuroraRgb.Modules.OnlineConfigs.Model;

namespace AuroraRgb.Nodes.Razer;

sealed class RazerBatteryStatusFetcher : RazerFetcher
{
public bool MouseBatteryCharging { get; private set; }
private readonly Timer _timer;

public RazerBatteryStatusFetcher()
{
_timer = new Timer(_ => TimerUpdate(), null, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(20));
}

private byte[] BatteryStatusMessage(RazerMouseHidInfo mouseHidInfo)
{
var tid = byte.Parse(mouseHidInfo.TransactionId.Split('x')[1], System.Globalization.NumberStyles.HexNumber);
var header = new byte[] { 0x00, tid, 0x00, 0x00, 0x00, 0x02, 0x07, 0x84 };

var crc = 0;
for (var i = 2; i < header.Length; i++)
{
crc ^= header[i];
}

var data = new byte[80];
var crcData = new byte[] { (byte)crc, 0 };

return header.Concat(data).Concat(crcData).ToArray();
}

private void TimerUpdate()
{
try
{
UpdateBatteryStatus();
}
catch (Exception e)
{
Global.logger.Error(e, "RazerBatteryStatusFetcher update error");
}
}

private void UpdateBatteryStatus()
{
var usbDevice = GetUsbDevice();
if (usbDevice == null)
{
return;
}

try
{
if (!Mutex.WaitOne(TimeSpan.FromMilliseconds(2000), true))
{
return;
}
}
catch (AbandonedMutexException)
{
//continue
}

var mouseDictionary = OnlineSettings.RazerDeviceInfo.MouseHidInfos;
var mouseHidInfo = mouseDictionary[GetDeviceProductKeyString(usbDevice)];
usbDevice.Open();
var batteryStatus = GetReport(usbDevice, BatteryStatusMessage(mouseHidInfo));
Mutex.ReleaseMutex();
usbDevice.Close();
usbDevice.Dispose();

if (batteryStatus != null)
{
MouseBatteryCharging = batteryStatus[9] == 1;
}
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_timer.Dispose();
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
using System;
using System.Linq;
using System.Threading;
using AuroraRgb.Modules;
using AuroraRgb.Modules.OnlineConfigs.Model;
using LibUsbDotNet.LibUsb;
using LibUsbDotNet.Main;

namespace AuroraRgb.Nodes.Razer;

class RazerBatteryFetcher : IDisposable
internal abstract class RazerFetcher : IDisposable
{
private const int HidReqSetReport = 0x09;
private const int HidReqGetReport = 0x01; // Add GET_REPORT request
Expand All @@ -20,87 +18,28 @@ class RazerBatteryFetcher : IDisposable
private const int UsbTypeRequestIn = UsbTypeClass | UsbRecipInterface | UsbDirIn;
private const int RazerUsbReportLen = 90; // Example length, set this according to actual length

public double MouseBatteryPercentage { get; private set; }
private readonly Timer _timer;
protected readonly Mutex Mutex = new(false, "Global\\RazerLinkReadWriteGuardMutex");

public RazerBatteryFetcher()
{
_timer = new Timer(_ => UpdateBattery(), null, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(20));
}

private byte[] GenerateMessage(RazerMouseHidInfo mouseHidInfo)
{
var tid = byte.Parse(mouseHidInfo.TransactionId.Split('x')[1], System.Globalization.NumberStyles.HexNumber);
var header = new byte[] { 0x00, tid, 0x00, 0x00, 0x00, 0x02, 0x07, 0x80 };

var crc = 0;
for (var i = 2; i < header.Length; i++)
{
crc ^= header[i];
}

var data = new byte[80];
var crcData = new byte[] { (byte)crc, 0 };

return header.Concat(data).Concat(crcData).ToArray();
}

private void UpdateBattery()
protected static IUsbDevice? GetUsbDevice()
{
const int vendorId = 0x1532;
var mouseDictionary = OnlineSettings.RazerDeviceInfo.MouseHidInfos;

using var context = new UsbContext();
var usbDevice = context.Find(d =>
d.VendorId == vendorId &&
mouseDictionary.ContainsKey(GetDeviceProductKeyString(d)));
if (usbDevice == null)
{
return;
}

using Mutex mutex = new(false, "Global\\RazerLinkReadWriteGuardMutex");

try
{
if (!mutex.WaitOne(TimeSpan.FromMilliseconds(2000), true))
{
return;
}
}
catch (AbandonedMutexException)
{
//continue
}

var mouseHidInfo = mouseDictionary[GetDeviceProductKeyString(usbDevice)];
var res = GetValue(usbDevice, GenerateMessage(mouseHidInfo));
mutex.ReleaseMutex();

if (res == null)
{
return;
}

MouseBatteryPercentage = res[9] / 255d;
return usbDevice;
}

private static byte[]? GetValue(IUsbDevice usbDevice, byte[] msg)
protected static byte[]? GetReport(IUsbDevice usbDevice, byte[] msg)
{
usbDevice.Open();
RazerSendControlMsg(usbDevice, msg, 0x09);
Thread.Sleep(50);
var res = RazerReadResponseMsg(usbDevice, 0x01);
usbDevice.Close();
usbDevice.Dispose();
return res;
}

private static string GetDeviceProductKeyString(IUsbDevice device)
{
return "0x"+device.ProductId.ToString("X4");
}

private static void RazerSendControlMsg(IUsbDevice usbDev, byte[] data, uint reportIndex)
{
const ushort value = 0x300;
Expand Down Expand Up @@ -130,8 +69,21 @@ private static void RazerSendControlMsg(IUsbDevice usbDev, byte[] data, uint rep
return transferredLength != responseBuffer.Length ? null : responseBuffer;
}

protected static string GetDeviceProductKeyString(IUsbDevice device)
{
return "0x"+device.ProductId.ToString("X4");
}

protected virtual void Dispose(bool disposing)
{
if (!disposing) return;
Mutex.Close();
Mutex.Dispose();
}

public void Dispose()
{
_timer.Dispose();
Dispose(true);
GC.SuppressFinalize(this);
}
}
5 changes: 4 additions & 1 deletion Project-Aurora/Project-Aurora/Nodes/RazerDevices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ namespace AuroraRgb.Nodes;

public class RazerDevices : Node
{
private Temporary<RazerBatteryFetcher> _razerBatteryFetcher = new(() => new RazerBatteryFetcher());
private readonly Temporary<RazerBatteryPctFetcher> _razerBatteryFetcher = new(() => new RazerBatteryPctFetcher());

private readonly Temporary<RazerBatteryStatusFetcher> _razerBatteryStatusFetcher = new(() => new RazerBatteryStatusFetcher());

public double MouseBatteryPercentage => _razerBatteryFetcher.Value.MouseBatteryPercentage;
public bool MouseBatteryCharging => _razerBatteryStatusFetcher.Value.MouseBatteryCharging;
}

0 comments on commit 635ab2b

Please sign in to comment.