mirror of
https://github.com/home-assistant/core.git
synced 2026-02-15 07:36:16 +00:00
Bump pyvesync to 3.4.1 (#160573)
Co-authored-by: Joe Trabulsy <jtrabulsy@gmail.com>
This commit is contained in:
@@ -32,7 +32,6 @@ VS_HUMIDIFIER_MODE_SLEEP = "sleep"
|
||||
|
||||
VS_FAN_MODE_AUTO = "auto"
|
||||
VS_FAN_MODE_SLEEP = "sleep"
|
||||
VS_FAN_MODE_ADVANCED_SLEEP = "advancedSleep"
|
||||
VS_FAN_MODE_TURBO = "turbo"
|
||||
VS_FAN_MODE_PET = "pet"
|
||||
VS_FAN_MODE_MANUAL = "manual"
|
||||
@@ -42,7 +41,6 @@ VS_FAN_MODE_NORMAL = "normal"
|
||||
VS_FAN_MODE_PRESET_LIST_HA = [
|
||||
VS_FAN_MODE_AUTO,
|
||||
VS_FAN_MODE_SLEEP,
|
||||
VS_FAN_MODE_ADVANCED_SLEEP,
|
||||
VS_FAN_MODE_TURBO,
|
||||
VS_FAN_MODE_PET,
|
||||
VS_FAN_MODE_NORMAL,
|
||||
|
||||
@@ -5,8 +5,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from pyvesync.base_devices.vesyncbasedevice import VeSyncBaseDevice
|
||||
from pyvesync.device_container import DeviceContainer
|
||||
from pyvesync.base_devices import VeSyncFanBase, VeSyncPurifier
|
||||
|
||||
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
@@ -22,7 +21,6 @@ from .common import is_fan, is_purifier, rgetattr
|
||||
from .const import (
|
||||
VS_DEVICES,
|
||||
VS_DISCOVERY,
|
||||
VS_FAN_MODE_ADVANCED_SLEEP,
|
||||
VS_FAN_MODE_AUTO,
|
||||
VS_FAN_MODE_MANUAL,
|
||||
VS_FAN_MODE_NORMAL,
|
||||
@@ -40,7 +38,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
VS_TO_HA_MODE_MAP = {
|
||||
VS_FAN_MODE_AUTO: VS_FAN_MODE_AUTO,
|
||||
VS_FAN_MODE_SLEEP: VS_FAN_MODE_SLEEP,
|
||||
VS_FAN_MODE_ADVANCED_SLEEP: "advanced_sleep",
|
||||
VS_FAN_MODE_TURBO: VS_FAN_MODE_TURBO,
|
||||
VS_FAN_MODE_PET: VS_FAN_MODE_PET,
|
||||
VS_FAN_MODE_MANUAL: VS_FAN_MODE_MANUAL,
|
||||
@@ -60,26 +57,33 @@ async def async_setup_entry(
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
@callback
|
||||
def discover(devices: list[VeSyncBaseDevice]) -> None:
|
||||
def discover(devices: list[VeSyncFanBase | VeSyncPurifier]) -> None:
|
||||
"""Add new devices to platform."""
|
||||
_setup_entities(devices, async_add_entities, coordinator)
|
||||
_setup_entities(
|
||||
[dev for dev in devices if is_fan(dev) or is_purifier(dev)],
|
||||
async_add_entities,
|
||||
coordinator,
|
||||
)
|
||||
|
||||
config_entry.async_on_unload(
|
||||
async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_DEVICES), discover)
|
||||
)
|
||||
|
||||
_setup_entities(
|
||||
config_entry.runtime_data.manager.devices, async_add_entities, coordinator
|
||||
config_entry.runtime_data.manager.devices.air_purifiers
|
||||
+ config_entry.runtime_data.manager.devices.fans,
|
||||
async_add_entities,
|
||||
coordinator,
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def _setup_entities(
|
||||
devices: DeviceContainer | list[VeSyncBaseDevice],
|
||||
devices: list[VeSyncFanBase | VeSyncPurifier],
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
coordinator: VeSyncDataCoordinator,
|
||||
) -> None:
|
||||
"""Check if device is fan and add entity."""
|
||||
"""Check if device is fan or purifier and add entity."""
|
||||
|
||||
async_add_entities(
|
||||
VeSyncFanHA(dev, coordinator)
|
||||
@@ -95,7 +99,7 @@ def _get_ha_mode(vs_mode: str) -> str | None:
|
||||
return ha_mode
|
||||
|
||||
|
||||
class VeSyncFanHA(VeSyncBaseEntity, FanEntity):
|
||||
class VeSyncFanHA(VeSyncBaseEntity[VeSyncFanBase | VeSyncPurifier], FanEntity):
|
||||
"""Representation of a VeSync fan."""
|
||||
|
||||
_attr_supported_features = (
|
||||
@@ -109,7 +113,7 @@ class VeSyncFanHA(VeSyncBaseEntity, FanEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device: VeSyncBaseDevice,
|
||||
device: VeSyncFanBase | VeSyncPurifier,
|
||||
coordinator: VeSyncDataCoordinator,
|
||||
) -> None:
|
||||
"""Initialize the fan."""
|
||||
@@ -179,7 +183,7 @@ class VeSyncFanHA(VeSyncBaseEntity, FanEntity):
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the state attributes of the fan."""
|
||||
attr = {}
|
||||
attr: dict[str, Any] = {}
|
||||
|
||||
if hasattr(self.device.state, "active_time"):
|
||||
attr["active_time"] = self.device.state.active_time
|
||||
@@ -219,37 +223,47 @@ class VeSyncFanHA(VeSyncBaseEntity, FanEntity):
|
||||
if percentage == 0:
|
||||
# Turning off is a special case: do not set speed or mode
|
||||
if not await self.device.turn_off():
|
||||
raise HomeAssistantError(
|
||||
"An error occurred while turning off: "
|
||||
+ self.device.last_response.message
|
||||
)
|
||||
if self.device.last_response:
|
||||
raise HomeAssistantError(
|
||||
"An error occurred while turning off: "
|
||||
+ self.device.last_response.message
|
||||
)
|
||||
raise HomeAssistantError("Failed to turn off fan, no response found.")
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
# If the fan is off, turn it on first
|
||||
if not self.device.is_on:
|
||||
if not await self.device.turn_on():
|
||||
raise HomeAssistantError(
|
||||
"An error occurred while turning on: "
|
||||
+ self.device.last_response.message
|
||||
)
|
||||
if self.device.last_response:
|
||||
raise HomeAssistantError(
|
||||
"An error occurred while turning on: "
|
||||
+ self.device.last_response.message
|
||||
)
|
||||
raise HomeAssistantError("Failed to turn on fan, no response found.")
|
||||
|
||||
# Switch to manual mode if not already set
|
||||
if self.device.state.mode not in (VS_FAN_MODE_MANUAL, VS_FAN_MODE_NORMAL):
|
||||
if not await self.device.set_manual_mode():
|
||||
if self.device.last_response:
|
||||
raise HomeAssistantError(
|
||||
"An error occurred while setting manual mode."
|
||||
+ self.device.last_response.message
|
||||
)
|
||||
raise HomeAssistantError(
|
||||
"An error occurred while setting manual mode."
|
||||
+ self.device.last_response.message
|
||||
"Failed to set manual mode, no response found."
|
||||
)
|
||||
|
||||
# Calculate the speed level and set it
|
||||
if not await self.device.set_fan_speed(
|
||||
percentage_to_ordered_list_item(self.device.fan_levels, percentage)
|
||||
):
|
||||
raise HomeAssistantError(
|
||||
"An error occurred while changing fan speed: "
|
||||
+ self.device.last_response.message
|
||||
)
|
||||
if self.device.last_response:
|
||||
raise HomeAssistantError(
|
||||
"An error occurred while changing fan speed: "
|
||||
+ self.device.last_response.message
|
||||
)
|
||||
raise HomeAssistantError("Failed to set fan speed, no response found.")
|
||||
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -270,17 +284,19 @@ class VeSyncFanHA(VeSyncBaseEntity, FanEntity):
|
||||
success = await self.device.set_auto_mode()
|
||||
elif vs_mode == VS_FAN_MODE_SLEEP:
|
||||
success = await self.device.set_sleep_mode()
|
||||
elif vs_mode == VS_FAN_MODE_ADVANCED_SLEEP:
|
||||
success = await self.device.set_advanced_sleep_mode()
|
||||
elif vs_mode == VS_FAN_MODE_PET:
|
||||
success = await self.device.set_pet_mode()
|
||||
if hasattr(self.device, "set_pet_mode"):
|
||||
success = await self.device.set_pet_mode()
|
||||
elif vs_mode == VS_FAN_MODE_TURBO:
|
||||
success = await self.device.set_turbo_mode()
|
||||
elif vs_mode == VS_FAN_MODE_NORMAL:
|
||||
success = await self.device.set_normal_mode()
|
||||
if hasattr(self.device, "set_normal_mode"):
|
||||
success = await self.device.set_normal_mode()
|
||||
|
||||
if not success:
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
if self.device.last_response:
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
raise HomeAssistantError("Failed to set preset mode, no response found.")
|
||||
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -297,7 +313,9 @@ class VeSyncFanHA(VeSyncBaseEntity, FanEntity):
|
||||
if percentage is None:
|
||||
success = await self.device.turn_on()
|
||||
if not success:
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
if self.device.last_response:
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
raise HomeAssistantError("Failed to turn on fan, no response found.")
|
||||
self.async_write_ha_state()
|
||||
else:
|
||||
await self.async_set_percentage(percentage)
|
||||
@@ -306,12 +324,21 @@ class VeSyncFanHA(VeSyncBaseEntity, FanEntity):
|
||||
"""Turn the device off."""
|
||||
success = await self.device.turn_off()
|
||||
if not success:
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
if self.device.last_response:
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
raise HomeAssistantError("Failed to turn off fan, no response found.")
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_oscillate(self, oscillating: bool) -> None:
|
||||
"""Set oscillation."""
|
||||
success = await self.device.toggle_oscillation(oscillating)
|
||||
if not success:
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
self.async_write_ha_state()
|
||||
if hasattr(self.device, "toggle_oscillation"):
|
||||
success = await self.device.toggle_oscillation(oscillating)
|
||||
if not success:
|
||||
if self.device.last_response:
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
raise HomeAssistantError(
|
||||
"Failed to set oscillation, no response found."
|
||||
)
|
||||
self.async_write_ha_state()
|
||||
else:
|
||||
raise HomeAssistantError("Oscillation not supported by this device.")
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from pyvesync.base_devices.vesyncbasedevice import VeSyncBaseDevice
|
||||
from pyvesync.base_devices.humidifier_base import VeSyncHumidifier
|
||||
|
||||
from homeassistant.components.humidifier import (
|
||||
MODE_AUTO,
|
||||
@@ -17,6 +17,7 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .common import is_humidifier
|
||||
from .const import (
|
||||
VS_DEVICES,
|
||||
VS_DISCOVERY,
|
||||
@@ -51,16 +52,24 @@ async def async_setup_entry(
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
@callback
|
||||
def discover(devices: list[VeSyncBaseDevice]) -> None:
|
||||
def discover(devices: list[VeSyncHumidifier]) -> None:
|
||||
"""Add new devices to platform."""
|
||||
_setup_entities(devices, async_add_entities, coordinator)
|
||||
_setup_entities(
|
||||
[dev for dev in devices if is_humidifier(dev)],
|
||||
async_add_entities,
|
||||
coordinator,
|
||||
)
|
||||
|
||||
config_entry.async_on_unload(
|
||||
async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_DEVICES), discover)
|
||||
)
|
||||
|
||||
_setup_entities(
|
||||
config_entry.runtime_data.manager.devices.humidifiers,
|
||||
[
|
||||
dev
|
||||
for dev in config_entry.runtime_data.manager.devices.humidifiers
|
||||
if is_humidifier(dev)
|
||||
],
|
||||
async_add_entities,
|
||||
coordinator,
|
||||
)
|
||||
@@ -68,7 +77,7 @@ async def async_setup_entry(
|
||||
|
||||
@callback
|
||||
def _setup_entities(
|
||||
devices: list[VeSyncBaseDevice],
|
||||
devices: list[VeSyncHumidifier],
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
coordinator: VeSyncDataCoordinator,
|
||||
) -> None:
|
||||
@@ -83,7 +92,7 @@ def _get_ha_mode(vs_mode: str) -> str | None:
|
||||
return ha_mode
|
||||
|
||||
|
||||
class VeSyncHumidifierHA(VeSyncBaseEntity, HumidifierEntity):
|
||||
class VeSyncHumidifierHA(VeSyncBaseEntity[VeSyncHumidifier], HumidifierEntity):
|
||||
"""Representation of a VeSync humidifier."""
|
||||
|
||||
# The base VeSyncBaseEntity has _attr_has_entity_name and this is to follow the device name
|
||||
@@ -95,7 +104,7 @@ class VeSyncHumidifierHA(VeSyncBaseEntity, HumidifierEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device: VeSyncBaseDevice,
|
||||
device: VeSyncHumidifier,
|
||||
coordinator: VeSyncDataCoordinator,
|
||||
) -> None:
|
||||
"""Initialize the VeSyncHumidifierHA device."""
|
||||
@@ -119,8 +128,8 @@ class VeSyncHumidifierHA(VeSyncBaseEntity, HumidifierEntity):
|
||||
|
||||
self._available_modes.sort()
|
||||
|
||||
def _get_vs_mode(self, ha_mode: str) -> str | None:
|
||||
return self._ha_to_vs_mode_map.get(ha_mode)
|
||||
def _get_vs_mode(self, ha_mode: str) -> str:
|
||||
return self._ha_to_vs_mode_map[ha_mode]
|
||||
|
||||
@property
|
||||
def available_modes(self) -> list[str]:
|
||||
@@ -153,7 +162,9 @@ class VeSyncHumidifierHA(VeSyncBaseEntity, HumidifierEntity):
|
||||
async def async_set_humidity(self, humidity: int) -> None:
|
||||
"""Set the target humidity of the device."""
|
||||
if not await self.device.set_humidity(humidity):
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
if self.device.last_response:
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
raise HomeAssistantError("Failed to set humidity.")
|
||||
|
||||
async def async_set_mode(self, mode: str) -> None:
|
||||
"""Set the mode of the device."""
|
||||
@@ -161,8 +172,13 @@ class VeSyncHumidifierHA(VeSyncBaseEntity, HumidifierEntity):
|
||||
raise HomeAssistantError(
|
||||
f"Invalid mode {mode}. Available modes: {self.available_modes}"
|
||||
)
|
||||
set_mode = self._get_vs_mode(mode)
|
||||
if set_mode is None:
|
||||
raise HomeAssistantError(f"Could not map mode {mode} to VeSync mode.")
|
||||
if not await self.device.set_mode(self._get_vs_mode(mode)):
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
if self.device.last_response:
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
raise HomeAssistantError("Failed to set mode.")
|
||||
|
||||
if mode == MODE_SLEEP:
|
||||
# We successfully changed the mode. Consider it a success even if display operation fails.
|
||||
@@ -174,7 +190,9 @@ class VeSyncHumidifierHA(VeSyncBaseEntity, HumidifierEntity):
|
||||
"""Turn the device on."""
|
||||
success = await self.device.turn_on()
|
||||
if not success:
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
if self.device.last_response:
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
raise HomeAssistantError("Failed to turn on humidifier.")
|
||||
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -182,7 +200,9 @@ class VeSyncHumidifierHA(VeSyncBaseEntity, HumidifierEntity):
|
||||
"""Turn the device off."""
|
||||
success = await self.device.turn_off()
|
||||
if not success:
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
if self.device.last_response:
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
raise HomeAssistantError("Failed to turn off humidifier.")
|
||||
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ from typing import Any
|
||||
|
||||
from pyvesync.base_devices.bulb_base import VeSyncBulb
|
||||
from pyvesync.base_devices.switch_base import VeSyncSwitch
|
||||
from pyvesync.base_devices.vesyncbasedevice import VeSyncBaseDevice
|
||||
from pyvesync.device_container import DeviceContainer
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
@@ -40,7 +38,7 @@ async def async_setup_entry(
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
@callback
|
||||
def discover(devices: list[VeSyncBaseDevice]) -> None:
|
||||
def discover(devices: list[VeSyncBulb | VeSyncSwitch]) -> None:
|
||||
"""Add new devices to platform."""
|
||||
_setup_entities(devices, async_add_entities, coordinator)
|
||||
|
||||
@@ -49,13 +47,16 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
_setup_entities(
|
||||
config_entry.runtime_data.manager.devices, async_add_entities, coordinator
|
||||
config_entry.runtime_data.manager.devices.bulbs
|
||||
+ config_entry.runtime_data.manager.devices.switches,
|
||||
async_add_entities,
|
||||
coordinator,
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def _setup_entities(
|
||||
devices: DeviceContainer | list[VeSyncBaseDevice],
|
||||
devices: list[VeSyncBulb | VeSyncSwitch],
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
coordinator: VeSyncDataCoordinator,
|
||||
) -> None:
|
||||
@@ -73,9 +74,10 @@ def _setup_entities(
|
||||
async_add_entities(entities, update_before_add=True)
|
||||
|
||||
|
||||
class VeSyncBaseLightHA(VeSyncBaseEntity, LightEntity):
|
||||
class VeSyncBaseLightHA(VeSyncBaseEntity[VeSyncSwitch | VeSyncBulb], LightEntity):
|
||||
"""Base class for VeSync Light Devices Representations."""
|
||||
|
||||
device: VeSyncBulb | VeSyncSwitch
|
||||
_attr_name = None
|
||||
|
||||
@property
|
||||
@@ -86,26 +88,24 @@ class VeSyncBaseLightHA(VeSyncBaseEntity, LightEntity):
|
||||
@property
|
||||
def brightness(self) -> int:
|
||||
"""Get light brightness."""
|
||||
# get value from pyvesync library api,
|
||||
result = self.device.state.brightness
|
||||
try:
|
||||
# check for validity of brightness value received
|
||||
brightness_value = int(result)
|
||||
except ValueError:
|
||||
# deal if any unexpected/non numeric value
|
||||
if self.device.state.brightness is None:
|
||||
_LOGGER.debug(
|
||||
"VeSync - received unexpected 'brightness' value from pyvesync api: %s",
|
||||
result,
|
||||
"VeSync - received unexpected 'brightness' value from pyvesync api of None"
|
||||
)
|
||||
return 0
|
||||
|
||||
# convert percent brightness to ha expected range
|
||||
return round((max(1, brightness_value) / 100) * 255)
|
||||
return round((max(1, self.device.state.brightness) / 100) * 255)
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the device on."""
|
||||
attribute_adjustment_only = False
|
||||
# set white temperature
|
||||
if self.color_mode == ColorMode.COLOR_TEMP and ATTR_COLOR_TEMP_KELVIN in kwargs:
|
||||
if (
|
||||
self.color_mode == ColorMode.COLOR_TEMP
|
||||
and ATTR_COLOR_TEMP_KELVIN in kwargs
|
||||
and hasattr(self.device, "set_color_temp")
|
||||
):
|
||||
# get white temperature from HA data
|
||||
color_temp = color_util.color_temperature_kelvin_to_mired(
|
||||
kwargs[ATTR_COLOR_TEMP_KELVIN]
|
||||
@@ -165,6 +165,7 @@ class VeSyncDimmableLightHA(VeSyncBaseLightHA, LightEntity):
|
||||
class VeSyncTunableWhiteLightHA(VeSyncBaseLightHA, LightEntity):
|
||||
"""Representation of a VeSync Tunable White Light device."""
|
||||
|
||||
device: VeSyncBulb
|
||||
_attr_color_mode = ColorMode.COLOR_TEMP
|
||||
_attr_min_color_temp_kelvin = 2700 # 370 Mireds
|
||||
_attr_max_color_temp_kelvin = 6500 # 153 Mireds
|
||||
@@ -173,24 +174,18 @@ class VeSyncTunableWhiteLightHA(VeSyncBaseLightHA, LightEntity):
|
||||
@property
|
||||
def color_temp_kelvin(self) -> int | None:
|
||||
"""Return the color temperature value in Kelvin."""
|
||||
# get value from pyvesync library api
|
||||
# pyvesync v3 provides BulbState.color_temp_kelvin() - possible to use that instead?
|
||||
result = self.device.state.color_temp
|
||||
try:
|
||||
# check for validity of brightness value received
|
||||
color_temp_value = int(result)
|
||||
except ValueError:
|
||||
# deal if any unexpected/non numeric value
|
||||
_LOGGER.debug(
|
||||
(
|
||||
"VeSync - received unexpected 'color_temp_pct' value from pyvesync"
|
||||
" api: %s"
|
||||
),
|
||||
result,
|
||||
)
|
||||
if hasattr(self.device.state, "color_temp") is False:
|
||||
return None
|
||||
|
||||
# pyvesync v3 provides BulbState.color_temp_kelvin() - possible to use that instead?
|
||||
if self.device.state.color_temp is None:
|
||||
_LOGGER.debug(
|
||||
"VeSync - received unexpected 'color_temp' value from pyvesync api of None"
|
||||
)
|
||||
return 0
|
||||
|
||||
# flip cold/warm
|
||||
color_temp_value = 100 - color_temp_value
|
||||
color_temp_value = 100 - self.device.state.color_temp
|
||||
# ensure value between 0-100
|
||||
color_temp_value = max(0, min(color_temp_value, 100))
|
||||
# convert percent value to Mireds
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/vesync",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pyvesync"],
|
||||
"requirements": ["pyvesync==3.3.3"]
|
||||
"requirements": ["pyvesync==3.4.1"]
|
||||
}
|
||||
|
||||
@@ -27,6 +27,20 @@ _LOGGER = logging.getLogger(__name__)
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
|
||||
def _mist_levels(device: VeSyncBaseDevice) -> list[int]:
|
||||
"""Check if the device supports mist level adjustment."""
|
||||
if is_humidifier(device):
|
||||
return device.mist_levels
|
||||
raise HomeAssistantError("Device does not support mist level adjustment.")
|
||||
|
||||
|
||||
def _set_mist_level(device: VeSyncBaseDevice, value: float) -> Awaitable[bool]:
|
||||
"""Set mist level on humidifier."""
|
||||
if is_humidifier(device):
|
||||
return device.set_mist_level(int(value))
|
||||
raise HomeAssistantError("Device does not support mist level adjustment.")
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class VeSyncNumberEntityDescription(NumberEntityDescription):
|
||||
"""Class to describe a Vesync number entity."""
|
||||
@@ -42,12 +56,12 @@ NUMBER_DESCRIPTIONS: list[VeSyncNumberEntityDescription] = [
|
||||
VeSyncNumberEntityDescription(
|
||||
key="mist_level",
|
||||
translation_key="mist_level",
|
||||
native_min_value_fn=lambda device: min(device.mist_levels),
|
||||
native_max_value_fn=lambda device: max(device.mist_levels),
|
||||
native_min_value_fn=lambda device: min(_mist_levels(device)),
|
||||
native_max_value_fn=lambda device: max(_mist_levels(device)),
|
||||
native_step=1,
|
||||
mode=NumberMode.SLIDER,
|
||||
exists_fn=is_humidifier,
|
||||
set_value_fn=lambda device, value: device.set_mist_level(value),
|
||||
set_value_fn=_set_mist_level,
|
||||
value_fn=lambda device: device.state.mist_virtual_level,
|
||||
)
|
||||
]
|
||||
|
||||
@@ -4,7 +4,7 @@ from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
|
||||
from pyvesync.base_devices.vesyncbasedevice import VeSyncBaseDevice
|
||||
from pyvesync.base_devices import VeSyncBaseDevice
|
||||
from pyvesync.device_container import DeviceContainer
|
||||
|
||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||
@@ -45,6 +45,27 @@ HA_TO_VS_HUMIDIFIER_NIGHT_LIGHT_LEVEL_MAP = {
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
|
||||
def _set_humidifier_nightlight(device: VeSyncBaseDevice, *args) -> Awaitable[bool]:
|
||||
"""Toggle humidifier nightlight on."""
|
||||
if is_humidifier(device):
|
||||
return device.set_nightlight_brightness(*args)
|
||||
raise HomeAssistantError("Device does not support toggling nightlight.")
|
||||
|
||||
|
||||
def _toggle_purifier_nightlight(device: VeSyncBaseDevice, *args) -> Awaitable[bool]:
|
||||
"""Toggle air purifier nightlight on."""
|
||||
if is_purifier(device):
|
||||
return device.set_nightlight_mode(*args)
|
||||
raise HomeAssistantError("Device does not support toggling nightlight.")
|
||||
|
||||
|
||||
def _toggle_outlet_nightlight(device: VeSyncBaseDevice, *args) -> Awaitable[bool]:
|
||||
"""Toggle outlet nightlight on."""
|
||||
if is_outlet(device) and device.supports_nightlight:
|
||||
return device.set_nightlight_state(*args)
|
||||
raise HomeAssistantError("Device does not support toggling nightlight.")
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class VeSyncSelectEntityDescription(SelectEntityDescription):
|
||||
"""Class to describe a Vesync select entity."""
|
||||
@@ -64,8 +85,8 @@ SELECT_DESCRIPTIONS: list[VeSyncSelectEntityDescription] = [
|
||||
exists_fn=lambda device: is_humidifier(device) and device.supports_nightlight,
|
||||
# The select_option service framework ensures that only options specified are
|
||||
# accepted. ServiceValidationError gets raised for invalid value.
|
||||
select_option_fn=lambda device, value: device.set_nightlight_brightness(
|
||||
HA_TO_VS_HUMIDIFIER_NIGHT_LIGHT_LEVEL_MAP.get(value, 0)
|
||||
select_option_fn=lambda device, value: _set_humidifier_nightlight(
|
||||
device, HA_TO_VS_HUMIDIFIER_NIGHT_LIGHT_LEVEL_MAP.get(value, 0)
|
||||
),
|
||||
# Reporting "off" as the choice for unhandled level.
|
||||
current_option_fn=lambda device: VS_TO_HA_HUMIDIFIER_NIGHT_LIGHT_LEVEL_MAP.get(
|
||||
@@ -84,7 +105,7 @@ SELECT_DESCRIPTIONS: list[VeSyncSelectEntityDescription] = [
|
||||
],
|
||||
icon="mdi:brightness-6",
|
||||
exists_fn=lambda device: is_purifier(device) and device.supports_nightlight,
|
||||
select_option_fn=lambda device, value: device.set_nightlight_mode(value),
|
||||
select_option_fn=_toggle_purifier_nightlight,
|
||||
current_option_fn=lambda device: device.state.nightlight_status,
|
||||
),
|
||||
# night_light for outlets
|
||||
@@ -98,7 +119,7 @@ SELECT_DESCRIPTIONS: list[VeSyncSelectEntityDescription] = [
|
||||
],
|
||||
icon="mdi:brightness-6",
|
||||
exists_fn=lambda device: is_outlet(device) and device.supports_nightlight,
|
||||
select_option_fn=lambda device, value: device.set_nightlight_state(value),
|
||||
select_option_fn=_toggle_outlet_nightlight,
|
||||
current_option_fn=lambda device: device.state.nightlight_status,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
"state_attributes": {
|
||||
"preset_mode": {
|
||||
"state": {
|
||||
"advanced_sleep": "Advanced sleep",
|
||||
"auto": "[%key:common::state::auto%]",
|
||||
"normal": "[%key:common::state::normal%]",
|
||||
"pet": "Pet",
|
||||
|
||||
@@ -5,7 +5,7 @@ from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any, Final
|
||||
|
||||
from pyvesync.base_devices.vesyncbasedevice import VeSyncBaseDevice
|
||||
from pyvesync.base_devices import VeSyncBaseDevice, VeSyncHumidifier
|
||||
from pyvesync.device_container import DeviceContainer
|
||||
|
||||
from homeassistant.components.switch import (
|
||||
@@ -28,6 +28,38 @@ _LOGGER = logging.getLogger(__name__)
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
|
||||
def _toggle_switch(device: VeSyncBaseDevice, *args) -> Awaitable[bool]:
|
||||
"""Toggle power on."""
|
||||
if args and args[0] is True and hasattr(device, "turn_on"):
|
||||
return device.turn_on()
|
||||
if args and args[0] is False and hasattr(device, "turn_off"):
|
||||
return device.turn_off()
|
||||
raise HomeAssistantError("Device does not support toggling power.")
|
||||
|
||||
|
||||
def _toggle_display(device: VeSyncBaseDevice, *args) -> Awaitable[bool]:
|
||||
"""Toggle display on."""
|
||||
if hasattr(device, "toggle_display"):
|
||||
return device.toggle_display(*args)
|
||||
raise HomeAssistantError("Device does not support toggling display.")
|
||||
|
||||
|
||||
def _toggle_child_lock(device: VeSyncBaseDevice, *args) -> Awaitable[bool]:
|
||||
"""Toggle child lock on."""
|
||||
if hasattr(device, "toggle_child_lock"):
|
||||
return device.toggle_child_lock(*args)
|
||||
raise HomeAssistantError("Device does not support toggling child lock.")
|
||||
|
||||
|
||||
def _toggle_auto_stop(device: VeSyncBaseDevice, *args) -> Awaitable[bool]:
|
||||
"""Toggle automatic stop on."""
|
||||
match device:
|
||||
case VeSyncHumidifier() as sw if hasattr(sw, "toggle_automatic_stop"):
|
||||
return sw.toggle_automatic_stop(*args)
|
||||
case _:
|
||||
raise HomeAssistantError("Device does not support toggling automatic stop.")
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class VeSyncSwitchEntityDescription(SwitchEntityDescription):
|
||||
"""A class that describes custom switch entities."""
|
||||
@@ -45,8 +77,8 @@ SENSOR_DESCRIPTIONS: Final[tuple[VeSyncSwitchEntityDescription, ...]] = (
|
||||
# Other types of wall switches support dimming. Those use light.py platform.
|
||||
exists_fn=lambda device: is_wall_switch(device) or is_outlet(device),
|
||||
name=None,
|
||||
on_fn=lambda device: device.turn_on(),
|
||||
off_fn=lambda device: device.turn_off(),
|
||||
on_fn=lambda device: _toggle_switch(device, True),
|
||||
off_fn=lambda device: _toggle_switch(device, False),
|
||||
),
|
||||
VeSyncSwitchEntityDescription(
|
||||
key="display",
|
||||
@@ -55,16 +87,16 @@ SENSOR_DESCRIPTIONS: Final[tuple[VeSyncSwitchEntityDescription, ...]] = (
|
||||
lambda device: rgetattr(device, "state.display_set_status") is not None
|
||||
),
|
||||
translation_key="display",
|
||||
on_fn=lambda device: device.toggle_display(True),
|
||||
off_fn=lambda device: device.toggle_display(False),
|
||||
on_fn=lambda device: _toggle_display(device, True),
|
||||
off_fn=lambda device: _toggle_display(device, False),
|
||||
),
|
||||
VeSyncSwitchEntityDescription(
|
||||
key="child_lock",
|
||||
is_on=lambda device: device.state.child_lock,
|
||||
exists_fn=(lambda device: rgetattr(device, "state.child_lock") is not None),
|
||||
translation_key="child_lock",
|
||||
on_fn=lambda device: device.toggle_child_lock(True),
|
||||
off_fn=lambda device: device.toggle_child_lock(False),
|
||||
on_fn=lambda device: _toggle_child_lock(device, True),
|
||||
off_fn=lambda device: _toggle_child_lock(device, False),
|
||||
),
|
||||
VeSyncSwitchEntityDescription(
|
||||
key="auto_off_config",
|
||||
@@ -73,8 +105,8 @@ SENSOR_DESCRIPTIONS: Final[tuple[VeSyncSwitchEntityDescription, ...]] = (
|
||||
lambda device: rgetattr(device, "state.automatic_stop_config") is not None
|
||||
),
|
||||
translation_key="auto_off_config",
|
||||
on_fn=lambda device: device.toggle_automatic_stop(True),
|
||||
off_fn=lambda device: device.toggle_automatic_stop(False),
|
||||
on_fn=lambda device: _toggle_auto_stop(device, True),
|
||||
off_fn=lambda device: _toggle_auto_stop(device, False),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -89,7 +121,7 @@ async def async_setup_entry(
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
@callback
|
||||
def discover(devices: list[VeSyncBaseDevice]) -> None:
|
||||
def discover(devices: DeviceContainer) -> None:
|
||||
"""Add new devices to platform."""
|
||||
_setup_entities(devices, async_add_entities, coordinator)
|
||||
|
||||
@@ -98,13 +130,15 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
_setup_entities(
|
||||
config_entry.runtime_data.manager.devices, async_add_entities, coordinator
|
||||
config_entry.runtime_data.manager.devices,
|
||||
async_add_entities,
|
||||
coordinator,
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def _setup_entities(
|
||||
devices: DeviceContainer | list[VeSyncBaseDevice],
|
||||
devices: DeviceContainer,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
coordinator: VeSyncDataCoordinator,
|
||||
) -> None:
|
||||
@@ -117,7 +151,7 @@ def _setup_entities(
|
||||
)
|
||||
|
||||
|
||||
class VeSyncSwitchEntity(SwitchEntity, VeSyncBaseEntity):
|
||||
class VeSyncSwitchEntity(SwitchEntity, VeSyncBaseEntity[VeSyncBaseDevice]):
|
||||
"""VeSync switch entity class."""
|
||||
|
||||
entity_description: VeSyncSwitchEntityDescription
|
||||
@@ -145,13 +179,17 @@ class VeSyncSwitchEntity(SwitchEntity, VeSyncBaseEntity):
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity off."""
|
||||
if not await self.entity_description.off_fn(self.device):
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
if self.device.last_response:
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
raise HomeAssistantError("Unknown error turning off device, no response.")
|
||||
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity on."""
|
||||
if not await self.entity_description.on_fn(self.device):
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
if self.device.last_response:
|
||||
raise HomeAssistantError(self.device.last_response.message)
|
||||
raise HomeAssistantError("Unknown error turning on device, no response.")
|
||||
|
||||
self.async_write_ha_state()
|
||||
|
||||
2
requirements_all.txt
generated
2
requirements_all.txt
generated
@@ -2656,7 +2656,7 @@ pyvera==0.3.16
|
||||
pyversasense==0.0.6
|
||||
|
||||
# homeassistant.components.vesync
|
||||
pyvesync==3.3.3
|
||||
pyvesync==3.4.1
|
||||
|
||||
# homeassistant.components.vizio
|
||||
pyvizio==0.1.61
|
||||
|
||||
2
requirements_test_all.txt
generated
2
requirements_test_all.txt
generated
@@ -2234,7 +2234,7 @@ pyuptimerobot==22.2.0
|
||||
pyvera==0.3.16
|
||||
|
||||
# homeassistant.components.vesync
|
||||
pyvesync==3.3.3
|
||||
pyvesync==3.4.1
|
||||
|
||||
# homeassistant.components.vizio
|
||||
pyvizio==0.1.61
|
||||
|
||||
@@ -115,17 +115,20 @@
|
||||
'to_json': 'Method',
|
||||
'to_jsonb': 'Method',
|
||||
'toggle_automatic_stop': 'Method',
|
||||
'toggle_child_lock': 'Method',
|
||||
'toggle_display': 'Method',
|
||||
'toggle_drying_mode': 'Method',
|
||||
'toggle_nightlight': 'Method',
|
||||
'toggle_switch': 'Method',
|
||||
'turn_off': 'Method',
|
||||
'turn_off_automatic_stop': 'Method',
|
||||
'turn_off_child_lock': 'Method',
|
||||
'turn_off_display': 'Method',
|
||||
'turn_off_drying_mode': 'Method',
|
||||
'turn_off_nightlight': 'Method',
|
||||
'turn_on': 'Method',
|
||||
'turn_on_automatic_stop': 'Method',
|
||||
'turn_on_child_lock': 'Method',
|
||||
'turn_on_display': 'Method',
|
||||
'turn_on_drying_mode': 'Method',
|
||||
'turn_on_nightlight': 'Method',
|
||||
|
||||
@@ -740,9 +740,9 @@
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'preset_modes': list([
|
||||
'advanced_sleep',
|
||||
'auto',
|
||||
'normal',
|
||||
'sleep',
|
||||
'turbo',
|
||||
]),
|
||||
}),
|
||||
@@ -783,15 +783,15 @@
|
||||
'active_time': None,
|
||||
'display_status': 'off',
|
||||
'friendly_name': 'SmartTowerFan',
|
||||
'mode': 'normal',
|
||||
'mode': <FanModes.NORMAL: 'normal'>,
|
||||
'oscillating': True,
|
||||
'percentage': 0,
|
||||
'percentage_step': 8.333333333333334,
|
||||
'preset_mode': 'normal',
|
||||
'preset_modes': list([
|
||||
'advanced_sleep',
|
||||
'auto',
|
||||
'normal',
|
||||
'sleep',
|
||||
'turbo',
|
||||
]),
|
||||
'supported_features': <FanEntityFeature: 59>,
|
||||
|
||||
@@ -143,8 +143,8 @@ async def test_turn_on_off_raises_error(
|
||||
[
|
||||
("normal", "pyvesync.devices.vesyncfan.VeSyncTowerFan.set_normal_mode"),
|
||||
(
|
||||
"advanced_sleep",
|
||||
"pyvesync.devices.vesyncfan.VeSyncTowerFan.set_advanced_sleep_mode",
|
||||
"sleep",
|
||||
"pyvesync.devices.vesyncfan.VeSyncTowerFan.set_sleep_mode",
|
||||
),
|
||||
("turbo", "pyvesync.devices.vesyncfan.VeSyncTowerFan.set_turbo_mode"),
|
||||
("auto", "pyvesync.devices.vesyncfan.VeSyncTowerFan.set_auto_mode"),
|
||||
|
||||
Reference in New Issue
Block a user