Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,6 @@ docstest/*
.claude/settings.local.json
.mcp.json
.vesync_auth
**/CLAUDE.md
**/CLAUDE.md
/docs/superpowers
/docs/development/reviews
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ pip install pyvesync
6. Two Plug Outdoor Outlet (ESO15-TB) (Each plug is a separate `VeSyncOutlet` object, energy readings are for both plugs combined)
7. BSDOG / Greensun Smart Outlet Series (BSDOG01, BSDOG02, WYSMTOD16A, WM-PLUG and more)
8. WHOPLUG / Greensun Smart Outlet
9. WYLDR Smart Plug (WYLDR16A1081) (Supports energy monitoring but not energy history)

<!--SUPPORTED OUTLETS END-->

Expand Down
22 changes: 21 additions & 1 deletion docs/development/utils/colors.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Color Handlers

The `pyvesync.utils.colors` module provides classes and functions for handling color conversions and representations. It includes the `Color` class, which serves as a base for color manipulation, and the `HSV` and `RGB` classes for specific color models. The module is designed for internal use within the library and is not intended for public use.
The `pyvesync.utils.colors` module provides classes and functions for handling color conversions and representations. It includes the `Color` class, which serves as a base for color manipulation, and the `HSV` and `RGB` classes for specific color models. The `RGBNightlightColor` class provides the color helpers used by humidifiers with an RGB nightlight. The module is designed for internal use within the library and is not intended for public use.

## Color class

Expand Down Expand Up @@ -37,3 +37,23 @@ This is the primary class that holds the color data and provides methods for con
- "!Config"
- "!__post_init__"
- "!__str__"

## RGBNightlightColor class

Color helpers for devices with an RGB nightlight, such as the OasisMist 4.5L
(`LUH-O451S-WEU`) humidifier. The VeSync app drives the nightlight color from
an 8-color gradient slider and sends brightness-adjusted RGB values (brightness
applied via the HSV value component) rather than a raw color plus a separate
brightness. This class encapsulates that geometry: mapping an arbitrary RGB
color to the slider position (`colorSliderLocation`), applying brightness to a
color, and recovering the full-brightness base color from a dimmed one.

::: pyvesync.utils.colors.RGBNightlightColor
handler: python
options:
show_root_heading: true
show_source: true
filters:
- "!Config"
- "!^__init*"
- "!__str__"
48 changes: 31 additions & 17 deletions docs/supported_devices.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The VeSync API supports a variety of devices. The following is a list of devices
- [Etekcity 15A Rectangle Outlet][pyvesync.devices.vesyncoutlet.VeSyncOutlet15A]
- [Etekcity 15A Outdoor Dual Outlet][pyvesync.devices.vesyncoutlet.VeSyncOutdoorPlug]
- [BSDOG / Greensun Smart Outlet Series][pyvesync.devices.vesyncoutlet.VeSyncBSDOGPlug] - WHOPLUG / GREENSUN
- [WYLDR Smart Plug][pyvesync.devices.vesyncoutlet.VeSyncBSDOGPlug] - WYLDR16A1081 (energy monitoring without energy history)
3. Switches
- [ESWL01][pyvesync.devices.vesyncswitch.VeSyncWallSwitch] - Etekcity Wall Switch
- [ESWL03][pyvesync.devices.vesyncswitch.VeSyncWallSwitch] - Etekcity 3-Way Switch
Expand Down Expand Up @@ -59,14 +60,21 @@ Switches have minimal features, the dimmer switch is the only switch that has ad

### Outlets

| Device Name | Power Stats | Nightlight |
| :------: | :----: | :----: |
| 7A Round Outlet | ✔ | |
| 10A Round EU Outlet | ✔ | |
| 10A Round US Outlet | | |
| 15A Rectangle Outlet | ✔ | ✔ |
| 15A Outdoor Dual Outlet | ✔ | |
| Round Smart Series | | |
| Device Name | Power Stats | Energy History | Nightlight |
| :------: | :----: | :----: | :----: |
| 7A Round Outlet | ✔ | ✔ | |
| 10A Round EU Outlet | ✔ | ✔ | |
| 10A Round US Outlet | | | |
| 15A Rectangle Outlet | ✔ | ✔ | ✔ |
| 15A Outdoor Dual Outlet | ✔ | ✔ | |
| Smart Plug Series (WHOGPLUG / BSDOG01) | ✔ | ✔ | |
| WYLDR Smart Plug (WYLDR16A1081) | ✔ | | |

