diff --git a/Project-Aurora/Project-Aurora/Nodes/Razer/RazerBatteryPctFetcher.cs b/Project-Aurora/Project-Aurora/Nodes/Razer/RazerBatteryPctFetcher.cs new file mode 100644 index 000000000..af32c6711 --- /dev/null +++ b/Project-Aurora/Project-Aurora/Nodes/Razer/RazerBatteryPctFetcher.cs @@ -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(); + } +} \ No newline at end of file diff --git a/Project-Aurora/Project-Aurora/Nodes/Razer/RazerBatteryStatusFetcher.cs b/Project-Aurora/Project-Aurora/Nodes/Razer/RazerBatteryStatusFetcher.cs new file mode 100644 index 000000000..9708f3a2a --- /dev/null +++ b/Project-Aurora/Project-Aurora/Nodes/Razer/RazerBatteryStatusFetcher.cs @@ -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(); + } +} \ No newline at end of file diff --git a/Project-Aurora/Project-Aurora/Nodes/Razer/RazerBatteryFetcher.cs b/Project-Aurora/Project-Aurora/Nodes/Razer/RazerFetcher.cs similarity index 56% rename from Project-Aurora/Project-Aurora/Nodes/Razer/RazerBatteryFetcher.cs rename to Project-Aurora/Project-Aurora/Nodes/Razer/RazerFetcher.cs index 3b490f27e..040182620 100644 --- a/Project-Aurora/Project-Aurora/Nodes/Razer/RazerBatteryFetcher.cs +++ b/Project-Aurora/Project-Aurora/Nodes/Razer/RazerFetcher.cs @@ -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 @@ -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; @@ -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); } } \ No newline at end of file diff --git a/Project-Aurora/Project-Aurora/Nodes/RazerDevices.cs b/Project-Aurora/Project-Aurora/Nodes/RazerDevices.cs index e8ad48d76..baca13610 100644 --- a/Project-Aurora/Project-Aurora/Nodes/RazerDevices.cs +++ b/Project-Aurora/Project-Aurora/Nodes/RazerDevices.cs @@ -5,7 +5,10 @@ namespace AuroraRgb.Nodes; public class RazerDevices : Node { - private Temporary _razerBatteryFetcher = new(() => new RazerBatteryFetcher()); + private readonly Temporary _razerBatteryFetcher = new(() => new RazerBatteryPctFetcher()); + + private readonly Temporary _razerBatteryStatusFetcher = new(() => new RazerBatteryStatusFetcher()); public double MouseBatteryPercentage => _razerBatteryFetcher.Value.MouseBatteryPercentage; + public bool MouseBatteryCharging => _razerBatteryStatusFetcher.Value.MouseBatteryCharging; } \ No newline at end of file