diff --git a/src/Kevsoft.WLED/Kevsoft.WLED.csproj b/src/Kevsoft.WLED/Kevsoft.WLED.csproj index e8c171a..3898f26 100644 --- a/src/Kevsoft.WLED/Kevsoft.WLED.csproj +++ b/src/Kevsoft.WLED/Kevsoft.WLED.csproj @@ -23,8 +23,8 @@ - - + + diff --git a/src/Kevsoft.WLED/LedsResponse.cs b/src/Kevsoft.WLED/LedsResponse.cs index dd0c0eb..36e311d 100644 --- a/src/Kevsoft.WLED/LedsResponse.cs +++ b/src/Kevsoft.WLED/LedsResponse.cs @@ -37,4 +37,16 @@ public sealed class LedsResponse /// [JsonPropertyName("maxseg")] public byte MaximumSegments { get; set; } + + /// + /// Preset number loaded on boot. + /// + [JsonPropertyName("bootps")] + public int BootupPreset { get; set; } + + /// + /// Matrix configuration + /// + [JsonPropertyName("matrix")] + public MatrixResponse Matrix { get; set; } = null!; } \ No newline at end of file diff --git a/src/Kevsoft.WLED/MatrixResponse.cs b/src/Kevsoft.WLED/MatrixResponse.cs new file mode 100644 index 0000000..a227456 --- /dev/null +++ b/src/Kevsoft.WLED/MatrixResponse.cs @@ -0,0 +1,12 @@ +namespace Kevsoft.WLED; + +public class MatrixResponse +{ + /// The number of LEDs in the width of the matrix + [JsonPropertyName("w")] + public int Width { get; set; } + + /// The number of LEDs in the Height of the matrix + [JsonPropertyName("h")] + public int Height { get; set; } +} \ No newline at end of file diff --git a/src/Kevsoft.WLED/SegmentRequest.cs b/src/Kevsoft.WLED/SegmentRequest.cs index 4dcd34f..d1486b7 100644 --- a/src/Kevsoft.WLED/SegmentRequest.cs +++ b/src/Kevsoft.WLED/SegmentRequest.cs @@ -87,6 +87,9 @@ public sealed class SegmentRequest [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? Mirror { get; set; } + [JsonPropertyName("i")] + public object[] IndividualLedControl { get; set; } = []; + public static SegmentRequest From(SegmentResponse segmentResponse) { diff --git a/src/Kevsoft.WLED/SingleLedRequest.cs b/src/Kevsoft.WLED/SingleLedRequest.cs new file mode 100644 index 0000000..b04e807 --- /dev/null +++ b/src/Kevsoft.WLED/SingleLedRequest.cs @@ -0,0 +1,10 @@ +namespace Kevsoft.WLED; + +public sealed class SingleLedRequest +{ + /// The position of the LED in the segment + public int LedPosition { get; set; } + + /// The color of the LED as HEX (e.g. FF0000 for red) + public string Color { get; set; } = string.Empty; +} diff --git a/src/Kevsoft.WLED/WLedClient.cs b/src/Kevsoft.WLED/WLedClient.cs index ccddbba..4e7618c 100644 --- a/src/Kevsoft.WLED/WLedClient.cs +++ b/src/Kevsoft.WLED/WLedClient.cs @@ -61,7 +61,7 @@ public async Task GetPalettes() var message = await _client.GetAsync("json/pal"); message.EnsureSuccessStatusCode(); - + return (await message.Content.ReadFromJsonAsync())!; } @@ -73,7 +73,7 @@ public async Task Post(WLedRootRequest request) var result = await _client.PostAsync("/json", content); result.EnsureSuccessStatusCode(); } - + public async Task Post(StateRequest request) { var stateString = JsonSerializer.Serialize(request); @@ -82,4 +82,74 @@ public async Task Post(StateRequest request) var result = await _client.PostAsync("/json/state", content); result.EnsureSuccessStatusCode(); } -} \ No newline at end of file + + public async Task Post(List ledList) + { + // Eliminate duplicate positions + ledList = ledList.GroupBy(x => x.LedPosition).Select(x => x.Last()).ToList(); + + List list = []; + int counter = 0; + + //Attempt to group colors together to reduce the number of packets sent as there is a 256 color at a time limit + foreach (IGrouping? leds in ledList.GroupBy(x => x.Color)) + { + if (counter >=255) + { + await Post(new StateRequest { On = true, Segments = [new() { Id = 0, IndividualLedControl = [.. list] }] }); + list = []; + counter = 0; + } + // If there is only one LED in the group, add it to the list + if (leds.Count() == 1) + { + list.Add(leds.First().LedPosition); + list.Add(leds.First().Color); + counter++; + continue; + } + + // If there are multiple LEDs in the group, find the sequential LED's and group them up + // to make the next step easier + List> grouped = leds.Select(x => x.LedPosition).OrderBy(x => x) + .Aggregate(new List> { new() }, + (acc, curr) => + { + if (!acc.Last().Any() || curr - acc.Last().Last() == 1) + acc.Last().Add(curr); + else + acc.Add([curr]); + return acc; + }); + + foreach (List group in grouped) + { + //Another round of sending the colors if we are at the limit + if (counter >= 255) + { + await Post(new StateRequest { On = true, Segments = [new() { Id = 0, IndividualLedControl = [.. list] }] }); + list = []; + counter = 0; + } + + // If there is only one LED in the group, add it to the list + if (group.Count == 1) + { + list.Add(group.First()); + list.Add(leds.First().Color); + counter++; + continue; + } + + //And if there are multiple LED's, Add them to the list, but when displaying max + //is not displayed so add 1 to the max to get it to display properly + list.Add(group.Min()); + list.Add(group.Max() + 1); + list.Add(leds.First().Color); + counter++; + } + } + //And finally send the last packet + await Post(new StateRequest { On = true, Segments = [new() { Id = 0, IndividualLedControl = [.. list] }] }); + } +} diff --git a/test/Kevsoft.WLED.Tests/JsonBuilder.cs b/test/Kevsoft.WLED.Tests/JsonBuilder.cs index c83a1fd..b377aa8 100644 --- a/test/Kevsoft.WLED.Tests/JsonBuilder.cs +++ b/test/Kevsoft.WLED.Tests/JsonBuilder.cs @@ -60,8 +60,13 @@ public static string CreateInformationJson(InformationResponse information) ""lc"": {information.Leds.LightCapabilities}, ""pwr"": {information.Leds.PowerUsage}, ""maxpwr"": {information.Leds.MaximumPower}, - ""maxseg"": {information.Leds.MaximumSegments} - }}, + ""maxseg"": {information.Leds.MaximumSegments}, + ""bootps"": {information.Leds.BootupPreset}, + ""matrix"": {{ + ""w"": {information.Leds.Matrix.Width}, + ""h"": {information.Leds.Matrix.Height} + }} + }}, ""str"": {information.ToggleSendReceive.ToString().ToLower()}, ""name"": ""{information.Name}"", ""udpport"": {information.UdpPort}, diff --git a/test/Kevsoft.WLED.Tests/WLedClientPostTests.cs b/test/Kevsoft.WLED.Tests/WLedClientPostTests.cs index 274bab5..a80ef3d 100644 --- a/test/Kevsoft.WLED.Tests/WLedClientPostTests.cs +++ b/test/Kevsoft.WLED.Tests/WLedClientPostTests.cs @@ -38,6 +38,29 @@ public async Task PostEmptyStateRequestData() json.RootElement.EnumerateObject().Should().HaveCount(0); } + [Fact] + public async Task PostEmptySingleLedRequestData() + { + var mockHttpMessageHandler = new MockHttpMessageHandler(); + var baseUri = $"http://{Guid.NewGuid():N}.com"; + mockHttpMessageHandler.AppendResponse($"{baseUri}/json/state"); + var client = new WLedClient(mockHttpMessageHandler, baseUri); + + List request = []; + await client.Post(request); + + var (uri, body) = mockHttpMessageHandler.CapturedRequests.Single(); + uri.Should().Be($"{baseUri}/json/state"); + var json = JsonDocument.Parse(body!); + // Expected Request: + // {"on":true,"seg":[{"id":0,"i":[]}]} + + json.RootElement.EnumerateObject().Should().HaveCount(2); + json.RootElement.GetProperty("seg").EnumerateArray().Should().HaveCount(1); + json.RootElement.GetProperty("seg")[0].GetProperty("id").GetInt32().Should().Be(0); + json.RootElement.GetProperty("seg")[0].GetProperty("i").EnumerateArray().Should().HaveCount(0); + } + [Fact] public async Task PostFullWLedRootResponse() {