Skip to content

feat: Add RGB nightlight support for VeSyncHumid200300S (LUH-O451S-WEU)#502

Open
MangelSpec wants to merge 2 commits intowebdjoe:devfrom
MangelSpec:dev
Open

feat: Add RGB nightlight support for VeSyncHumid200300S (LUH-O451S-WEU)#502
MangelSpec wants to merge 2 commits intowebdjoe:devfrom
MangelSpec:dev

Conversation

@MangelSpec
Copy link
Copy Markdown

Summary

Adds RGB nightlight control support for the VeSyncHumid200300S device class, specifically for the LUH-O451S-WEU (OasisMist 4.5L) humidifier model.

This addresses missing nightlight functionality that prevents Home Assistant from creating light entities for humidifiers with RGB nightlight features. Related issue: home-assistant/core#160387

Changes

  • Added HumidifierFeatures.RGB_NIGHTLIGHT feature flag
  • Added supports_rgb_nightlight property to base class
  • Implemented set_rgb_nightlight(power, brightness, red, green, blue) method
  • Added RGB nightlight state attributes (status, brightness, r/g/b, color_mode)

API Quirks

  1. Brightness-adjusted RGB: API expects RGB values pre-multiplied by brightness via HSV conversion
  2. Color slider location: API requires a colorSliderLocation (0-100) mapped from an 8-color gradient
  3. !Stale API responses!: After setting values, API returns old data for several minutes - implemented timeout to prevent state drift. This is sadly not working perfectly yet, I didn't find out how to improve this behavior or force the getHumidifierStatus to return the values we just set before. Maybe someone with more insight or time can improve this as it's also causing issues when updating the nightlight via App and then using this library.

Known Limitations

  • Minimum brightness is 40% (enforced by VeSync app)
  • Other models with RGB nightlights (e.g., LUH-D301S-WUSR) may work by adding the feature flag - only tested on LUH-O451S-WEU

Testing

Tested on physical LUH-O451S-WEU device: power on/off, brightness, color changes, state refresh.

image

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown
Owner

@webdjoe webdjoe left a comment

Choose a reason for hiding this comment

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

Apologies for the delay, great work! Just a few minor edits to keep the library clean.


logger = logging.getLogger(__name__)

# RGB nightlight constants
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Please move to the constants module. Keep the stale data timeout and see comment below

self.state.warm_mist_level = resp_model.warm_level
self.state.warm_mist_enabled = resp_model.warm_enabled
# Handle RGB nightlight
if self.supports_rgb_nightlight and resp_model.rgbNightLight is not None:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I'm not sure how to work around this, but for the time being move this to a separate private method

brightness = 100 if toggle else 0
return await self.set_nightlight_brightness(brightness)

# 8-color gradient used by VeSync app for RGB nightlight color slider
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Move _RGB_NIGHTLIGHT_GRADIENTS to a new class in the utils.colors module in case it is used by other devices. Name it RGBNightlightColor or something that makes sense

]

@staticmethod
def _color_distance(r1: int, g1: int, b1: int, r2: int, g2: int, b2: int) -> float:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Move to utils.colors class with _RGB_NIGHTLIGHT_GRADIENTS

return ((r1 - r2) ** 2 + (g1 - g2) ** 2 + (b1 - b2) ** 2) ** 0.5

@staticmethod
def _interpolate_color(
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Move to utils.colors class with _RGB_NIGHTLIGHT_GRADIENTS

return (r, g, b)

@staticmethod
def _apply_brightness_to_rgb(
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Move to utils.colors class with _RGB_NIGHTLIGHT_GRADIENTS

return (int(r * 255), int(g * 255), int(b * 255))

@staticmethod
def _normalize_rgb_to_full_brightness(
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Same as above

return (int(r * 255), int(g * 255), int(b * 255))

@classmethod
def _rgb_to_color_slider_location(cls, red: int, green: int, blue: int) -> int:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Same as above

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.

2 participants