Skip to content

Individual LED Control #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Kevsoft.WLED/Kevsoft.WLED.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Net.Http.Json" Version="6.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="System.Net.Http.Json" Version="9.0.0" />
<PackageReference Include="System.Text.Json" Version="9.0.0" />
</ItemGroup>

</Project>
12 changes: 12 additions & 0 deletions src/Kevsoft.WLED/LedsResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,16 @@ public sealed class LedsResponse
/// </summary>
[JsonPropertyName("maxseg")]
public byte MaximumSegments { get; set; }

/// <summary>
/// Preset number loaded on boot.
/// </summary>
[JsonPropertyName("bootps")]
public int BootupPreset { get; set; }

/// <summary>
/// Matrix configuration
/// </summary>
[JsonPropertyName("matrix")]
public MatrixResponse Matrix { get; set; } = null!;
}
12 changes: 12 additions & 0 deletions src/Kevsoft.WLED/MatrixResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Kevsoft.WLED;

public class MatrixResponse
{
/// <summary> The number of LEDs in the width of the matrix </summary>
[JsonPropertyName("w")]
public int Width { get; set; }

/// <summary> The number of LEDs in the Height of the matrix </summary>
[JsonPropertyName("h")]
public int Height { get; set; }
}
3 changes: 3 additions & 0 deletions src/Kevsoft.WLED/SegmentRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ public sealed class SegmentRequest
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? Mirror { get; set; }

[JsonPropertyName("i")]
public object[] IndividualLedControl { get; set; } = [];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this have a type?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a couple different things that can be in this list. Ends up being a mix of ints and strings, but there's not a pattern to how they are ordered - and Wled didn't respond to them all sent as strings im my testing. For what i set up - I'm ignoring the RGB option and going with just hex, but the hex string could be a list of 3 ints

just the Hex Values,
coordinate followed by hex color,
2 ints for start and end of a range followed by the hex color.



public static SegmentRequest From(SegmentResponse segmentResponse)
{
Expand Down
10 changes: 10 additions & 0 deletions src/Kevsoft.WLED/SingleLedRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Kevsoft.WLED;

public sealed class SingleLedRequest
{
/// <summary> The position of the LED in the segment </summary>
public int LedPosition { get; set; }

/// <summary> The color of the LED as HEX (e.g. FF0000 for red) </summary>
public string Color { get; set; } = string.Empty;
}
76 changes: 73 additions & 3 deletions src/Kevsoft.WLED/WLedClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public async Task<string[]> GetPalettes()
var message = await _client.GetAsync("json/pal");

message.EnsureSuccessStatusCode();

return (await message.Content.ReadFromJsonAsync<string[]>())!;
}

Expand All @@ -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);
Expand All @@ -82,4 +82,74 @@ public async Task Post(StateRequest request)
var result = await _client.PostAsync("/json/state", content);
result.EnsureSuccessStatusCode();
}
}

public async Task Post(List<SingleLedRequest> ledList)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could do with some tests around this Post method to prove out all the logic

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll Look into this. Something in this repository keeps tripping the "warning as an error" thing for venerable nugets on my computer, even though im not seeing that flag on the projects, so may need to nudge a few of those along the way.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think i got a test added now.

{
// Eliminate duplicate positions
ledList = ledList.GroupBy(x => x.LedPosition).Select(x => x.Last()).ToList();

List<object> 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<string, SingleLedRequest>? 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<List<int>> grouped = leds.Select(x => x.LedPosition).OrderBy(x => x)
.Aggregate(new List<List<int>> { new() },
(acc, curr) =>
{
if (!acc.Last().Any() || curr - acc.Last().Last() == 1)
acc.Last().Add(curr);
else
acc.Add([curr]);
return acc;
});

foreach (List<int> 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] }] });
}
}
9 changes: 7 additions & 2 deletions test/Kevsoft.WLED.Tests/JsonBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
23 changes: 23 additions & 0 deletions test/Kevsoft.WLED.Tests/WLedClientPostTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SingleLedRequest> 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()
{
Expand Down