1
0
mirror of https://github.com/DarthAffe/RGB.NET.git synced 2025-12-12 17:48:31 +00:00

Added Provider for WLED-Devices

This commit is contained in:
Darth Affe 2024-01-17 23:02:02 +01:00
parent 3400edcf93
commit 67672be54c
13 changed files with 620 additions and 0 deletions

View File

@ -0,0 +1,33 @@
using System.Net.Http;
using System.Net.Http.Json;
namespace RGB.NET.Devices.WLED;
/// <summary>
/// Partial implementation of the WLED-JSON-API
/// </summary>
public static class WledAPI
{
/// <summary>
/// Gets the data returned by the 'info' endpoint of the WLED-device.
/// </summary>
/// <param name="address">The address of the device to request data from.</param>
/// <returns>The data returned by the WLED-device.</returns>
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<WledInfo>()
.Result;
}
catch
{
return null;
}
}
}

View File

@ -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<Map> 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<byte> 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; }
}
}

View File

@ -0,0 +1,8 @@
using RGB.NET.Core;
namespace RGB.NET.Devices.WLED;
/// <summary>
/// Represents a WLED-device.
/// </summary>
internal interface IWledRGBDevice : IRGBDevice;

View File

@ -0,0 +1,11 @@
namespace RGB.NET.Devices.WLED;
/// <summary>
/// Represents the data used to create a WLED-device.
/// </summary>
public interface IWledDeviceDefinition
{
string Address { get; }
string? Manufacturer { get; }
string? Model { get; }
}

View File

@ -0,0 +1,102 @@
using System;
using System.Net.Sockets;
using RGB.NET.Core;
namespace RGB.NET.Devices.WLED;
/// <inheritdoc />
/// <summary>
/// Represents the update-queue performing updates for WLED devices.
/// </summary>
internal sealed class WledDeviceUpdateQueue : UpdateQueue
{
#region Properties & Fields
/// <summary>
/// The UDP-Connection used to send data.
/// </summary>
private readonly UdpClient _socket;
/// <summary>
/// The buffer the UDP-data is stored in.
/// </summary>
private byte[] _buffer;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="WledDeviceUpdateQueue"/> class.
/// </summary>
/// <param name="updateTrigger">The update trigger used by this queue.</param>
/// <param name="deviceType">The device type used to identify the device.</param>
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
/// <inheritdoc />
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);
}
}
/// <inheritdoc />
protected override bool Update(in ReadOnlySpan<(object key, Color color)> dataSet)
{
try
{
Span<byte> 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;
}
/// <inheritdoc />
public override void Dispose()
{
base.Dispose();
_socket.Dispose();
_buffer = [];
}
#endregion
}

View File

@ -0,0 +1,37 @@
using RGB.NET.Core;
namespace RGB.NET.Devices.WLED;
/// <inheritdoc cref="AbstractRGBDevice{WledRGBDeviceInfo}" />
/// <inheritdoc cref="IWledRGBDevice" />
/// <summary>
/// Represents a WLED-device.
/// </summary>
public sealed class WledRGBDevice : AbstractRGBDevice<WledRGBDeviceInfo>, IWledRGBDevice, ILedStripe
{
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="WledRGBDevice"/> class.
/// </summary>
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));
}
/// <inheritdoc />
protected override object GetLedCustomData(LedId ledId) => ledId - LedId.LedStripe1;
#endregion
}

View File

@ -0,0 +1,52 @@
using RGB.NET.Core;
namespace RGB.NET.Devices.WLED;
/// <inheritdoc />
/// <summary>
/// Represents a generic information for a WLED-<see cref="T:RGB.NET.Core.IRGBDevice" />.
/// </summary>
public sealed class WledRGBDeviceInfo : IRGBDeviceInfo
{
#region Properties & Fields
/// <inheritdoc />
public RGBDeviceType DeviceType => RGBDeviceType.LedStripe;
/// <inheritdoc />
public string DeviceName { get; }
/// <inheritdoc />
public string Manufacturer { get; }
/// <inheritdoc />
public string Model { get; }
/// <inheritdoc />
public object? LayoutMetadata { get; set; }
/// <summary>
/// Gets some info returned by the WLED-device.
/// </summary>
public WledInfo Info { get; }
#endregion
#region Constructors
/// <summary>
/// Internal constructor of managed <see cref="WledRGBDeviceInfo"/>.
/// </summary>
/// <param name="manufacturer">The manufacturer of the device.</param>
/// <param name="model">The represented device model.</param>
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
}

View File

@ -0,0 +1,18 @@
namespace RGB.NET.Devices.WLED;
/// <inheritdoc />
public class WledDeviceDefinition(string address, string? manufacturer = null, string? model = null) : IWledDeviceDefinition
{
#region Properties & Fields
/// <inheritdoc />
public string Address { get; } = address;
/// <inheritdoc />
public string? Manufacturer { get; } = manufacturer;
/// <inheritdoc />
public string? Model { get; } = model;
#endregion
}

