// ReSharper disable MemberCanBePrivate.Global using System; using System.Collections.Generic; using System.Linq; using System.Threading; using HidSharp; using RGB.NET.Core; using RGB.NET.Devices.PicoPi.Enum; using RGB.NET.HID; namespace RGB.NET.Devices.PicoPi; /// /// /// Represents a device provider responsible for PicoPi-devices. /// // ReSharper disable once InconsistentNaming public sealed class PicoPiDeviceProvider : AbstractRGBDeviceProvider { #region Constants private const int AUTO_UPDATE_MODE_CHUNK_THRESHOLD = 2; #endregion #region Properties & Fields // ReSharper disable once InconsistentNaming private static readonly Lock _lock = new(); private static PicoPiDeviceProvider? _instance; /// /// Gets the singleton instance. /// public static PicoPiDeviceProvider Instance { get { lock (_lock) return _instance ?? new PicoPiDeviceProvider(); } } /// /// Gets the HID-definitions for PicoPi-devices. /// public static HIDLoader DeviceDefinitions { get; } = new(PicoPiSDK.VENDOR_ID) { { PicoPiSDK.HID_BULK_CONTROLLER_PID, RGBDeviceType.LedStripe, "WS2812B-Controller", LedMappings.StripeMapping, 0 }, }; private readonly List _sdks = []; /// /// Gets or sets the endpoint used to update devices. (default ). /// If auto is set it automatically is using bulk-updates for devies with more than 40 LEDs if supported. Else HID is used. /// public UpdateMode UpdateMode { get; set; } = UpdateMode.Auto; #endregion #region Constructors /// /// Initializes a new instance of the class. /// /// Thrown if this constructor is called even if there is already an instance of this class. public PicoPiDeviceProvider() { lock (_lock) { if (_instance != null) throw new InvalidOperationException($"There can be only one instance of type {nameof(PicoPiDeviceProvider)}"); _instance = this; } } #endregion #region Methods /// protected override void InitializeSDK() { } /// protected override IEnumerable GetLoadedDevices(RGBDeviceType loadFilter) { DeviceDefinitions.LoadFilter = loadFilter; return base.GetLoadedDevices(loadFilter); } /// protected override IEnumerable LoadDevices() { IEnumerable<(HIDDeviceDefinition definition, HidDevice device)> devices = DeviceDefinitions.GetConnectedDevices(); foreach ((HIDDeviceDefinition definition, HidDevice device) in devices) { PicoPiSDK sdk = new(device); _sdks.Add(sdk); IDeviceUpdateTrigger updateTrigger = GetUpdateTrigger(sdk.Id.GetHashCode()); foreach ((int channel, int ledCount, _) in sdk.Channels.Where(c => c.ledCount > 0)) { PicoPiRGBDevice picoPiDevice = new(new PicoPiRGBDeviceInfo(definition.DeviceType, definition.Name, sdk.Id, sdk.Version, channel, ledCount), GetUpdateQueue(updateTrigger, sdk, channel, ledCount), definition.LedMapping); picoPiDevice.Initialize(); yield return picoPiDevice; } if (sdk.IsBulkSupported) updateTrigger.Update += (_, _) => sdk.FlushBulk(); } } private IUpdateQueue GetUpdateQueue(IDeviceUpdateTrigger updateTrigger, PicoPiSDK sdk, int channel, int ledCount) { switch (UpdateMode) { case UpdateMode.HID: return new PicoPiHIDUpdateQueue(updateTrigger, sdk, channel, ledCount); case UpdateMode.BULK: if (!sdk.IsBulkSupported) throw new NotSupportedException("Bulk updates aren't supported for this device. Make sure the firmware is built with bulk support and the libusb driver is installed."); return new PicoPiBulkUpdateQueue(updateTrigger, sdk, channel, ledCount); case UpdateMode.Auto: if (!sdk.IsBulkSupported || (sdk.Channels.Sum(c => (int)Math.Ceiling(c.ledCount / 20.0)) <= AUTO_UPDATE_MODE_CHUNK_THRESHOLD)) return new PicoPiHIDUpdateQueue(updateTrigger, sdk, channel, ledCount); return new PicoPiBulkUpdateQueue(updateTrigger, sdk, channel, ledCount); default: throw new IndexOutOfRangeException($"Update mode {UpdateMode} is not supported."); } } /// protected override void Reset() { base.Reset(); foreach (PicoPiSDK sdk in _sdks) sdk.Dispose(); _sdks.Clear(); } /// protected override void Dispose(bool disposing) { lock (_lock) { base.Dispose(disposing); _instance = null; } } #endregion }