Skip to content
This repository has been archived by the owner on Dec 21, 2023. It is now read-only.

Commit

Permalink
Fix/api calls (#138)
Browse files Browse the repository at this point in the history
* add update coordinator

* down to 30s coordinator update.

* fix style.

* bump pyvesync

* bump pyvesync

* sourcery refactor.

* Fix missing vs_mode_auto (#143)

* Update manifest.json
  • Loading branch information
vlebourl authored Apr 28, 2023
1 parent 8044649 commit 2e2f77a
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 88 deletions.
28 changes: 26 additions & 2 deletions custom_components/vesync/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"""VeSync integration."""
import logging
from datetime import timedelta

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from pyvesync.vesync import VeSync

from .common import async_process_devices
Expand Down Expand Up @@ -53,13 +55,35 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
_LOGGER.error("Unable to login to the VeSync server")
return False

device_dict = await async_process_devices(hass, manager)

forward_setup = hass.config_entries.async_forward_entry_setup

hass.data[DOMAIN] = {config_entry.entry_id: {}}
hass.data[DOMAIN][config_entry.entry_id][VS_MANAGER] = manager

# Create a DataUpdateCoordinator for the manager
async def async_update_data():
"""Fetch data from API endpoint."""
try:
await hass.async_add_executor_job(manager.update)
except Exception as err:
raise UpdateFailed(f"Update failed: {err}")

coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name="vesync",
update_method=async_update_data,
update_interval=timedelta(seconds=30),
)

# Fetch initial data so we have data when entities subscribe
await coordinator.async_refresh()

# Store the coordinator instance in hass.data
hass.data[DOMAIN][config_entry.entry_id]["coordinator"] = coordinator

device_dict = await async_process_devices(hass, manager)

for p, vs_p in PLATFORMS.items():
hass.data[DOMAIN][config_entry.entry_id][vs_p] = []
if device_dict[vs_p]:
Expand Down
18 changes: 11 additions & 7 deletions custom_components/vesync/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,39 +21,43 @@ async def async_setup_entry(
) -> None:
"""Set up binary sensors."""

coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]

@callback
def discover(devices):
"""Add new devices to platform."""
_setup_entities(devices, async_add_entities)
_setup_entities(devices, async_add_entities, coordinator)

config_entry.async_on_unload(
async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_BINARY_SENSORS), discover)
)

_setup_entities(
hass.data[DOMAIN][config_entry.entry_id][VS_BINARY_SENSORS], async_add_entities
hass.data[DOMAIN][config_entry.entry_id][VS_BINARY_SENSORS],
async_add_entities,
coordinator,
)


@callback
def _setup_entities(devices, async_add_entities):
def _setup_entities(devices, async_add_entities, coordinator):
"""Check if device is online and add entity."""
entities = []
for dev in devices:
if has_feature(dev, "details", "water_lacks"):
entities.append(VeSyncOutOfWaterSensor(dev))
entities.append(VeSyncOutOfWaterSensor(dev, coordinator))
if has_feature(dev, "details", "water_tank_lifted"):
entities.append(VeSyncWaterTankLiftedSensor(dev))
entities.append(VeSyncWaterTankLiftedSensor(dev, coordinator))

async_add_entities(entities, update_before_add=True)


class VeSyncBinarySensorEntity(VeSyncBaseEntity, BinarySensorEntity):
"""Representation of a binary sensor describing diagnostics of a VeSync humidifier."""

def __init__(self, humidifier):
def __init__(self, humidifier, coordinator):
"""Initialize the VeSync humidifier device."""
super().__init__(humidifier)
super().__init__(humidifier, coordinator)
self.smarthumidifier = humidifier

@property
Expand Down
19 changes: 13 additions & 6 deletions custom_components/vesync/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from homeassistant.components.diagnostics import async_redact_data
from homeassistant.helpers.entity import Entity, ToggleEntity
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from pyvesync.vesyncfan import model_features

from .const import (
Expand Down Expand Up @@ -48,7 +49,6 @@ async def async_process_devices(hass, manager):
VS_BINARY_SENSORS: [],
}

await hass.async_add_executor_job(manager.update)
redacted = async_redact_data(
{k: [d.__dict__ for d in v] for k, v in manager._dev_list.items()},
["cid", "uuid", "mac_id"],
Expand Down Expand Up @@ -105,12 +105,13 @@ async def async_process_devices(hass, manager):
return devices


class VeSyncBaseEntity(Entity):
class VeSyncBaseEntity(CoordinatorEntity, Entity):
"""Base class for VeSync Entity Representations."""

def __init__(self, device):
def __init__(self, device, coordinator):
"""Initialize the VeSync device."""
self.device = device
super().__init__(coordinator, context=device)

@property
def base_unique_id(self):
Expand Down Expand Up @@ -152,14 +153,20 @@ def device_info(self):
"sw_version": self.device.current_firm_version,
}

