Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
cdbd91d
Add roadmap plans for full WLED JSON API coverage
kevbite May 29, 2026
bd51e94
Plan 0: multi-target net8/9/10 and modernise toolchain
kevbite May 29, 2026
60d5fe6
Plan 1: core value types and enums
kevbite May 29, 2026
30d605c
Plan 2: command-value types and fluent state/segment builders
kevbite May 29, 2026
1913872
Plan 3: complete the state object with typed fields and write-only co…
kevbite May 29, 2026
f4f8b6b
Plan 4: complete the segment object with colors, effect params and 2D…
kevbite May 29, 2026
47c2330
Plan 5: complete info object and add si/net/live read endpoints
kevbite May 29, 2026
4d1bb36
Plan 6: presets API with list, apply, save and delete
kevbite May 29, 2026
69537cd
Plan 7: playlists API with typed entries and parallel-array transform
kevbite May 29, 2026
351af85
Plan 8: per-segment individual LED control with auto-chunking
kevbite May 29, 2026
c0f364c
Plan 9: effect metadata parsing from /json/fxdata
kevbite May 29, 2026
c819658
Plan 10: config API with safe partial writes and node discovery
kevbite May 29, 2026
cf30b9a
Plan 11: client ergonomics, typed exceptions, cancellation and DI
kevbite May 29, 2026
1ad3f77
Plan 11: refresh README with feature matrix, add CHANGELOG and modern…
kevbite May 29, 2026
30ed34f
Add WLED JSON API coverage review
kevbite May 30, 2026
760e538
Add plan 12: usability and correctness improvements from review
kevbite May 30, 2026
09a2ee8
Widen transition (transition/tt) from byte to ushort to support 0-655…
kevbite May 30, 2026
ba0ed71
Add ranged-random effect/palette selector (Selector.RandomInRange, 'f…
kevbite May 30, 2026
fa4bf07
Widen ColorTemperature Kelvin range to 1000-20000 and add KelvinUnche…
kevbite May 30, 2026
71dc741
Fix no-id intent methods to target selected segments via seg object form
kevbite May 30, 2026
48cc290
Add typed config fields for identity mDNS, MQTT and boot defaults
kevbite May 30, 2026
1e0c70e
Update README, CHANGELOG and sample for post-review API changes
kevbite May 30, 2026
2bb843c
Add plan 13 for device ergonomics and agent guidance
kevbite May 30, 2026
b1cf203
Add AGENTS.md and typed effect/palette catalogs
kevbite May 30, 2026
15cc7cf
Add SelectedSegments fluent update for selected-segment object form
kevbite May 30, 2026
0c5ee6d
Add cohesive WLedDevice snapshot via GetDevice()
kevbite May 30, 2026
8e7f8bb
Make effect metadata actionable
kevbite May 30, 2026
cbd4df0
Add strong id and range value types
kevbite May 30, 2026
6ce18d3
Add fluent config update builder
kevbite May 30, 2026
60823be
Document Plan 13 ergonomics features
kevbite May 30, 2026
3ddbdb9
Remove outdated gotchas from AGENTS.md for clarity and conciseness
kevbite May 30, 2026
b7466d7
Fix Docker build for DependencyInjection project
kevbite May 30, 2026
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
84 changes: 84 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# AGENTS.md — WLED.NET

Guidance for coding agents (and humans) working in this repository. Read this before
making changes; it captures the project's purpose, conventions, commands and gotchas so a
change can be made safely without prior conversation history.

## Project purpose

**WLED.NET** is a strongly typed, hard-to-misuse .NET client for the
[WLED JSON API](https://kno.wled.ge/interfaces/json-api/). It turns the device's loosely
typed JSON (`/json`, `/json/state`, `/json/info`, `/json/eff`, `/json/pal`, `/json/cfg`,
`/json/fxdata`, `/json/nodes`, `presets.json`, …) into expressive C# types.

## Core design principle

> Model things well so they're easy to use. Make it impossible to send the wrong
> information by how we model the library.

Concretely:

- **Types over primitives.** Prefer value types, enums and command types over raw
`int`/`byte`/magic strings (`RgbColor`, `SegmentColors`, `Selector`, `ByteAdjust`,
`Toggleable`, `LightCapability` `[Flags]`, the `*Id`/`*Bounds` value types, …).
- **Validation at construction.** Where WLED documents a range (e.g. `c3` is 0–31, `bri`
is 0–255, ledmap is 0–9), the constructing type guards it so an out-of-range value
throws *before* it hits the wire.
- **Read model ≠ write model.** Responses are immutable/total; requests/builders expose
only what is settable.
- **Builders, not nullable bags.** High-level fluent builders (`StateUpdate`,
`SegmentUpdate`, `PlaylistBuilder`, `ConfigUpdate`) sit on top of the raw DTOs.

## Repository conventions

- **Dual DTO layer.** Each JSON object is a pair: an immutable `XResponse` (all fields,
non-null) and a sparse mutable `XRequest` (nullable fields with
`[JsonIgnore(WhenWritingNull)]`), usually with a static `Request.From(Response)` factory
and an implicit operator. The wire format never leaks into the public happy path.
- **`[JsonPropertyName]` always carries the raw WLED key**; the C# member uses a
descriptive .NET name (e.g. `EffectId` → `"fx"`).
- **Every new endpoint** adds a method to `IWLedClient` + `WLedClient`, with GET/POST
tests in `test/Kevsoft.WLED.Tests` (extend `JsonBuilder` and `MockHttpMessageHandler`).
- **Custom `JsonConverter`s** carry the "impossible to misuse" types across the wire and
are unit-tested in both directions against realistic WLED payloads.
- **Unknown keys round-trip.** Config sections use `[JsonExtensionData]` (`Unknown`) so a
read-modify-write cycle never drops firmware-specific fields.

## Target frameworks & netstandard2.0 constraints

- The library multi-targets `netstandard2.0;net8.0;net9.0;net10.0`
(see `Directory.Build.props`). Tests run on `net8.0;net9.0;net10.0`.
- Because of `netstandard2.0`, **do not** use:
- `Math.Clamp` — clamp manually.
- `System.HashCode` — implement `GetHashCode()` with `unchecked` arithmetic.
- newer async/Span API shapes that aren't available there.
- Prefer `readonly struct` + `IEquatable<T>` for small value types.

## Commands

```pwsh
dotnet build WLED.NET.sln -c Release
dotnet test WLED.NET.sln -c Release
```

## Breaking-change stance

- No `[Obsolete]` compatibility shims unless explicitly requested.
- Record user-facing and breaking changes in `CHANGELOG.md`.
- Keep raw `XResponse`/`XRequest` DTOs available as escape hatches even when adding
ergonomic builders/value types on top.

## Docs & sample expectations (acceptance criteria)

A feature isn't done when it compiles and tests pass. Also:

1. Update the root `README.md` feature matrix and add/refresh a short usage snippet.
2. Keep `samples/BasicConsole` exemplary — demonstrate the *ergonomic* path (builders,
intent methods, catalogs, snapshots), not raw DTOs.
3. Update `CHANGELOG.md`.

## Reference material

- WLED JSON API docs: <https://kno.wled.ge/interfaces/json-api/>
- This repo's `plans/` folder contains the staged roadmap (Plans 0–13) and the conventions
every plan must uphold.
77 changes: 77 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

This release is a substantial, breaking overhaul that reworks the library to cover the full
WLED JSON API with a strongly-typed, hard-to-misuse design. Legacy members were removed
rather than deprecated, so consuming code must be updated.

### Added

- **Multi-targeting** for `net8.0`, `net9.0`, `net10.0` and `netstandard2.0`.
- **Strong value types and enums** for colours (`RgbColor`, `RgbwColor`, `Color`),
brightness/relative adjustments (`ByteAdjust`), toggles (`Toggleable`), selectors and more.
- **Fluent builders** for state updates (`UpdateState`), segments, playlists and individual LEDs.
- **Intent methods**: `TurnOn`, `TurnOff`, `Toggle`, `SetBrightness`, `SetColor` (RGB/RGBW),
`SetEffect`, `SetPalette` and `Reboot`.
- **Presets**: read, apply, save and delete (`GetPresets`, `ApplyPreset`, `SavePreset`, `DeletePreset`).
- **Playlists**: read, start and save (`GetPlaylists`, `StartPlaylist`, `SavePlaylist`).
- **Individual LED control** with automatic, sequential request chunking (`SetIndividualLeds`).
- **Effect metadata** parsing from `/json/fxdata` (`GetEffectMetadata`).
- **Node discovery** via `/json/nodes` (`GetNodes`).
- **Device configuration** read and safe partial writes via `/json/cfg`
(`GetConfig`, `UpdateConfig`); network/access-point changes require explicit opt-in.
- **Typed exception hierarchy**: `WledException`, `WledConnectionException`,
`WledResponseException` (with `StatusCode`/`Body`) and `WledUnsupportedVersionException`.
- **`CancellationToken`** support on every asynchronous method.
- **Dependency-injection integration** in a new `WLED.DependencyInjection` package via
`services.AddWledClient(...)`, backed by `IHttpClientFactory`.
- A new `WLedClient(HttpClient)` constructor for DI / `IHttpClientFactory` scenarios.

### Changed

- Requests and responses are now modelled as separate immutable response types and mutable
request types, preventing accidental round-tripping of read-only fields.
- Posting state is now done through intent methods or `UpdateState(...)` rather than mutating
and re-posting a response object.
- **`SetColor`/`SetEffect`/`SetPalette` with no `segmentId` now target the *selected* segments**
(the WLED `"seg":{…}` object form) instead of segment 0. The state `seg` field is modelled as
a `SegmentPayload` union that serialises as either an object (selected segments) or an array
(id-targeted).
- **Transition values (`transition`/`tt`) widened from `byte` to `ushort`** to support the
documented `0–65535` range (~109 minutes) instead of clamping at 25.5 s.
- **`ColorTemperature.Kelvin` range widened to `1000–20000 K`** to match the docs' forward-
compatible guidance, with a new `ColorTemperature.KelvinUnchecked(int)` escape hatch for
values outside that range.

### Added

- **Ranged-random effect/palette selection** via `Selector.RandomInRange(from, to)`
(the WLED `"from~tor"` token).
- **Typed device-configuration fields** for `id.mdns`, `if.mqtt` (`en`/`broker`/`port`/`user`/`cid`)
and `def` (`on`/`bri`/`ps`), while preserving all other keys through `JsonExtensionData`.
- **Device snapshot read model** (`GetDevice`) returning a queryable `WLedDevice`/`WLedDeviceSegment`
graph from a single `GET /json`, resolving each segment's effect and palette and optionally
fetching effect metadata via `DeviceSnapshotOptions.IncludeEffectMetadata`.
- **Effect & palette catalogs** (`GetEffectCatalog`, `GetPaletteCatalog`) with `FindById`/`FindByName`
(and `Try*`) lookups, an `AvailableOnly` view that hides reserved `RSVD`/`-` slots, and
`SetEffect`/`SetPalette` overloads that accept catalog entries.
- **Selected-segment fluent updates** via `StateUpdate.SelectedSegments(...)` (the WLED `"seg":{…}`
object form); mixing selected and id-targeted segments in one update throws.
- **Actionable effect metadata**: collection lookups over `IReadOnlyList<EffectMetadata>`, plus
`SegmentUpdate.Effect(EffectMetadata)` and `ApplyEffectDefaults(EffectMetadata)` to seed
speed/intensity/custom sliders from metadata defaults.
- **Strong id and range value types**: `SegmentId`, `EffectId`, `PaletteId`, `PresetId`,
`PlaylistId`, `LedMapId`, `SegmentBounds` and `MatrixBounds`, with range validation and
overloads on `SegmentUpdate.Range`/`Range2D` and `StateUpdate.LoadLedMap`.
- **Fluent configuration updates** via `UpdateConfig(Action<ConfigUpdate>)` with `Identity`,
`Mqtt` and `BootDefaults` helpers that emit only the sections and fields you touch.

### Removed

- Legacy members were removed outright (no `[Obsolete]` shims). Update to the new API surface.
3 changes: 3 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
<Copyright>Copyright 2020 Kevsoft</Copyright>
<Authors>Kevin Smith</Authors>
<LangVersion>latest</LangVersion>
<LibraryTargetFrameworks>netstandard2.0;net8.0;net9.0;net10.0</LibraryTargetFrameworks>
<TestTargetFrameworks>net8.0;net9.0;net10.0</TestTargetFrameworks>
<SampleTargetFramework>net10.0</SampleTargetFramework>
<nullable>enable</nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
Expand Down
19 changes: 11 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,35 +1,38 @@
ARG VERSION=0.0.0
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS restore
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS restore
WORKDIR /

COPY ./nuget.config .
COPY ./*.sln .
COPY ./Directory.Build.props .
COPY ./src/Kevsoft.WLED/*.csproj ./src/Kevsoft.WLED/
COPY ./src/Kevsoft.WLED.DependencyInjection/*.csproj ./src/Kevsoft.WLED.DependencyInjection/
COPY ./test/Kevsoft.WLED.Tests/*.csproj ./test/Kevsoft.WLED.Tests/
COPY ./samples/BasicConsole/*.csproj ./samples/BasicConsole/
RUN dotnet restore

FROM restore as build
FROM restore AS build
ARG VERSION
COPY ./icon.png .
COPY ./src/Kevsoft.WLED/ ./src/Kevsoft.WLED/
RUN dotnet build ./src/**/*.csproj --configuration Release -p:Version=${VERSION} --no-restore
COPY ./src/Kevsoft.WLED.DependencyInjection/ ./src/Kevsoft.WLED.DependencyInjection/
RUN dotnet build ./src/Kevsoft.WLED.DependencyInjection/Kevsoft.WLED.DependencyInjection.csproj --configuration Release -p:Version=${VERSION} --no-restore

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

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

FROM build as pack
FROM build AS pack
ARG VERSION
RUN dotnet pack --configuration Release -p:Version=${VERSION} --no-build
RUN dotnet pack ./src/Kevsoft.WLED/Kevsoft.WLED.csproj --configuration Release -p:Version=${VERSION} --no-build
RUN dotnet pack ./src/Kevsoft.WLED.DependencyInjection/Kevsoft.WLED.DependencyInjection.csproj --configuration Release -p:Version=${VERSION} --no-build

FROM pack as push
FROM pack AS push
RUN env

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