Power stats are realtime power, voltage and energy readings from the device.
Energy history is the weekly, monthly and yearly energy usage retrieved with
`get_weekly_energy()`, `get_monthly_energy()` and `get_yearly_energy()`. Devices
without the energy history feature log a debug message and make no API call when
these methods are used.

### Purifiers

Expand All @@ -83,15 +91,21 @@ Switches have minimal features, the dimmer switch is the only switch that has ad

### Humidifiers

| Device Name | Night Light | Warm Mist |
| ------ |-------------| ----- |
| Classic 200S | | |
| Classic 300S | ✔ | ✔ |
| Dual 200S | | |
| LV600S | | ✔ |
| OasisMist | | ✔ |
| Superior 6000S | | ✔ |
| Sprout Humidifier | | |
| Device Name | Night Light | RGB Night Light | Warm Mist |
| ------ | ----- | ----- | ----- |
| Classic 200S | | | |
| Classic 300S | ✔ | | ✔ |
| Dual 200S | | | |
| LV600S | | | ✔ |
| OasisMist 4.5L | | ✔ | ✔ |
| Superior 6000S | | | ✔ |
| Sprout Humidifier | | | |

The OasisMist 4.5L (`LUH-O451S-WEU`) exposes an RGB nightlight through
[`set_rgb_nightlight`][pyvesync.devices.vesynchumidifier.VeSyncHumid200300S.set_rgb_nightlight].
Other models with the same hardware may work by adding the
`HumidifierFeatures.RGB_NIGHTLIGHT` feature flag, but only the OasisMist 4.5L has
been verified.

### Fans

