Skip to content

fix: separate LUH-O451S-WUS humidity range and mist modes from WUSR/601S - #547

Open
proscar87 wants to merge 5 commits into
webdjoe:masterfrom
proscar87:fix/oasismist-450s-wus-humidity-range
Open

fix: separate LUH-O451S-WUS humidity range and mist modes from WUSR/601S#547
proscar87 wants to merge 5 commits into
webdjoe:masterfrom
proscar87:fix/oasismist-450s-wus-humidity-range

Conversation

@proscar87

Copy link
Copy Markdown

Problem

The HumidifierMap entry for dev_types=['LUH-O451S-WUS', 'LUH-O451S-WUSR', 'LUH-O601S-WUS', 'LUH-O601S-KUS'] treats all four dev types identically: target_minmax=(30, 80) (the class default) and a HUMIDITY mist mode.

Both #295 and #296 show this is wrong for the LUH-O451S-WUS variant specifically (configModule WFON_AHM_LUH-A451S-WUS_US):

@webdjoe confirmed in #296 that this is specific to that configModule, and that the WUSR variant (WFON_AHM_LUH-A451S-WUSR_US) can go down to 30.

Fix

Split the single HumidifierMap entry into two:

  • dev_types=['LUH-O451S-WUS']: target_minmax=(40, 80), HUMIDITY mode removed.
  • dev_types=['LUH-O451S-WUSR', 'LUH-O601S-WUS', 'LUH-O601S-KUS']: unchanged (target_minmax=(30, 80), HUMIDITY mode kept) — no evidence these are affected.

target_minmax and mist_modes aren't just documentation — set_humidity()/set_humidity_mode() validate against them client-side before calling the API (vesynchumidifier.py), so this actually prevents the rejected calls rather than just describing them.

Splitting the dev_types list also means LUH-O451S-WUSR now gets its own entry in humidifier_modules, which the test suite auto-parametrizes from (HUMIDIFIERS = [m.setup_entry for m in humidifier_modules]) — added the corresponding fixtures (HUMIDIFIER_DETAILS/DETAILS_RESPONSES/METHOD_RESPONSES entries, recorded YAML) so it's exercised the same as every other device.

Testing

Added 4 regression tests in test_humidifiers.py:

  • test_oasismist_450s_wus_rejects_humidity_below_40 / test_oasismist_450s_wusr_still_allows_humidity_30
  • test_oasismist_450s_wus_has_no_humidity_mode / test_oasismist_450s_wusr_still_has_humidity_mode

Confirmed all 4 fail against the pre-fix code (git stash on just device_map.py/call_json_humidifiers.py) with the exact errors from the two issues, and pass with the fix. Full suite: 395 passed, 3 skipped. flake8 src/pyvesync, pylint src/pyvesync (10.00/10), ruff check src/pyvesync/device_map.py (only pre-existing, unrelated CPY001 finding, confirmed present on master too) all clean. mypy src/pyvesync has 2 pre-existing errors in vesyncthermostat.py, unrelated to this change and present on master.

Not validated against real hardware (no OasisMist 450S on hand) — the fix is based directly on the live API traces both issues include and @webdjoe's own confirmation of the configModule-specific behavior, not guesswork about the device.

Fixes #295, fixes #296.

MangelSpec and others added 5 commits July 11, 2026 19:15
…U) (webdjoe#502)

## 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](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.
Add WYLDR16A1081 to the device map as a dedicated entry with a new
VeSyncWYLDRPlug class. This device is functionally identical to the
BSDOG01 series (on/off, real-time power, voltage, daily energy) but does
not support energy history retrieval via the bypassV2 API — calls to
getEnergyHistory return result code -1 for this device type.

VeSyncWYLDRPlug overrides _get_energy_history() with a no-op so that
weekly and monthly history requests are silently skipped instead of
generating spurious warning logs on every poll cycle.
…bdjoe#514)

Using loop.stop() leaves the loop open, which can cause 'Too many open
files' errors when running the test suite.
…B381S) (webdjoe#528)

## Summary

The LEH-B381S (Sprout Humidifier) already declares `NIGHTLIGHT` /
`NIGHTLIGHT_BRIGHTNESS` support in `device_map.py`, and
`VeSyncSproutHumid` already had a working private
`_set_nightlight_state` helper that builds the correct `setLightStatus`
payload. However, the class never overrode the base class's
`set_nightlight_brightness` / `toggle_nightlight` methods, so any caller
(e.g. Home Assistant's `select`/`switch` entities) fell through to the
base-class stub, which just logs `Nightlight brightness has not been
configured.` and returns `False` instead of calling the API.

Traceback this was pulled from (Home Assistant, via
`select.async_select_option`):
```
File ".../vesync/select.py", line 194, in async_select_option
ERROR [pyvesync.base_devices.humidifier_base] Nightlight brightness has not been configured.
```

- Added `set_nightlight_brightness` and `toggle_nightlight` overrides to
`VeSyncSproutHumid`, wired to the existing `_set_nightlight_state`
helper (mirroring the pattern used in
`VeSyncHumid200300S`/`VeSyncHumid1000S`).
- Fixed a latent bug in `_set_nightlight_state`: it unconditionally
stored the passed-in `brightness` (including `None`) as the new state,
and would send `"brightness": null` to the API when toggling the light
before a brightness had ever been read or set. It now falls back to the
last known brightness, or `100` if none is known yet.

## Test plan
- [x] Added `turn_on_nightlight`, `turn_off_nightlight`, and
`set_nightlight_brightness` test cases for `LEH-B381S` in
`test_humidifiers.py`
- [x] Recorded new API fixtures in
`src/tests/api/vesynchumidifier/LEH-B381S.yaml`
- [x] `pytest src/tests/` — 355 passed, 3 skipped
- [x] `mypy src/pyvesync/devices/vesynchumidifier.py` — no issues
- [ ] Validated against a physical LEH-B381S device (reporter has the
device; will confirm in this thread)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Joe Trabulsy <jtrabulsy@gmail.com>
configModule WFON_AHM_LUH-A451S-WUS_US only accepts a target humidity of
40-80 (the cloud API rejects 30 with "target humidity is out of range")
and rejects the HUMIDITY mist mode ("Mode value invaild!"), unlike the
WUSR/601S variants it was grouped with, which the maintainer confirmed
in the issue threads can go down to 30.

Splits the single HumidifierMap entry for
['LUH-O451S-WUS', 'LUH-O451S-WUSR', 'LUH-O601S-WUS', 'LUH-O601S-KUS']
into two: LUH-O451S-WUS keeps target_minmax=(40, 80) and drops the
HUMIDITY mode, the other three dev_types keep the previous (30, 80)
range and HUMIDITY mode, matching the only variant with confirmed
API-rejected behavior.

Fixes webdjoe#295, fixes webdjoe#296.
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.

5 participants