From 6ed2188d6c30c415a867ec77bce7ce3c8685971f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Bregu=C5=82a?= Date: Thu, 4 Sep 2025 01:52:00 +0200 Subject: [PATCH 1/4] Add segment name --- src/wled/models.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/wled/models.py b/src/wled/models.py index 15e02908..f77a3c5e 100644 --- a/src/wled/models.py +++ b/src/wled/models.py @@ -294,6 +294,13 @@ class Segment(BaseModel): 255 indicates the coldest temperature """ + name: str | None = field(default=None, metadata=field_options(alias="n")) + """The name of the segment. + + Names are not present by default. If this is none, use + "Segment{segment_id}" to match the WLED UI. + """ + @dataclass(kw_only=True) class Leds: From a321a5d61784e711b3cfc4482ffb18311e7a281b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Bregu=C5=82a?= Date: Thu, 4 Sep 2025 13:55:29 +0200 Subject: [PATCH 2/4] Enhance segment name handling and documentation --- src/wled/models.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/wled/models.py b/src/wled/models.py index f77a3c5e..13104a37 100644 --- a/src/wled/models.py +++ b/src/wled/models.py @@ -295,12 +295,27 @@ class Segment(BaseModel): """ name: str | None = field(default=None, metadata=field_options(alias="n")) - """The name of the segment. - - Names are not present by default. If this is none, use - "Segment{segment_id}" to match the WLED UI. + """User-defined segment name (alias: "n"). + + Not present by default. If None or empty, callers may fall back to a UI-like + default, e.g., "Segment {segment_id}". """ + @property + def effective_name(self) -> str: + if self.name: + return self.name + if self.segment_id is not None: + return f"Segment {self.segment_id}" + return "Segment" + + @classmethod + def __post_deserialize__(cls, obj: "Segment") -> "Segment": + # Normalize empty string to None for consistency + if obj.name == "": + obj.name = None + return obj + @dataclass(kw_only=True) class Leds: From f16cfb7149f79dcbc34e451b09720949a2b39974 Mon Sep 17 00:00:00 2001 From: mik-laj <12058428+mik-laj@users.noreply.github.com> Date: Mon, 2 Mar 2026 12:48:59 +0100 Subject: [PATCH 3/4] Simplify code --- src/wled/models.py | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/wled/models.py b/src/wled/models.py index 13104a37..9ca9f66b 100644 --- a/src/wled/models.py +++ b/src/wled/models.py @@ -295,26 +295,7 @@ class Segment(BaseModel): """ name: str | None = field(default=None, metadata=field_options(alias="n")) - """User-defined segment name (alias: "n"). - - Not present by default. If None or empty, callers may fall back to a UI-like - default, e.g., "Segment {segment_id}". - """ - - @property - def effective_name(self) -> str: - if self.name: - return self.name - if self.segment_id is not None: - return f"Segment {self.segment_id}" - return "Segment" - - @classmethod - def __post_deserialize__(cls, obj: "Segment") -> "Segment": - # Normalize empty string to None for consistency - if obj.name == "": - obj.name = None - return obj + """User-defined segment name (alias: "n").""" @dataclass(kw_only=True) From 35767068bd2439e3cd845a81ff2543df3cd34b1c Mon Sep 17 00:00:00 2001 From: mik-laj <12058428+mik-laj@users.noreply.github.com> Date: Mon, 2 Mar 2026 13:17:57 +0100 Subject: [PATCH 4/4] Add tests --- tests/fixtures/state_with_segment_name.json | 85 +++++++++++++++++++ .../fixtures/state_without_segment_name.json | 49 +++++++++++ tests/test_models.py | 29 +++++++ 3 files changed, 163 insertions(+) create mode 100644 tests/fixtures/state_with_segment_name.json create mode 100644 tests/fixtures/state_without_segment_name.json create mode 100644 tests/test_models.py diff --git a/tests/fixtures/state_with_segment_name.json b/tests/fixtures/state_with_segment_name.json new file mode 100644 index 00000000..a54042ea --- /dev/null +++ b/tests/fixtures/state_with_segment_name.json @@ -0,0 +1,85 @@ +{ + "on": false, + "bri": 128, + "transition": 7, + "ps": -1, + "pl": -1, + "ledmap": 0, + "AudioReactive": { "on": true }, + "nl": { "on": false, "dur": 60, "mode": 1, "tbri": 0, "rem": -1 }, + "udpn": { "send": false, "recv": true, "sgrp": 1, "rgrp": 1 }, + "lor": 0, + "mainseg": 0, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 29, + "len": 29, + "grp": 1, + "spc": 0, + "of": 0, + "on": false, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Curtain", + "col": [ + [46, 255, 95, 0], + [0, 0, 0, 0], + [0, 0, 0, 0] + ], + "fx": 0, + "sx": 128, + "ix": 128, + "pal": 0, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 1, + "start": 29, + "stop": 55, + "len": 26, + "grp": 1, + "spc": 0, + "of": 0, + "on": false, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Wall", + "col": [ + [46, 255, 95, 0], + [0, 0, 0, 0], + [0, 0, 0, 0] + ], + "fx": 0, + "sx": 128, + "ix": 128, + "pal": 0, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + } + ] +} diff --git a/tests/fixtures/state_without_segment_name.json b/tests/fixtures/state_without_segment_name.json new file mode 100644 index 00000000..a34a49cd --- /dev/null +++ b/tests/fixtures/state_without_segment_name.json @@ -0,0 +1,49 @@ +{ + "on": false, + "bri": 128, + "transition": 7, + "ps": 1, + "pl": -1, + "ledmap": 0, + "AudioReactive": { "on": false }, + "nl": { "on": false, "dur": 60, "mode": 1, "tbri": 0, "rem": -1 }, + "udpn": { "send": false, "recv": true, "sgrp": 2, "rgrp": 2 }, + "lor": 0, + "mainseg": 0, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 26, + "len": 26, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "col": [ + [4, 255, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0] + ], + "fx": 0, + "sx": 128, + "ix": 128, + "pal": 0, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + } + ] +} diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 00000000..39ca73e4 --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,29 @@ +"""Tests for WLED models.""" +import json +from pathlib import Path + +from wled.models import State + +ROOT_DIR = Path(__file__).parent.parent +FIXTURE_DIR = ROOT_DIR / "tests" / "fixtures" + +def load_fixture(file_name: str) -> dict: + """Load a fixture file from the fixtures directory.""" + fixture_path = FIXTURE_DIR / file_name + return json.loads(fixture_path.read_text()) + + +def test_state_with_segment_name() -> None: + """Test segment name is parsed correctly.""" + data = load_fixture("state_with_segment_name.json") + model_instance = State.from_dict(data) + + assert model_instance.segments[0].name == "Curtain" + assert model_instance.segments[1].name == "Wall" + +def test_state_without_segment_name() -> None: + """Test segment name is None when not provided.""" + data = load_fixture("state_without_segment_name.json") + model_instance = State.from_dict(data) + + assert model_instance.segments[0].name is None