mirror of
https://github.com/DarthAffe/RGB.NET.git
synced 2025-12-12 17:48:31 +00:00
191 lines
5.8 KiB
C#
191 lines
5.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using RGB.NET.Core;
|
|
|
|
namespace RGB.NET.Devices.WS281X.NodeMCU;
|
|
|
|
// ReSharper disable once InconsistentNaming
|
|
/// <inheritdoc />
|
|
/// <summary>
|
|
/// Represents the update-queue performing updates for NodeMCU WS2812 devices.
|
|
/// </summary>
|
|
public sealed class NodeMCUWS2812USBUpdateQueue : UpdateQueue
|
|
{
|
|
#region Properties & Fields
|
|
|
|
private readonly string _hostname;
|
|
|
|
private HttpClient _httpClient = new();
|
|
private UdpClient? _udpClient;
|
|
|
|
private readonly Dictionary<int, byte[]> _dataBuffer = [];
|
|
private readonly Dictionary<int, byte> _sequenceNumbers = [];
|
|
|
|
private readonly Action<byte[]> _sendDataAction;
|
|
|
|
#endregion
|
|
|
|
#region Constructors
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="NodeMCUWS2812USBUpdateQueue"/> class.
|
|
/// If this constructor is used UDP updates are disabled.
|
|
/// </summary>
|
|
/// <param name="updateTrigger">The update trigger used by this queue.</param>
|
|
/// <param name="hostname">The hostname to connect to.</param>
|
|
public NodeMCUWS2812USBUpdateQueue(IDeviceUpdateTrigger updateTrigger, string hostname)
|
|
: base(updateTrigger)
|
|
{
|
|
this._hostname = hostname;
|
|
|
|
_sendDataAction = SendHttp;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="NodeMCUWS2812USBUpdateQueue"/> class.
|
|
/// If this constructor is used UDP updates are enabled.
|
|
/// </summary>
|
|
/// <param name="updateTrigger">The update trigger used by this queue.</param>
|
|
/// <param name="hostname">The hostname to connect to.</param>
|
|
/// <param name="udpPort">The port used by the UDP-connection.</param>
|
|
public NodeMCUWS2812USBUpdateQueue(IDeviceUpdateTrigger updateTrigger, string hostname, int udpPort)
|
|
: base(updateTrigger)
|
|
{
|
|
this._hostname = hostname;
|
|
|
|
_udpClient = new UdpClient();
|
|
EnableUdp(udpPort);
|
|
|
|
_sendDataAction = SendUdp;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
/// <inheritdoc />
|
|
protected override void OnStartup(object? sender, CustomUpdateData customData)
|
|
{
|
|
base.OnStartup(sender, customData);
|
|
|
|
ResetDevice();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override bool Update(in ReadOnlySpan<(object key, Color color)> dataSet)
|
|
{
|
|
try
|
|
{
|
|
foreach (IGrouping<int, ((int channel, int key), Color color)> channelData in dataSet.ToArray()
|
|
.Select(x => (((int channel, int key))x.key, x.color)).GroupBy(x => x.Item1.channel))
|
|
{
|
|
byte[] buffer = GetBuffer(channelData);
|
|
_sendDataAction(buffer);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
WS281XDeviceProvider.Instance.Throw(ex);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void SendHttp(byte[] buffer)
|
|
{
|
|
string data = Convert.ToBase64String(buffer);
|
|
lock (_httpClient) _httpClient.PostAsync(GetUrl("update"), new StringContent(data, Encoding.ASCII)).Wait();
|
|
}
|
|
|
|
private void SendUdp(byte[] buffer)
|
|
{
|
|
_udpClient?.Send(buffer, buffer.Length);
|
|
}
|
|
|
|
private byte[] GetBuffer(IGrouping<int, ((int channel, int key) identifier, Color color)> data)
|
|
{
|
|
int channel = data.Key;
|
|
byte[] buffer = _dataBuffer[channel];
|
|
|
|
buffer[0] = GetSequenceNumber(channel);
|
|
buffer[1] = (byte)channel;
|
|
int i = 2;
|
|
foreach ((byte _, byte r, byte g, byte b) in data.OrderBy(x => x.identifier.key)
|
|
.Select(x => x.color.GetRGBBytes()))
|
|
{
|
|
buffer[i++] = r;
|
|
buffer[i++] = g;
|
|
buffer[i++] = b;
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
internal IEnumerable<(int channel, int ledCount)> GetChannels()
|
|
{
|
|
string configString;
|
|
lock (_httpClient) configString = _httpClient.GetStringAsync(GetUrl("config")).Result;
|
|
|
|
configString = configString.Replace(" ", "")
|
|
.Replace("\r", "")
|
|
.Replace("\n", "");
|
|
|
|
//HACK DarthAffe 13.07.2020: Adding a JSON-Parser dependency just for this is not really worth it right now ...
|
|
MatchCollection channelMatches = Regex.Matches(configString, "\\{\"channel\":(?<channel>\\d+),\"leds\":(?<leds>\\d+)\\}");
|
|
foreach (Match channelMatch in channelMatches)
|
|
{
|
|
int channel = int.Parse(channelMatch.Groups["channel"].Value);
|
|
int leds = int.Parse(channelMatch.Groups["leds"].Value);
|
|
if (leds > 0)
|
|
{
|
|
_dataBuffer[channel] = new byte[(leds * 3) + 2];
|
|
_sequenceNumbers[channel] = 0;
|
|
yield return (channel, leds);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void ResetDevice()
|
|
{
|
|
lock (_httpClient) _httpClient.GetStringAsync(GetUrl("reset")).Wait();
|
|
}
|
|
|
|
private void EnableUdp(int port)
|
|
{
|
|
_httpClient.PostAsync(GetUrl("enableUDP"), new StringContent(port.ToString(), Encoding.UTF8, "application/json")).Result.Content.ReadAsStringAsync().Wait();
|
|
_udpClient?.Connect(_hostname, port);
|
|
}
|
|
|
|
private byte GetSequenceNumber(int channel)
|
|
{
|
|
byte sequenceNumber = (byte)Math.Max(1, (_sequenceNumbers[channel] + 1) % byte.MaxValue);
|
|
_sequenceNumbers[channel] = sequenceNumber;
|
|
return sequenceNumber;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Dispose()
|
|
{
|
|
lock (_httpClient)
|
|
{
|
|
base.Dispose();
|
|
|
|
_udpClient?.Dispose();
|
|
_udpClient = null;
|
|
|
|
ResetDevice();
|
|
_httpClient.Dispose();
|
|
}
|
|
}
|
|
|
|
private string GetUrl(string path) => $"http://{_hostname}/{path}";
|
|
|
|
#endregion
|
|
} |