def update(self):
"""Update vesync device."""
self.device.update()
async def async_added_to_hass(self):
"""When entity is added to hass."""
self.async_on_remove(
self.coordinator.async_add_listener(self.async_write_ha_state)
)


class VeSyncDevice(VeSyncBaseEntity, ToggleEntity):
"""Base class for VeSync Device Representations."""

def __init__(self, device, coordinator):
"""Initialize the VeSync device."""
super().__init__(device, coordinator)

@property
def is_on(self):
"""Return True if device is on."""
Expand Down
18 changes: 12 additions & 6 deletions custom_components/vesync/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,32 +33,38 @@ async def async_setup_entry(
) -> None:
"""Set up the VeSync fan platform."""

coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]

@callback
def discover(devices):
"""Add new devices to platform."""
_setup_entities(devices, async_add_entities)
_setup_entities(devices, async_add_entities, coordinator)

config_entry.async_on_unload(
async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_FANS), discover)
)

_setup_entities(
hass.data[DOMAIN][config_entry.entry_id][VS_FANS], async_add_entities
hass.data[DOMAIN][config_entry.entry_id][VS_FANS],
async_add_entities,
coordinator,
)


@callback
def _setup_entities(devices, async_add_entities):
def _setup_entities(devices, async_add_entities, coordinator):
"""Check if device is online and add entity."""
async_add_entities([VeSyncFanHA(dev) for dev in devices], update_before_add=True)
async_add_entities(
[VeSyncFanHA(dev, coordinator) for dev in devices], update_before_add=True
)


class VeSyncFanHA(VeSyncDevice, FanEntity):
"""Representation of a VeSync fan."""

def __init__(self, fan):
def __init__(self, fan, coordinator):
"""Initialize the VeSync fan device."""
super().__init__(fan)
super().__init__(fan, coordinator)
self.smartfan = fan
self._speed_range = (1, 1)
self._attr_preset_modes = [VS_MODE_MANUAL, VS_MODE_AUTO, VS_MODE_SLEEP]
Expand Down
33 changes: 20 additions & 13 deletions custom_components/vesync/humidifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
DOMAIN,
VS_DISCOVERY,
VS_HUMIDIFIERS,
VS_MODE_AUTO,
VS_MODE_HUMIDITY,
VS_MODE_MANUAL,
VS_MODE_SLEEP,
Expand All @@ -36,8 +37,9 @@


VS_TO_HA_MODE_MAP = {
VS_MODE_MANUAL: MODE_NORMAL,
VS_MODE_AUTO: MODE_AUTO,
VS_MODE_HUMIDITY: MODE_AUTO,
VS_MODE_MANUAL: MODE_NORMAL,
VS_MODE_SLEEP: MODE_SLEEP,
}

Expand All @@ -51,25 +53,30 @@ async def async_setup_entry(
) -> None:
"""Set up the VeSync humidifier platform."""

coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]

@callback
def discover(devices):
"""Add new devices to platform."""
_setup_entities(devices, async_add_entities)
_setup_entities(devices, async_add_entities, coordinator)

config_entry.async_on_unload(
async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_HUMIDIFIERS), discover)
)

_setup_entities(
hass.data[DOMAIN][config_entry.entry_id][VS_HUMIDIFIERS], async_add_entities
hass.data[DOMAIN][config_entry.entry_id][VS_HUMIDIFIERS],
async_add_entities,
coordinator,
)


@callback
def _setup_entities(devices, async_add_entities):
def _setup_entities(devices, async_add_entities, coordinator):
"""Check if device is online and add entity."""
async_add_entities(
[VeSyncHumidifierHA(dev) for dev in devices], update_before_add=True
[VeSyncHumidifierHA(dev, coordinator) for dev in devices],
update_before_add=True,
)


Expand All @@ -93,9 +100,9 @@ class VeSyncHumidifierHA(VeSyncDevice, HumidifierEntity):
_attr_max_humidity = MAX_HUMIDITY
_attr_min_humidity = MIN_HUMIDITY

def __init__(self, humidifier: VeSyncHumid200300S):
def __init__(self, humidifier: VeSyncHumid200300S, coordinator):
"""Initialize the VeSync humidifier device."""
super().__init__(humidifier)
super().__init__(humidifier, coordinator)
self.smarthumidifier = humidifier

