diff --git a/RGB.NET.Devices.WLED/API/WledAPI.cs b/RGB.NET.Devices.WLED/API/WledAPI.cs new file mode 100644 index 0000000..c67cda9 --- /dev/null +++ b/RGB.NET.Devices.WLED/API/WledAPI.cs @@ -0,0 +1,33 @@ +using System.Net.Http; +using System.Net.Http.Json; + +namespace RGB.NET.Devices.WLED; + +/// +/// Partial implementation of the WLED-JSON-API +/// +public static class WledAPI +{ + /// + /// Gets the data returned by the 'info' endpoint of the WLED-device. + /// + /// The address of the device to request data from. + /// The data returned by the WLED-device. + public static WledInfo? Info(string address) + { + if (string.IsNullOrEmpty(address)) return null; + + using HttpClient client = new(); + try + { + return client.Send(new HttpRequestMessage(HttpMethod.Get, $"http://{address}/json/info")) + .Content + .ReadFromJsonAsync() + .Result; + } + catch + { + return null; + } + } +} \ No newline at end of file diff --git a/RGB.NET.Devices.WLED/API/WledInfo.cs b/RGB.NET.Devices.WLED/API/WledInfo.cs new file mode 100644 index 0000000..ca92ab8 --- /dev/null +++ b/RGB.NET.Devices.WLED/API/WledInfo.cs @@ -0,0 +1,156 @@ +// ReSharper disable InconsistentNaming +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace RGB.NET.Devices.WLED; + +public class WledInfo +{ + [JsonPropertyName("ver")] + public string Version { get; set; } = ""; + + [JsonPropertyName("vid")] + public uint BuildId { get; set; } + + [JsonPropertyName("leds")] + public LedsInfo Leds { get; set; } = new(); + + [JsonPropertyName("str")] + public bool SyncReceive { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } = ""; + + [JsonPropertyName("udpport")] + public ushort UDPPort { get; set; } + + [JsonPropertyName("live")] + public bool IsLive { get; set; } + + [JsonPropertyName("liveseg")] + public short MainSegment { get; set; } + + [JsonPropertyName("lm")] + public string RealtimeDataSource { get; set; } = ""; + + [JsonPropertyName("lip")] + public string RealtimeDataSourceIpAddress { get; set; } = ""; + + [JsonPropertyName("ws")] + public byte ConnectedWebSocketCount { get; set; } + + [JsonPropertyName("fxcount")] + public byte EffectCount { get; set; } + + [JsonPropertyName("palcount")] + public short PaletteCount { get; set; } + + [JsonPropertyName("maps")] + public List LedMaps { get; set; } = []; + + [JsonPropertyName("wifi")] + public Wifi WifiInfo { get; set; } = new(); + + [JsonPropertyName("fs")] + public Fs FilesystemInfo { get; set; } = new(); + + [JsonPropertyName("ndc")] + public short DiscoveredDeviceCount { get; set; } + + [JsonPropertyName("arch")] + public string PlatformName { get; set; } = ""; + + [JsonPropertyName("core")] + public string ArduinoCoreVersion { get; set; } = ""; + + [JsonPropertyName("freeheap")] + public uint FreeHeap { get; set; } + + [JsonPropertyName("uptime")] + public uint Uptime { get; set; } + + [JsonPropertyName("time")] + public string Time { get; set; } = ""; + + [JsonPropertyName("brand")] + public string Brand { get; set; } = ""; + + [JsonPropertyName("product")] + public string Product { get; set; } = ""; + + [JsonPropertyName("ip")] + public string IpAddress { get; set; } = ""; + + + public class LedsInfo + { + [JsonPropertyName("count")] + public ushort Count { get; set; } + + [JsonPropertyName("pwr")] + public ushort PowerUsage { get; set; } + + [JsonPropertyName("fps")] + public ushort RefreshRate { get; set; } + + [JsonPropertyName("maxpwr")] + public ushort MaxPower { get; set; } + + [JsonPropertyName("maxseg")] + public byte MaxSegments { get; set; } + + [JsonPropertyName("matrix")] + public MatrixDims? Matrix { get; set; } + + [JsonPropertyName("seglc")] + public List SegmentLightCapabilities { get; set; } = []; + + [JsonPropertyName("lc")] + public byte CombinedSegmentLightCapabilities { get; set; } + } + + public class Map + { + [JsonPropertyName("id")] + public byte Id { get; set; } + + [JsonPropertyName("n")] + public string Name { get; set; } = ""; + } + + public class Wifi + { + [JsonPropertyName("bssid")] + public string BSSID { get; set; } = ""; + + [JsonPropertyName("rssi")] + public long RSSI { get; set; } + + [JsonPropertyName("signal")] + public byte SignalQuality { get; set; } + + [JsonPropertyName("channel")] + public int Channel { get; set; } + } + + public class Fs + { + [JsonPropertyName("u")] + public uint UsedSpace { get; set; } + + [JsonPropertyName("t")] + public uint TotalSpace { get; set; } + + [JsonPropertyName("pmt")] + public ulong LastPresetsJsonModificationTime { get; set; } + } + + public class MatrixDims + { + [JsonPropertyName("w")] + public ushort Width { get; set; } + + [JsonPropertyName("h")] + public ushort Height { get; set; } + } +} diff --git a/RGB.NET.Devices.WLED/Generic/IWLedRGBDevice.cs b/RGB.NET.Devices.WLED/Generic/IWLedRGBDevice.cs new file mode 100644 index 0000000..f875820 --- /dev/null +++ b/RGB.NET.Devices.WLED/Generic/IWLedRGBDevice.cs @@ -0,0 +1,8 @@ +using RGB.NET.Core; + +namespace RGB.NET.Devices.WLED; + +/// +/// Represents a WLED-device. +/// +internal interface IWledRGBDevice : IRGBDevice; \ No newline at end of file diff --git a/RGB.NET.Devices.WLED/Generic/IWledDeviceDefinition.cs b/RGB.NET.Devices.WLED/Generic/IWledDeviceDefinition.cs new file mode 100644 index 0000000..2ea5299 --- /dev/null +++ b/RGB.NET.Devices.WLED/Generic/IWledDeviceDefinition.cs @@ -0,0 +1,11 @@ +namespace RGB.NET.Devices.WLED; + +/// +/// Represents the data used to create a WLED-device. +/// +public interface IWledDeviceDefinition +{ + string Address { get; } + string? Manufacturer { get; } + string? Model { get; } +} \ No newline at end of file diff --git a/RGB.NET.Devices.WLED/Generic/WLedDeviceUpdateQueue.cs b/RGB.NET.Devices.WLED/Generic/WLedDeviceUpdateQueue.cs new file mode 100644 index 0000000..d16834b --- /dev/null +++ b/RGB.NET.Devices.WLED/Generic/WLedDeviceUpdateQueue.cs @@ -0,0 +1,102 @@ +using System; +using System.Net.Sockets; +using RGB.NET.Core; + +namespace RGB.NET.Devices.WLED; + +/// +/// +/// Represents the update-queue performing updates for WLED devices. +/// +internal sealed class WledDeviceUpdateQueue : UpdateQueue +{ + #region Properties & Fields + + /// + /// The UDP-Connection used to send data. + /// + private readonly UdpClient _socket; + + /// + /// The buffer the UDP-data is stored in. + /// + private byte[] _buffer; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The update trigger used by this queue. + /// The device type used to identify the device. + public WledDeviceUpdateQueue(IDeviceUpdateTrigger updateTrigger, string address, int port, int ledCount) + : base(updateTrigger) + { + _buffer = new byte[2 + (ledCount * 3)]; + _buffer[0] = 2; // protocol: DRGB + _buffer[1] = 2; // Timeout 2s + + _socket = new UdpClient(); + _socket.Connect(address, port); + } + + #endregion + + #region Methods + + /// + protected override void OnUpdate(object? sender, CustomUpdateData customData) + { + try + { + if (customData[CustomUpdateDataIndex.HEARTBEAT] as bool? ?? false) + Update(Array.Empty<(object key, Color color)>()); + else + base.OnUpdate(sender, customData); + } + catch (Exception ex) + { + WledDeviceProvider.Instance.Throw(ex); + } + } + + /// + protected override bool Update(in ReadOnlySpan<(object key, Color color)> dataSet) + { + try + { + Span data = _buffer.AsSpan()[2..]; + foreach ((object key, Color color) in dataSet) + { + int ledIndex = (int)key; + int offset = (ledIndex * 3); + data[offset] = color.GetR(); + data[offset + 1] = color.GetG(); + data[offset + 2] = color.GetB(); + } + + _socket.Send(_buffer); + + return true; + } + catch (Exception ex) + { + WledDeviceProvider.Instance.Throw(ex); + } + + return false; + } + + /// + public override void Dispose() + { + base.Dispose(); + + _socket.Dispose(); + _buffer = []; + } + + #endregion +} \ No newline at end of file diff --git a/RGB.NET.Devices.WLED/Generic/WLedRGBDevice.cs b/RGB.NET.Devices.WLED/Generic/WLedRGBDevice.cs new file mode 100644 index 0000000..080b246 --- /dev/null +++ b/RGB.NET.Devices.WLED/Generic/WLedRGBDevice.cs @@ -0,0 +1,37 @@ +using RGB.NET.Core; + +namespace RGB.NET.Devices.WLED; + +/// +/// +/// +/// Represents a WLED-device. +/// +public sealed class WledRGBDevice : AbstractRGBDevice, IWledRGBDevice, ILedStripe +{ + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + internal WledRGBDevice(WledRGBDeviceInfo info, string address, IDeviceUpdateTrigger updateTrigger) + : base(info, new WledDeviceUpdateQueue(updateTrigger, address, info.Info.UDPPort, info.Info.Leds.Count)) + { + InitializeLayout(); + } + + #endregion + + #region Methods + + private void InitializeLayout() + { + for (int i = 0; i < DeviceInfo.Info.Leds.Count; i++) + AddLed(LedId.LedStripe1 + i, new Point(i * 10, 0), new Size(10, 10)); + } + + /// + protected override object GetLedCustomData(LedId ledId) => ledId - LedId.LedStripe1; + + #endregion +} \ No newline at end of file diff --git a/RGB.NET.Devices.WLED/Generic/WLedRGBDeviceInfo.cs b/RGB.NET.Devices.WLED/Generic/WLedRGBDeviceInfo.cs new file mode 100644 index 0000000..896ca94 --- /dev/null +++ b/RGB.NET.Devices.WLED/Generic/WLedRGBDeviceInfo.cs @@ -0,0 +1,52 @@ +using RGB.NET.Core; + +namespace RGB.NET.Devices.WLED; + +/// +/// +/// Represents a generic information for a WLED-. +/// +public sealed class WledRGBDeviceInfo : IRGBDeviceInfo +{ + #region Properties & Fields + + /// + public RGBDeviceType DeviceType => RGBDeviceType.LedStripe; + + /// + public string DeviceName { get; } + + /// + public string Manufacturer { get; } + + /// + public string Model { get; } + + /// + public object? LayoutMetadata { get; set; } + + /// + /// Gets some info returned by the WLED-device. + /// + public WledInfo Info { get; } + + #endregion + + #region Constructors + + /// + /// Internal constructor of managed . + /// + /// The manufacturer of the device. + /// The represented device model. + internal WledRGBDeviceInfo(WledInfo info, string? manufacturer, string? model) + { + this.Info = info; + this.Manufacturer = manufacturer ?? info.Brand; + this.Model = model ?? info.Name; + + DeviceName = DeviceHelper.CreateDeviceName(Manufacturer, Model); + } + + #endregion +} \ No newline at end of file diff --git a/RGB.NET.Devices.WLED/Generic/WledDeviceDefinition.cs b/RGB.NET.Devices.WLED/Generic/WledDeviceDefinition.cs new file mode 100644 index 0000000..7d70f28 --- /dev/null +++ b/RGB.NET.Devices.WLED/Generic/WledDeviceDefinition.cs @@ -0,0 +1,18 @@ +namespace RGB.NET.Devices.WLED; + +/// +public class WledDeviceDefinition(string address, string? manufacturer = null, string? model = null) : IWledDeviceDefinition +{ + #region Properties & Fields + + /// + public string Address { get; } = address; + + /// + public string? Manufacturer { get; } = manufacturer; + + /// + public string? Model { get; } = model; + + #endregion +} \ No newline at end of file diff --git a/RGB.NET.Devices.WLED/README.md b/RGB.NET.Devices.WLED/README.md new file mode 100644 index 0000000..da3eb5a --- /dev/null +++ b/RGB.NET.Devices.WLED/README.md @@ -0,0 +1,18 @@ +[RGB.NET](https://github.com/DarthAffe/RGB.NET) Device-Provider-Package for [WLED](https://kno.wled.ge/)-devices. + +## Usage +This provider does not load anything by default and requires additional configuration to work. + +```csharp +// Add as many WLED-devices as you like +WledDeviceProvider.Instance.AddDeviceDefinition(new WledDeviceDefinition("")); + +surface.Load(WledDeviceProvider.Instance); +``` + +You can also override the manufacturer and device model in the DeviceDefinition. + +# Required SDK +This provider does not require an additional SDK. + +UDP realtime needs to be enabled on the WLED device. \ No newline at end of file diff --git a/RGB.NET.Devices.WLED/RGB.NET.Devices.WLED.csproj b/RGB.NET.Devices.WLED/RGB.NET.Devices.WLED.csproj new file mode 100644 index 0000000..84ceafa --- /dev/null +++ b/RGB.NET.Devices.WLED/RGB.NET.Devices.WLED.csproj @@ -0,0 +1,62 @@ + + + net8.0;net7.0;net6.0 + latest + enable + + Darth Affe + Wyrez + en-US + en-US + RGB.NET.Devices.WLED + RGB.NET.Devices.WLED + RGB.NET.Devices.WLED + RGB.NET.Devices.WLED + RGB.NET.Devices.WLED + WLED-Device-Implementations of RGB.NET + WLED-Device-Implementations of RGB.NET, a C# (.NET) library for accessing various RGB-peripherals + Copyright © Darth Affe 2024 + Copyright © Darth Affe 2024 + icon.png + README.md + https://github.com/DarthAffe/RGB.NET + LGPL-2.1-only + Github + https://github.com/DarthAffe/RGB.NET + True + + + + 0.0.1 + 0.0.1 + 0.0.1 + + ..\bin\ + true + True + True + portable + snupkg + + + + TRACE;DEBUG + true + false + + + + true + $(NoWarn);CS1591;CS1572;CS1573 + RELEASE + + + + + + + + + + + \ No newline at end of file diff --git a/RGB.NET.Devices.WLED/RGB.NET.Devices.WLED.csproj.DotSettings b/RGB.NET.Devices.WLED/RGB.NET.Devices.WLED.csproj.DotSettings new file mode 100644 index 0000000..e43e41f --- /dev/null +++ b/RGB.NET.Devices.WLED/RGB.NET.Devices.WLED.csproj.DotSettings @@ -0,0 +1,3 @@ + + True + True \ No newline at end of file diff --git a/RGB.NET.Devices.WLED/WLedDeviceProvider.cs b/RGB.NET.Devices.WLED/WLedDeviceProvider.cs new file mode 100644 index 0000000..edb53c0 --- /dev/null +++ b/RGB.NET.Devices.WLED/WLedDeviceProvider.cs @@ -0,0 +1,113 @@ +// ReSharper disable MemberCanBePrivate.Global + +using System; +using System.Collections.Generic; +using RGB.NET.Core; + +namespace RGB.NET.Devices.WLED; + +/// +/// +/// Represents a device provider responsible for WS2812B- and WS2811-Led-devices. +/// +// ReSharper disable once InconsistentNaming +// ReSharper disable once UnusedType.Global +public sealed class WledDeviceProvider : AbstractRGBDeviceProvider +{ + #region Constants + + private const int HEARTBEAT_TIMER = 1000; + + #endregion + + #region Properties & Fields + + // ReSharper disable once InconsistentNaming + private static readonly object _lock = new(); + + private static WledDeviceProvider? _instance; + /// + /// Gets the singleton instance. + /// + public static WledDeviceProvider Instance + { + get + { + lock (_lock) + return _instance ?? new WledDeviceProvider(); + } + } + + /// + /// Gets a list of all defined device-definitions. + /// + public List DeviceDefinitions { get; } = []; + + #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 WledDeviceProvider() + { + lock (_lock) + { + if (_instance != null) throw new InvalidOperationException($"There can be only one instance of type {nameof(WledDeviceProvider)}"); + _instance = this; + } + } + + #endregion + + #region Methods + + /// + /// Adds the specified to this device-provider. + /// + /// The to add. + public void AddDeviceDefinition(IWledDeviceDefinition deviceDefinition) => DeviceDefinitions.Add(deviceDefinition); + + /// + protected override void InitializeSDK() { } + + /// + protected override IEnumerable LoadDevices() + { + int i = 0; + foreach (IWledDeviceDefinition deviceDefinition in DeviceDefinitions) + { + IDeviceUpdateTrigger updateTrigger = GetUpdateTrigger(i++); + WledRGBDevice? device = CreateWledDevice(deviceDefinition, updateTrigger); + if (device != null) + yield return device; + } + } + + private static WledRGBDevice? CreateWledDevice(IWledDeviceDefinition deviceDefinition, IDeviceUpdateTrigger updateTrigger) + { + WledInfo? wledInfo = WledAPI.Info(deviceDefinition.Address); + if (wledInfo == null) return null; + + return new WledRGBDevice(new WledRGBDeviceInfo(wledInfo, deviceDefinition.Manufacturer, deviceDefinition.Model), deviceDefinition.Address, updateTrigger); + } + + protected override IDeviceUpdateTrigger CreateUpdateTrigger(int id, double updateRateHardLimit) => new DeviceUpdateTrigger(updateRateHardLimit) { HeartbeatTimer = HEARTBEAT_TIMER }; + + /// + protected override void Dispose(bool disposing) + { + lock (_lock) + { + base.Dispose(disposing); + + DeviceDefinitions.Clear(); + + _instance = null; + } + } + + #endregion +} \ No newline at end of file diff --git a/RGB.NET.sln b/RGB.NET.sln index 257bf65..44b786e 100644 --- a/RGB.NET.sln +++ b/RGB.NET.sln @@ -49,6 +49,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RGB.NET.Devices.OpenRGB", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RGB.NET.Devices.Corsair_Legacy", "RGB.NET.Devices.Corsair_Legacy\RGB.NET.Devices.Corsair_Legacy.csproj", "{66AF690C-27A1-4097-AC53-57C0ED89E286}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RGB.NET.Devices.WLED", "RGB.NET.Devices.WLED\RGB.NET.Devices.WLED.csproj", "{C533C5EA-66A8-4826-A814-80791E7593ED}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -139,6 +141,10 @@ Global {66AF690C-27A1-4097-AC53-57C0ED89E286}.Debug|Any CPU.Build.0 = Debug|Any CPU {66AF690C-27A1-4097-AC53-57C0ED89E286}.Release|Any CPU.ActiveCfg = Release|Any CPU {66AF690C-27A1-4097-AC53-57C0ED89E286}.Release|Any CPU.Build.0 = Release|Any CPU + {C533C5EA-66A8-4826-A814-80791E7593ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C533C5EA-66A8-4826-A814-80791E7593ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C533C5EA-66A8-4826-A814-80791E7593ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C533C5EA-66A8-4826-A814-80791E7593ED}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -161,6 +167,7 @@ Global {EDBA49D6-AE96-4E96-9E6A-30154D93BD5F} = {92D7C263-D4C9-4D26-93E2-93C1F9C2CD16} {F29A96E5-CDD0-469F-A871-A35A7519BC49} = {D13032C6-432E-4F43-8A32-071133C22B16} {66AF690C-27A1-4097-AC53-57C0ED89E286} = {D13032C6-432E-4F43-8A32-071133C22B16} + {C533C5EA-66A8-4826-A814-80791E7593ED} = {D13032C6-432E-4F43-8A32-071133C22B16} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7F222AD4-1F9E-4AAB-9D69-D62372D4C1BA}