Skip to content

Commit 71cadf0

Browse files
authored
Upgrade WLED.NET JSON API coverage and ergonomics (#15)
* Add roadmap plans for full WLED JSON API coverage * Plan 0: multi-target net8/9/10 and modernise toolchain * Plan 1: core value types and enums * Plan 2: command-value types and fluent state/segment builders * Plan 3: complete the state object with typed fields and write-only commands * Plan 4: complete the segment object with colors, effect params and 2D fields * Plan 5: complete info object and add si/net/live read endpoints * Plan 6: presets API with list, apply, save and delete * Plan 7: playlists API with typed entries and parallel-array transform * Plan 8: per-segment individual LED control with auto-chunking * Plan 9: effect metadata parsing from /json/fxdata * Plan 10: config API with safe partial writes and node discovery * Plan 11: client ergonomics, typed exceptions, cancellation and DI - Add intent methods (TurnOn/TurnOff/Toggle/SetBrightness/SetColor/SetEffect/SetPalette/Reboot) - Add CancellationToken to every client method - Add typed exception hierarchy (WledException + connection/response/version) - Add HttpClient constructor and Kevsoft.WLED.DependencyInjection AddWledClient - Add intent, exception and DI tests * Plan 11: refresh README with feature matrix, add CHANGELOG and modern sample - Rewrite README around intent methods, fluent builders, DI and error handling - Add a supported-features capability matrix and document target frameworks - Add CHANGELOG.md (Keep a Changelog format) - Rewrite BasicConsole sample to showcase the ergonomic API * Add WLED JSON API coverage review * Add plan 12: usability and correctness improvements from review * Widen transition (transition/tt) from byte to ushort to support 0-65535 range * Add ranged-random effect/palette selector (Selector.RandomInRange, 'from~tor') * Widen ColorTemperature Kelvin range to 1000-20000 and add KelvinUnchecked escape hatch * Fix no-id intent methods to target selected segments via seg object form Model the state 'seg' field as a union (SegmentPayload) that serializes as either an object (selected segments) or an array (id-targeted), matching the WLED API. No-id SetColor/SetEffect/SetPalette now emit the object form instead of an array that WLED would infer as id:0. * Add typed config fields for identity mDNS, MQTT and boot defaults Model id.mdns, if.mqtt (en/broker/port/user/cid) and def (on/bri/ps) as typed properties while keeping JsonExtensionData as an escape hatch for everything else. * Update README, CHANGELOG and sample for post-review API changes * Add plan 13 for device ergonomics and agent guidance * Add AGENTS.md and typed effect/palette catalogs Add root AGENTS.md with project conventions, commands and gotchas. Add EffectCatalog/PaletteCatalog with id/name lookup, reserved-entry filtering (RSVD/-), and SetEffect/SetPalette catalog-entry overloads. * Add SelectedSegments fluent update for selected-segment object form StateUpdate.SelectedSegments(...) targets the selected segments via the seg object form and accumulates across calls. Mixing it with Segment(id, ...) in one update throws, since WLED's seg field is one form or the other. * Add cohesive WLedDevice snapshot via GetDevice() GetDevice() issues a single GET /json and exposes state, info and typed effect/palette catalogs as one snapshot, with per-segment effect/palette resolution and derived helpers (SelectedSegments, ActiveSegments, SupportsWhiteChannel, etc). Effect metadata is opt-in to avoid extra requests. * Make effect metadata actionable Add lookup helpers (FindById/FindByName/Try*) over effect metadata collections, plus SegmentUpdate.Effect(EffectMetadata) and ApplyEffectDefaults(EffectMetadata) to set speed/intensity/custom sliders from metadata defaults (c3 clamped to 0-31). * Add strong id and range value types Introduce SegmentId, EffectId, PaletteId, PresetId, PlaylistId, LedMapId, SegmentBounds and MatrixBounds readonly structs with range validation, equality and netstandard2.0-safe hashing. Wire SegmentBounds and MatrixBounds into SegmentUpdate.Range/Range2D and LedMapId into StateUpdate.LoadLedMap as non-ambiguous overloads. * Add fluent config update builder Introduce ConfigUpdate and an UpdateConfig(Action<ConfigUpdate>) client overload exposing Identity, Mqtt and BootDefaults helpers. Only touched sections and fields are serialised, reusing the existing network opt-in guard. * Document Plan 13 ergonomics features Add README sections and feature-matrix rows for the device snapshot, catalogs, selected-segment updates, strong value types and the fluent config builder; expand the CHANGELOG and BasicConsole sample to match. * Remove outdated gotchas from AGENTS.md for clarity and conciseness * Fix Docker build for DependencyInjection project Copy the Kevsoft.WLED.DependencyInjection csproj and sources so dotnet restore (which reads the solution) and the build/pack stages succeed. Build the DI project (transitively building the main library) instead of globbing multiple csproj into a single dotnet build, and pack each packable library explicitly. Also align FROM..AS keyword casing.
1 parent 03c81a8 commit 71cadf0

137 files changed

Lines changed: 10058 additions & 135 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# AGENTS.md — WLED.NET
2+
3+
Guidance for coding agents (and humans) working in this repository. Read this before
4+
making changes; it captures the project's purpose, conventions, commands and gotchas so a
5+
change can be made safely without prior conversation history.
6+
7+
## Project purpose
8+
9+
**WLED.NET** is a strongly typed, hard-to-misuse .NET client for the
10+
[WLED JSON API](https://kno.wled.ge/interfaces/json-api/). It turns the device's loosely
11+
typed JSON (`/json`, `/json/state`, `/json/info`, `/json/eff`, `/json/pal`, `/json/cfg`,
12+
`/json/fxdata`, `/json/nodes`, `presets.json`, …) into expressive C# types.
13+
14+
## Core design principle
15+
16+
> Model things well so they're easy to use. Make it impossible to send the wrong
17+
> information by how we model the library.
18+
19+
Concretely:
20+
21+
- **Types over primitives.** Prefer value types, enums and command types over raw
22+
`int`/`byte`/magic strings (`RgbColor`, `SegmentColors`, `Selector`, `ByteAdjust`,
23+
`Toggleable`, `LightCapability` `[Flags]`, the `*Id`/`*Bounds` value types, …).
24+
- **Validation at construction.** Where WLED documents a range (e.g. `c3` is 0–31, `bri`
25+
is 0–255, ledmap is 0–9), the constructing type guards it so an out-of-range value
26+
throws *before* it hits the wire.
27+
- **Read model ≠ write model.** Responses are immutable/total; requests/builders expose
28+
only what is settable.
29+
- **Builders, not nullable bags.** High-level fluent builders (`StateUpdate`,
30+
`SegmentUpdate`, `PlaylistBuilder`, `ConfigUpdate`) sit on top of the raw DTOs.
31+
32+
## Repository conventions
33+
34+
- **Dual DTO layer.** Each JSON object is a pair: an immutable `XResponse` (all fields,
35+
non-null) and a sparse mutable `XRequest` (nullable fields with
36+
`[JsonIgnore(WhenWritingNull)]`), usually with a static `Request.From(Response)` factory
37+
and an implicit operator. The wire format never leaks into the public happy path.
38+
- **`[JsonPropertyName]` always carries the raw WLED key**; the C# member uses a
39+
descriptive .NET name (e.g. `EffectId``"fx"`).
40+
- **Every new endpoint** adds a method to `IWLedClient` + `WLedClient`, with GET/POST
41+
tests in `test/Kevsoft.WLED.Tests` (extend `JsonBuilder` and `MockHttpMessageHandler`).
42+
- **Custom `JsonConverter`s** carry the "impossible to misuse" types across the wire and
43+
are unit-tested in both directions against realistic WLED payloads.
44+
- **Unknown keys round-trip.** Config sections use `[JsonExtensionData]` (`Unknown`) so a
45+
read-modify-write cycle never drops firmware-specific fields.
46+
47+
## Target frameworks & netstandard2.0 constraints
48+
49+
- The library multi-targets `netstandard2.0;net8.0;net9.0;net10.0`
50+
(see `Directory.Build.props`). Tests run on `net8.0;net9.0;net10.0`.
51+
- Because of `netstandard2.0`, **do not** use:
52+
- `Math.Clamp` — clamp manually.
53+
- `System.HashCode` — implement `GetHashCode()` with `unchecked` arithmetic.
54+
- newer async/Span API shapes that aren't available there.
55+
- Prefer `readonly struct` + `IEquatable<T>` for small value types.
56+
57+
## Commands
58+
59+
```pwsh
60+
dotnet build WLED.NET.sln -c Release
61+
dotnet test WLED.NET.sln -c Release
62+
```
63+
64+
## Breaking-change stance
65+
66+
- No `[Obsolete]` compatibility shims unless explicitly requested.
67+
- Record user-facing and breaking changes in `CHANGELOG.md`.
68+
- Keep raw `XResponse`/`XRequest` DTOs available as escape hatches even when adding
69+
ergonomic builders/value types on top.
70+
71+
## Docs & sample expectations (acceptance criteria)
72+
73+
A feature isn't done when it compiles and tests pass. Also:
74+
75+
1. Update the root `README.md` feature matrix and add/refresh a short usage snippet.
76+
2. Keep `samples/BasicConsole` exemplary — demonstrate the *ergonomic* path (builders,
77+
intent methods, catalogs, snapshots), not raw DTOs.
78+
3. Update `CHANGELOG.md`.
79+
80+
## Reference material
81+
82+
- WLED JSON API docs: <https://kno.wled.ge/interfaces/json-api/>
83+
- This repo's `plans/` folder contains the staged roadmap (Plans 0–13) and the conventions
84+
every plan must uphold.

CHANGELOG.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Changelog
2+
3+
All notable changes to this project are documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
This release is a substantial, breaking overhaul that reworks the library to cover the full
11+
WLED JSON API with a strongly-typed, hard-to-misuse design. Legacy members were removed
12+
rather than deprecated, so consuming code must be updated.
13+
14+
### Added
15+
16+
- **Multi-targeting** for `net8.0`, `net9.0`, `net10.0` and `netstandard2.0`.
17+
- **Strong value types and enums** for colours (`RgbColor`, `RgbwColor`, `Color`),
18+
brightness/relative adjustments (`ByteAdjust`), toggles (`Toggleable`), selectors and more.
19+
- **Fluent builders** for state updates (`UpdateState`), segments, playlists and individual LEDs.
20+
- **Intent methods**: `TurnOn`, `TurnOff`, `Toggle`, `SetBrightness`, `SetColor` (RGB/RGBW),
21+
`SetEffect`, `SetPalette` and `Reboot`.
22+
- **Presets**: read, apply, save and delete (`GetPresets`, `ApplyPreset`, `SavePreset`, `DeletePreset`).
23+
- **Playlists**: read, start and save (`GetPlaylists`, `StartPlaylist`, `SavePlaylist`).
24+
- **Individual LED control** with automatic, sequential request chunking (`SetIndividualLeds`).
25+
- **Effect metadata** parsing from `/json/fxdata` (`GetEffectMetadata`).
26+
- **Node discovery** via `/json/nodes` (`GetNodes`).
27+
- **Device configuration** read and safe partial writes via `/json/cfg`
28+
(`GetConfig`, `UpdateConfig`); network/access-point changes require explicit opt-in.
29+
- **Typed exception hierarchy**: `WledException`, `WledConnectionException`,
30+
`WledResponseException` (with `StatusCode`/`Body`) and `WledUnsupportedVersionException`.
31+
- **`CancellationToken`** support on every asynchronous method.
32+
- **Dependency-injection integration** in a new `WLED.DependencyInjection` package via
33+
`services.AddWledClient(...)`, backed by `IHttpClientFactory`.
34+
- A new `WLedClient(HttpClient)` constructor for DI / `IHttpClientFactory` scenarios.
35+
36+
### Changed
37+
38+
- Requests and responses are now modelled as separate immutable response types and mutable
39+
request types, preventing accidental round-tripping of read-only fields.
40+
- Posting state is now done through intent methods or `UpdateState(...)` rather than mutating
41+
and re-posting a response object.
42+
- **`SetColor`/`SetEffect`/`SetPalette` with no `segmentId` now target the *selected* segments**
43+
(the WLED `"seg":{…}` object form) instead of segment 0. The state `seg` field is modelled as
44+
a `SegmentPayload` union that serialises as either an object (selected segments) or an array
45+
(id-targeted).
46+
- **Transition values (`transition`/`tt`) widened from `byte` to `ushort`** to support the
47+
documented `0–65535` range (~109 minutes) instead of clamping at 25.5 s.
48+
- **`ColorTemperature.Kelvin` range widened to `1000–20000 K`** to match the docs' forward-
49+
compatible guidance, with a new `ColorTemperature.KelvinUnchecked(int)` escape hatch for
50+
values outside that range.
51+
52+
### Added
53+
54+
- **Ranged-random effect/palette selection** via `Selector.RandomInRange(from, to)`
55+
(the WLED `"from~tor"` token).
56+
- **Typed device-configuration fields** for `id.mdns`, `if.mqtt` (`en`/`broker`/`port`/`user`/`cid`)
57+
and `def` (`on`/`bri`/`ps`), while preserving all other keys through `JsonExtensionData`.
58+
- **Device snapshot read model** (`GetDevice`) returning a queryable `WLedDevice`/`WLedDeviceSegment`
59+
graph from a single `GET /json`, resolving each segment's effect and palette and optionally
60+
fetching effect metadata via `DeviceSnapshotOptions.IncludeEffectMetadata`.
61+
- **Effect & palette catalogs** (`GetEffectCatalog`, `GetPaletteCatalog`) with `FindById`/`FindByName`
62+
(and `Try*`) lookups, an `AvailableOnly` view that hides reserved `RSVD`/`-` slots, and
63+
`SetEffect`/`SetPalette` overloads that accept catalog entries.
64+
- **Selected-segment fluent updates** via `StateUpdate.SelectedSegments(...)` (the WLED `"seg":{…}`
65+
object form); mixing selected and id-targeted segments in one update throws.
66+
- **Actionable effect metadata**: collection lookups over `IReadOnlyList<EffectMetadata>`, plus
67+
`SegmentUpdate.Effect(EffectMetadata)` and `ApplyEffectDefaults(EffectMetadata)` to seed
68+
speed/intensity/custom sliders from metadata defaults.
69+
- **Strong id and range value types**: `SegmentId`, `EffectId`, `PaletteId`, `PresetId`,
70+
`PlaylistId`, `LedMapId`, `SegmentBounds` and `MatrixBounds`, with range validation and
71+
overloads on `SegmentUpdate.Range`/`Range2D` and `StateUpdate.LoadLedMap`.
72+
- **Fluent configuration updates** via `UpdateConfig(Action<ConfigUpdate>)` with `Identity`,
73+
`Mqtt` and `BootDefaults` helpers that emit only the sections and fields you touch.
74+
75+
### Removed
76+
77+
- Legacy members were removed outright (no `[Obsolete]` shims). Update to the new API surface.

Directory.Build.props

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
<Copyright>Copyright 2020 Kevsoft</Copyright>
55
<Authors>Kevin Smith</Authors>
66
<LangVersion>latest</LangVersion>
7+
<LibraryTargetFrameworks>netstandard2.0;net8.0;net9.0;net10.0</LibraryTargetFrameworks>
8+
<TestTargetFrameworks>net8.0;net9.0;net10.0</TestTargetFrameworks>
9+
<SampleTargetFramework>net10.0</SampleTargetFramework>
710
<nullable>enable</nullable>
811
<ImplicitUsings>enable</ImplicitUsings>
912
<GenerateDocumentationFile>true</GenerateDocumentationFile>

Dockerfile

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,38 @@
11
ARG VERSION=0.0.0
2-
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS restore
2+
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS restore
33
WORKDIR /
44

55
COPY ./nuget.config .
66
COPY ./*.sln .
77
COPY ./Directory.Build.props .
88
COPY ./src/Kevsoft.WLED/*.csproj ./src/Kevsoft.WLED/
9+
COPY ./src/Kevsoft.WLED.DependencyInjection/*.csproj ./src/Kevsoft.WLED.DependencyInjection/
910
COPY ./test/Kevsoft.WLED.Tests/*.csproj ./test/Kevsoft.WLED.Tests/
1011
COPY ./samples/BasicConsole/*.csproj ./samples/BasicConsole/
1112
RUN dotnet restore
1213

13-
FROM restore as build
14+
FROM restore AS build
1415
ARG VERSION
1516
COPY ./icon.png .
1617
COPY ./src/Kevsoft.WLED/ ./src/Kevsoft.WLED/
17-
RUN dotnet build ./src/**/*.csproj --configuration Release -p:Version=${VERSION} --no-restore
18+
COPY ./src/Kevsoft.WLED.DependencyInjection/ ./src/Kevsoft.WLED.DependencyInjection/
19+
RUN dotnet build ./src/Kevsoft.WLED.DependencyInjection/Kevsoft.WLED.DependencyInjection.csproj --configuration Release -p:Version=${VERSION} --no-restore
1820

19-
FROM build as build-tests
21+
FROM build AS build-tests
2022
ARG VERSION
2123
COPY ./test/Kevsoft.WLED.Tests/ ./test/Kevsoft.WLED.Tests/
2224
RUN dotnet build ./test/**/*.csproj --configuration Release -p:Version=${VERSION} --no-restore
2325

24-
FROM build-tests as test
26+
FROM build-tests AS test
2527
ENTRYPOINT ["dotnet", "test", "./test/Kevsoft.WLED.Tests/Kevsoft.WLED.Tests.csproj", "--configuration", "Release", "--no-restore", "--no-build"]
2628
CMD ["--logger" , "trx", "--results-directory", "./TestResults"]
2729

28-
FROM build as pack
30+
FROM build AS pack
2931
ARG VERSION
30-
RUN dotnet pack --configuration Release -p:Version=${VERSION} --no-build
32+
RUN dotnet pack ./src/Kevsoft.WLED/Kevsoft.WLED.csproj --configuration Release -p:Version=${VERSION} --no-build
33+
RUN dotnet pack ./src/Kevsoft.WLED.DependencyInjection/Kevsoft.WLED.DependencyInjection.csproj --configuration Release -p:Version=${VERSION} --no-build
3134

32-
FROM pack as push
35+
FROM pack AS push
3336
RUN env
3437

3538
ENTRYPOINT ["dotnet", "nuget", "push", "./src/Kevsoft.WLED/bin/Release/*.nupkg", "--source", "NuGet.org"]

0 commit comments

Comments
 (0)