@property
Expand Down Expand Up @@ -157,21 +164,21 @@ def set_humidity(self, humidity: int) -> None:
raise ValueError(
"{humidity} is not between {self.min_humidity} and {self.max_humidity} (inclusive)"
)
success = self.smarthumidifier.set_humidity(humidity)
if not success:
if self.smarthumidifier.set_humidity(humidity):
self.schedule_update_ha_state()
else:
raise ValueError("An error occurred while setting humidity.")
self.schedule_update_ha_state()

def set_mode(self, mode: str) -> None:
"""Set the mode of the device."""
if mode not in self.available_modes:
raise ValueError(
"{mode} is not one of the valid available modes: {self.available_modes}"
)
success = self.smarthumidifier.set_humidity_mode(_get_vs_mode(mode))
if not success:
if self.smarthumidifier.set_humidity_mode(_get_vs_mode(mode)):
self.schedule_update_ha_state()
else:
raise ValueError("An error occurred while setting mode.")
self.schedule_update_ha_state()

def turn_on(
self,
Expand Down
32 changes: 24 additions & 8 deletions custom_components/vesync/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,31 +27,35 @@ async def async_setup_entry(
) -> None:
"""Set up lights."""

coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]

@callback
def discover(devices):
"""Add new devices to platform."""
_setup_entities(devices, async_add_entities)
_setup_entities(devices, async_add_entities, coordinator)

config_entry.async_on_unload(
async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_LIGHTS), discover)
)

_setup_entities(
hass.data[DOMAIN][config_entry.entry_id][VS_LIGHTS], async_add_entities
hass.data[DOMAIN][config_entry.entry_id][VS_LIGHTS],
async_add_entities,
coordinator,
)


@callback
def _setup_entities(devices, async_add_entities):
def _setup_entities(devices, async_add_entities, coordinator):
"""Check if device is online and add entity."""
entities = []
for dev in devices:
if DEV_TYPE_TO_HA.get(dev.device_type) in ("walldimmer", "bulb-dimmable"):
entities.append(VeSyncDimmableLightHA(dev))
entities.append(VeSyncDimmableLightHA(dev, coordinator))
if DEV_TYPE_TO_HA.get(dev.device_type) in ("bulb-tunable-white",):
entities.append(VeSyncTunableWhiteLightHA(dev))
entities.append(VeSyncTunableWhiteLightHA(dev, coordinator))
if hasattr(dev, "night_light") and dev.night_light:
entities.append(VeSyncNightLightHA(dev))
entities.append(VeSyncNightLightHA(dev, coordinator))

async_add_entities(entities, update_before_add=True)

Expand Down Expand Up @@ -84,6 +88,10 @@ def _ha_brightness_to_vesync(ha_brightness):
class VeSyncBaseLight(VeSyncDevice, LightEntity):
"""Base class for VeSync Light Devices Representations."""

def __init_(self, light, coordinator):
"""Initialize the VeSync light device."""
super().__init__(light, coordinator)

@property
def brightness(self):
"""Get light brightness."""
Expand Down Expand Up @@ -132,6 +140,10 @@ def turn_on(self, **kwargs):
class VeSyncDimmableLightHA(VeSyncBaseLight, LightEntity):
"""Representation of a VeSync dimmable light device."""

def __init__(self, device, coordinator):
"""Initialize the VeSync dimmable light device."""
super().__init__(device, coordinator)

@property
def color_mode(self):
"""Set color mode for this entity."""
Expand All @@ -146,6 +158,10 @@ def supported_color_modes(self):
class VeSyncTunableWhiteLightHA(VeSyncBaseLight, LightEntity):
"""Representation of a VeSync Tunable White Light device."""

def __init__(self, device, coordinator):
"""Initialize the VeSync Tunable White Light device."""
super().__init__(device, coordinator)

@property
def color_temp(self):
"""Get device white temperature."""
Expand Down Expand Up @@ -197,9 +213,9 @@ def supported_color_modes(self):
class VeSyncNightLightHA(VeSyncDimmableLightHA):
"""Representation of the night light on a VeSync device."""

def __init__(self, device):
def __init__(self, device, coordinator):
"""Initialize the VeSync device."""
super().__init__(device)
super().__init__(device, coordinator)
self.device = device
self.has_brightness = has_feature(
self.device, "details", "night_light_brightness"
Expand Down
2 changes: 1 addition & 1 deletion custom_components/vesync/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/vlebourl/custom_vesync",
"requirements": ["pyvesync==2.1.6"],
"version": "0.2.5"
"version": "1.0.0"
}
Loading

0 comments on commit 2e2f77a

Please sign in to comment.