Skip to content

fix(color): XY fallback for lights without the optional HueSaturation feature (#60)#61

Merged
simons-plugins merged 5 commits into
mainfrom
fix/color-xy-fallback
Jun 12, 2026
Merged

fix(color): XY fallback for lights without the optional HueSaturation feature (#60)#61
simons-plugins merged 5 commits into
mainfrom
fix/color-xy-fallback

Conversation

@simons-plugins

@simons-plugins simons-plugins commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Closes #60.

What

Clicking an RGB colour in Indigo failed with Unsupported command (code 129) on the rig's colour light. Root cause: ColorControlHandler sent MoveToHueAndSaturation unconditionally, but HueSaturation is optional per the Matter spec — even an Extended Color Light is only required to implement XY + ColorTemperature, and matter.js's ExtendedColorLightDevice (and real XY-only bulbs) reject HS commands outright.

How

  • ColorCapabilities (0x400A) is subscribed and cached in a new colorCapabilities device state (primed from the node snapshot at reconcile).
  • RGB set-colour: HS-capable → unchanged; XY-only → MoveToColor with a standard sRGB→CIE-xy conversion (D65, clamped to the spec's 0xFEFF); capabilities unknown (pre-fix device / not yet primed) → historical HS behaviour, no guessing.
  • Receive side: CurrentX/CurrentY reports recompute Indigo's RGB states (xy→sRGB, brightness from brightnessLevel); raw values kept in new colorX/colorY states so single-axis reports combine correctly. Devices created before the fix lack the new states and degrade quietly (same SDK guard pattern as whiteTemperature).

Also in this PR

docs/TESTING.md (requested by Simon): the four-layer test story for users and plugin devs — unit suite, the device zoo (with its track record and "add your weird device to ZOO" workflow), the matter.js virtual fleet (ports/discriminators/relaunch rules), and the live-validation method, plus the real-hardware validation table (Tapo P110M Wi-Fi, Aqara FP300 Thread).

Testing

756 tests (+8): XY-only command selection with CIE-value assertions, HS-capable and unknown-capability paths, capability caching, X/Y report recombination into RGB, pre-fix-device degradation, conversion round-trips, black→white-point. Live verification on jarvis with the XY-only rig light to follow on this branch build.

Version: 2026.2.22 → 2026.2.23 (patch).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Extended color support for color dimmers using CIE x/y color coordinates alongside hue/saturation controls.
    • Added color capability detection for dimmer devices.
  • Bug Fixes

    • Improved error handling when device name conflicts occur during creation with clear remediation guidance.
    • Fixed device state list refresh on startup to ensure proper state availability.
  • Documentation

    • Added comprehensive testing guide documenting the multi-layer validation strategy and procedures.
  • Chores

    • Version bump to 2026.2.23.

…#60)

HueSaturation is OPTIONAL per the Matter spec — an Extended Color Light
need only implement XY + ColorTemperature — but the RGB path sent
MoveToHueAndSaturation unconditionally, so XY-only lights (including
matter.js's ExtendedColorLightDevice on the rig) rejected every colour
command with UNSUPPORTED_COMMAND (live repro 2026-06-12).

- Subscribe + cache ColorCapabilities (0x400A) in a new
  colorCapabilities device state; RGB set-colour picks
  MoveToHueAndSaturation when HS is supported, MoveToColor (sRGB→CIE
  xy) when the device is XY-only, and keeps historical HS behaviour
  while capabilities are unknown.
- Receive side: CurrentX/CurrentY (0x0003/0x0004) updates recompute
  the Indigo RGB states via xy→sRGB, with raw values kept in new
  colorX/colorY states so one-axis reports stay coherent. Pre-fix
  devices without the new states degrade quietly.
- docs/TESTING.md: how the plugin is tested — unit suite, device zoo,
  the matter.js virtual fleet (incl. the rig rules), and the live
  validation method, with the real-hardware validation table.

756 tests. Closes #60.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@simons-plugins, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 11 minutes and 5 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 78acd427-7690-40a8-9126-eea34a4922bc

📥 Commits

Reviewing files that changed from the base of the PR and between 7c8922a and cba8099.

📒 Files selected for processing (9)
  • docs/aizome.css
  • docs/index.html
  • docs/matter.html
  • docs/testing.html
  • indigo-matter.indigoPlugin/Contents/Server Plugin/matter_handlers/base.py
  • indigo-matter.indigoPlugin/Contents/Server Plugin/matter_handlers/color_control.py
  • indigo-matter.indigoPlugin/Contents/Server Plugin/plugin.py
  • tests/test_dimmer_color.py
  • tests/test_plugin_module.py
📝 Walkthrough

Walkthrough

This PR implements capability-aware Extended Color Light support to resolve issue #60, where RGB commands failed on XY-only devices. The changes add CIE xy color coordinate handling alongside existing Hue/Saturation and ColorTemperature modes, introduce new device states for color capabilities and coordinates, improve device-creation error messaging, and add comprehensive testing documentation.

Changes

Extended Color Light Feature

Layer / File(s) Summary
Extended Color State Schema and Plugin Discovery
indigo-matter.indigoPlugin/Contents/Server Plugin/Devices.xml, indigo-matter.indigoPlugin/Contents/Info.plist, indigo-matter.indigoPlugin/Contents/Server Plugin/plugin.py, tests/test_plugin_module.py
matterColorDimmer device declares new integer states colorCapabilities, colorX, and colorY for tracking device color mode and coordinates. Plugin version is bumped to 2026.2.23, and deviceStartComm now calls stateListOrDisplayStateIdChanged() at startup to refresh Indigo's state-list cache without re-reading Devices.xml. A new test verifies this state-list refresh is triggered.
Color Conversion Helpers and Capability-Aware Command Selection
indigo-matter.indigoPlugin/Contents/Server Plugin/matter_handlers/color_control.py, tests/test_dimmer_color.py
ColorControlHandler gains sRGB↔CIE xy conversion functions (rgb_to_matter_xy, matter_xy_to_rgb) with proper XYZ(D65) chromaticity math, black-point handling, and brightness scaling. Handler subscribes to colorCapabilities bitmap and color coordinate attributes, caches capabilities into device state, and selects MoveToColor (xy) when Hue/Saturation is unsupported but XY is available; otherwise falls back to MoveToHueAndSaturation. Incoming colorX/colorY updates are converted to RGB and update Indigo color states. Comprehensive tests validate capability-driven command selection, conversion accuracy for primary/combined colors, and graceful degradation when required states are missing.
Device Creation Error Handling for Name Collisions
indigo-matter.indigoPlugin/Contents/Server Plugin/device_sync.py, tests/test_device_sync.py
DeviceSync._create_one now detects ValueError with "NameNotUnique" and logs a targeted remedy message (delete the preexisting device and reload the plugin) instead of a confusing traceback. Other ValueErrors and exceptions are logged normally. A test verifies the improved message path without traceback logging.
Testing Strategy Documentation
README.md, docs/TESTING.md
New docs/TESTING.md documents a four-layer testing strategy: mocked unit suite with golden wire-format frames, device zoo contract harness with synthetic cluster mappings and structural invariants, virtual LAN fleet using matter.js with deployment rules and known device imperfections, and live production validation with operational procedures and real-device findings table. README links to this documentation.

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

  • simons-plugins/indigo-matter#59: Both PRs modify device startup and creation paths (plugin.py::deviceStartComm and device_sync.py::_create_one) as part of hardening the device lifecycle pipeline.

Poem

🐰 A rabbit hops through color space,
From XY dreams to RGB embrace,
No more blue errors on XY lights—
Just smooth conversions, mired delights!
With states refreshed and handlers wise,
Extended colors paint the skies. 🌈

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: implementing XY fallback for lights lacking HueSaturation support, with issue reference #60.
Linked Issues check ✅ Passed The PR fully addresses issue #60's requirements: subscribes to ColorCapabilities, implements capability-driven command selection (MoveToColor for XY-only, MoveToHueAndSaturation for HS-capable), adds state caching, and mirrors XY→RGB on receive side.
Out of Scope Changes check ✅ Passed All changes are in-scope: color control logic, device state additions, version bump, testing documentation, and tests directly support issue #60's requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/color-xy-fallback

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

simons-plugins and others added 2 commits June 12, 2026 22:37
Live deploy of the XY fix surfaced it: Indigo builds a device's state
list at creation and never re-reads Devices.xml, so fielded colour
devices lacked the new colorCapabilities state and Indigo logged
"state key colorCapabilities not defined" on every report.
deviceStartComm now calls stateListOrDisplayStateIdChanged() (the
documented refresh), and the capabilities write degrades quietly when
the state is still missing, same as the other guarded states.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s creation

Live evidence (issue #62): a device whose type was changed via Indigo's
Type menu is left configured=False, drops out of iter("self"), and
never receives deviceStartComm — so the index loses it, _unique_name
can't see it, and reconcile's recreate attempt fails NameNotUniqueError
every pass as a raw traceback. Both #58 guards are structurally blind
to this state. The collision now logs the remedy (delete the device,
reload) instead of a traceback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@simons-plugins

Copy link
Copy Markdown
Owner Author

Live-verified on jarvis: the previously-failing XY-only rig light received and ACCEPTED moveToColor colorX: 41948 colorY: 21625 (= CIE 0.640/0.330, exact sRGB red) — virtual-device log as ground truth. The deploy also surfaced two adjacent fixes now in this PR: stateListOrDisplayStateIdChanged() on deviceStartComm (fielded devices lacked the new states; Indigo never re-reads Devices.xml) and the actionable NameNotUnique warning (issue #62, a type-edited configured=False device is invisible to iter('self')).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_device_sync.py`:
- Line 1544: Replace the unused unpacked variable in the test by changing the
assignment "indigo_mock, devices = indigo_env" to use the dummy variable pattern
(e.g., "indigo_mock, _ = indigo_env") so that "devices" is not left unused;
update the line where the indigo_env fixture is unpacked (referencing
indigo_mock and devices) accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2791a7b7-b2ab-4027-a008-490a8ba53b34

📥 Commits

Reviewing files that changed from the base of the PR and between e9c0272 and 7c8922a.

📒 Files selected for processing (10)
  • README.md
  • docs/TESTING.md
  • indigo-matter.indigoPlugin/Contents/Info.plist
  • indigo-matter.indigoPlugin/Contents/Server Plugin/Devices.xml
  • indigo-matter.indigoPlugin/Contents/Server Plugin/device_sync.py
  • indigo-matter.indigoPlugin/Contents/Server Plugin/matter_handlers/color_control.py
  • indigo-matter.indigoPlugin/Contents/Server Plugin/plugin.py
  • tests/test_device_sync.py
  • tests/test_dimmer_color.py
  • tests/test_plugin_module.py

Comment thread tests/test_device_sync.py
"""A NameNotUniqueError on create (issue #62: type-edited device invisible
to iter('self') still holds the name server-side) must log the actionable
remedy, not a raw traceback, and must not abort the batch."""
indigo_mock, devices = indigo_env

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix unused variable flagged by Ruff.

The devices variable is unpacked but never used in this test. Replace it with _ to follow the dummy variable pattern.

🧹 Proposed fix
-    indigo_mock, devices = indigo_env
+    indigo_mock, _ = indigo_env
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
indigo_mock, devices = indigo_env
indigo_mock, _ = indigo_env
🧰 Tools
🪛 Ruff (0.15.15)

[warning] 1544-1544: Unpacked variable devices is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_device_sync.py` at line 1544, Replace the unused unpacked variable
in the test by changing the assignment "indigo_mock, devices = indigo_env" to
use the dummy variable pattern (e.g., "indigo_mock, _ = indigo_env") so that
"devices" is not left unused; update the line where the indigo_env fixture is
unpacked (referencing indigo_mock and devices) accordingly.

Source: Linters/SAST tools

simons-plugins and others added 2 commits June 12, 2026 23:14
Two long-read pages in the Domio site's Aizome direction (indigo dye +
warm paper, Fraunces/General Sans, letterpress detailing) but as their
own engineering gazette: numbered sections, drop caps, rubber-stamp
validation marks, the share-model pipeline as a real diagram, and the
four test layers drawn as strata. Leaf accent for the landscape page,
ochre for the proving ground; plus a small landing index and a shared
stylesheet. Scroll-reveal is progressive-enhancement only (no-JS,
print and screenshot tools always get visible content).

Serve from GitHub Pages → main /docs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
)

Live testing found the W slider (whiteLevel) dead: Matter colour lights
have no separate white channel, so the handler ignored the channel
entirely and, with no Matter attribute to echo it, the Indigo slider
snapped back even when RGB worked.

- whiteLevel now maps to its Matter meaning: MoveToColorTemperature at
  the requested/stored white temperature plus MoveToLevelWithOnOff at
  the slider's intensity (W=0 → off). Handlers may now return a LIST of
  actions; the plugin sends each sequentially, individually acked.
- whiteLevel (and the RGB-side W zeroing) is echoed optimistically via
  updateStatesOnServer — cosmetic-only, guarded, since no device report
  will ever carry it.

765 tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@simons-plugins simons-plugins merged commit 064f254 into main Jun 12, 2026
3 checks passed
@simons-plugins simons-plugins deleted the fix/color-xy-fallback branch June 12, 2026 22:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RGB colour commands fail on XY-only lights — MoveToHueAndSaturation sent without checking ColorCapabilities

1 participant