View File

@ -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("<hostname>"));
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.

View File

@ -0,0 +1,62 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<Authors>Darth Affe</Authors>
<Company>Wyrez</Company>
<Language>en-US</Language>
<NeutralLanguage>en-US</NeutralLanguage>
<Title>RGB.NET.Devices.WLED</Title>
<AssemblyName>RGB.NET.Devices.WLED</AssemblyName>
<AssemblyTitle>RGB.NET.Devices.WLED</AssemblyTitle>
<PackageId>RGB.NET.Devices.WLED</PackageId>
<RootNamespace>RGB.NET.Devices.WLED</RootNamespace>
<Description>WLED-Device-Implementations of RGB.NET</Description>
<Summary>WLED-Device-Implementations of RGB.NET, a C# (.NET) library for accessing various RGB-peripherals</Summary>
<Copyright>Copyright © Darth Affe 2024</Copyright>
<PackageCopyright>Copyright © Darth Affe 2024</PackageCopyright>
<PackageIcon>icon.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageProjectUrl>https://github.com/DarthAffe/RGB.NET</PackageProjectUrl>
<PackageLicenseExpression>LGPL-2.1-only</PackageLicenseExpression>
<RepositoryType>Github</RepositoryType>
<RepositoryUrl>https://github.com/DarthAffe/RGB.NET</RepositoryUrl>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageReleaseNotes></PackageReleaseNotes>
<Version>0.0.1</Version>
<AssemblyVersion>0.0.1</AssemblyVersion>
<FileVersion>0.0.1</FileVersion>
<OutputPath>..\bin\</OutputPath>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<IncludeSource>True</IncludeSource>
<IncludeSymbols>True</IncludeSymbols>
<DebugType>portable</DebugType>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>TRACE;DEBUG</DefineConstants>
<DebugSymbols>true</DebugSymbols>
<Optimize>false</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<Optimize>true</Optimize>
<NoWarn>$(NoWarn);CS1591;CS1572;CS1573</NoWarn>
<DefineConstants>RELEASE</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Content Include="..\Resources\icon.png" Link="icon.png" Pack="true" PackagePath="\" />
<None Include="README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RGB.NET.Core\RGB.NET.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=api/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=generic/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -0,0 +1,113 @@
// ReSharper disable MemberCanBePrivate.Global
using System;
using System.Collections.Generic;
using RGB.NET.Core;
namespace RGB.NET.Devices.WLED;
/// <inheritdoc />
/// <summary>
/// Represents a device provider responsible for WS2812B- and WS2811-Led-devices.
/// </summary>
// 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;
/// <summary>
/// Gets the singleton <see cref="WledDeviceProvider"/> instance.
/// </summary>
public static WledDeviceProvider Instance
{
get
{
lock (_lock)
return _instance ?? new WledDeviceProvider();
}
}
/// <summary>
/// Gets a list of all defined device-definitions.
/// </summary>
public List<IWledDeviceDefinition> DeviceDefinitions { get; } = [];
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="WledDeviceProvider"/> class.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if this constructor is called even if there is already an instance of this class.</exception>
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
/// <summary>
/// Adds the specified <see cref="IWledDeviceDefinition" /> to this device-provider.
/// </summary>
/// <param name="deviceDefinition">The <see cref="IWledDeviceDefinition"/> to add.</param>
public void AddDeviceDefinition(IWledDeviceDefinition deviceDefinition) => DeviceDefinitions.Add(deviceDefinition);
/// <inheritdoc />
protected override void InitializeSDK() { }
/// <inheritdoc />
protected override IEnumerable<IRGBDevice> 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 };
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
lock (_lock)
{
base.Dispose(disposing);
DeviceDefinitions.Clear();
_instance = null;
}
}
#endregion
}

View File

@ -49,6 +49,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RGB.NET.Devices.OpenRGB", "
EndProject 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}" 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 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{66AF690C-27A1-4097-AC53-57C0ED89E286}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -161,6 +167,7 @@ Global
{EDBA49D6-AE96-4E96-9E6A-30154D93BD5F} = {92D7C263-D4C9-4D26-93E2-93C1F9C2CD16} {EDBA49D6-AE96-4E96-9E6A-30154D93BD5F} = {92D7C263-D4C9-4D26-93E2-93C1F9C2CD16}
{F29A96E5-CDD0-469F-A871-A35A7519BC49} = {D13032C6-432E-4F43-8A32-071133C22B16} {F29A96E5-CDD0-469F-A871-A35A7519BC49} = {D13032C6-432E-4F43-8A32-071133C22B16}
{66AF690C-27A1-4097-AC53-57C0ED89E286} = {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 EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7F222AD4-1F9E-4AAB-9D69-D62372D4C1BA} SolutionGuid = {7F222AD4-1F9E-4AAB-9D69-D62372D4C1BA}