Expand Down
50 changes: 50 additions & 0 deletions src/pyvesync/base_devices/humidifier_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ class HumidifierState(DeviceState):
'nightlight_brightness',
'nightlight_color_temp',
'nightlight_status',
'rgb_nightlight_blue',
'rgb_nightlight_brightness',
'rgb_nightlight_color_mode',
'rgb_nightlight_green',
'rgb_nightlight_red',
'rgb_nightlight_set_time',
'rgb_nightlight_status',
'temperature',
'warm_mist_enabled',
'warm_mist_level',
Expand Down Expand Up @@ -112,6 +119,13 @@ def __init__(
self.mode: str | None = None
self.nightlight_brightness: int | None = None
self.nightlight_status: str | None = None
self.rgb_nightlight_status: str | None = None
self.rgb_nightlight_brightness: int | None = None
self.rgb_nightlight_red: int | None = None
self.rgb_nightlight_green: int | None = None
self.rgb_nightlight_blue: int | None = None
self.rgb_nightlight_color_mode: str | None = None
self.rgb_nightlight_set_time: float | None = None
self.nightlight_color_temp: int | None = None
self.warm_mist_enabled: bool | None = None
self.warm_mist_level: int | None = None
Expand Down Expand Up @@ -289,6 +303,15 @@ def supports_nightlight_brightness(self) -> bool:
"""Return True if the humidifier supports nightlight brightness."""
return HumidifierFeatures.NIGHTLIGHT_BRIGHTNESS in self.features

@property
def supports_rgb_nightlight(self) -> bool:
"""Return True if the humidifier supports RGB nightlight.

Returns:
bool: True if RGB nightlight is supported, False otherwise.
"""
return HumidifierFeatures.RGB_NIGHTLIGHT in self.features

@property
def supports_drying_mode(self) -> bool:
"""Return True if the humidifier supports drying mode."""
Expand Down Expand Up @@ -461,6 +484,33 @@ async def toggle_nightlight(self, toggle: bool | None = None) -> bool:
logger.error('Nightlight has not been configured.')
return False

async def set_rgb_nightlight(
self,
power: bool | None = None,
brightness: int | None = None,
red: int | None = None,
green: int | None = None,
blue: int | None = None,
) -> bool:
"""Set RGB nightlight state and color.

Args:
power: Turn nightlight on (True) or off (False).
brightness: Brightness level (0-100).
red: Red color value (0-255).
green: Green color value (0-255).
blue: Blue color value (0-255).

Returns:
bool: Success of request.
"""
del power, brightness, red, green, blue
if not self.supports_rgb_nightlight:
logger.error('RGB Nightlight is not supported for this device.')
return False
logger.error('RGB Nightlight has not been configured.')
return False

async def set_warm_level(self, warm_level: int) -> bool:
"""Set Humidifier Warm Level.

Expand Down
18 changes: 18 additions & 0 deletions src/pyvesync/base_devices/outlet_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,12 +271,24 @@ def supports_energy(self) -> bool:
"""
return OutletFeatures.ENERGY_MONITOR in self.features

@property
def supports_energy_history(self) -> bool:
"""Return True if device supports energy history retrieval.

Returns:
bool: True if device supports energy history, False otherwise.
"""
return OutletFeatures.ENERGY_HISTORY in self.features

async def get_weekly_energy(self) -> None:
"""Build weekly energy history dictionary.

The data is stored in the `device.state.weekly_history` attribute
as a `ResponseEnergyResult` object.
"""
if not self.supports_energy_history:
logger.debug('Device does not support energy history.')
return
await self._get_energy_history(EnergyIntervals.WEEK)

async def get_monthly_energy(self) -> None:
Expand All @@ -285,6 +297,9 @@ async def get_monthly_energy(self) -> None:
The data is stored in the `device.state.monthly_history` attribute
as a `ResponseEnergyResult` object.
"""
if not self.supports_energy_history:
logger.debug('Device does not support energy history.')
return
await self._get_energy_history(EnergyIntervals.MONTH)

async def get_yearly_energy(self) -> None:
Expand All @@ -293,6 +308,9 @@ async def get_yearly_energy(self) -> None:
The data is stored in the `device.state.yearly_history` attribute
as a `ResponseEnergyResult` object.
"""
if not self.supports_energy_history:
logger.debug('Device does not support energy history.')
return
await self._get_energy_history(EnergyIntervals.YEAR)

async def update_energy(self) -> None:
Expand Down
8 changes: 8 additions & 0 deletions src/pyvesync/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@
KELVIN_MIN = 2700
KELVIN_MAX = 6500

# RGB nightlight constants
RGB_STALE_DATA_TIMEOUT = 180 # Seconds to ignore stale API data after setting values
RGB_FULL_BRIGHTNESS = 100 # Full brightness percentage


class ProductLines(StrEnum):
"""High level product line."""
Expand Down Expand Up @@ -497,6 +501,7 @@ class HumidifierFeatures(Features):
WARM_MIST: Warm mist status.
AUTO_STOP: Auto stop when target humidity is reached.
Different from auto, which adjusts fan level to maintain humidity.
RGB_NIGHTLIGHT: RGB nightlight with color control.
"""

ONOFF = 'onoff'
Expand All @@ -507,6 +512,7 @@ class HumidifierFeatures(Features):
AUTO_STOP = 'auto_stop'
NIGHTLIGHT_BRIGHTNESS = 'nightlight_brightness'
DRYING_MODE = 'drying_mode'
RGB_NIGHTLIGHT = 'rgb_nightlight'


class PurifierFeatures(Features):
Expand Down Expand Up @@ -573,11 +579,13 @@ class OutletFeatures(Features):
Attributes:
ONOFF: Device on/off status.
ENERGY_MONITOR: Energy monitor status.
ENERGY_HISTORY: Energy history retrieval support.
NIGHTLIGHT: Nightlight status.
"""

ONOFF = 'onoff'
ENERGY_MONITOR = 'energy_monitor'
ENERGY_HISTORY = 'energy_history'
NIGHTLIGHT = 'nightlight'


Expand Down
Loading