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

Commit 2e2f77a

Browse files
authored
Fix/api calls (#138)
* 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
1 parent 8044649 commit 2e2f77a

File tree

10 files changed

+205
-88
lines changed

10 files changed

+205
-88
lines changed

custom_components/vesync/__init__.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
"""VeSync integration."""
22
import logging
3+
from datetime import timedelta
34

45
from homeassistant.config_entries import ConfigEntry
56
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
67
from homeassistant.core import HomeAssistant, ServiceCall
78
from homeassistant.helpers import config_validation as cv
89
from homeassistant.helpers.dispatcher import async_dispatcher_send
10+
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
911
from pyvesync.vesync import VeSync
1012

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

56-
device_dict = await async_process_devices(hass, manager)
57-
5858
forward_setup = hass.config_entries.async_forward_entry_setup
5959

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

63+
# Create a DataUpdateCoordinator for the manager
64+
async def async_update_data():
65+
"""Fetch data from API endpoint."""
66+
try:
67+
await hass.async_add_executor_job(manager.update)
68+
except Exception as err:
69+
raise UpdateFailed(f"Update failed: {err}")
70+
71+
coordinator = DataUpdateCoordinator(
72+
hass,
73+
_LOGGER,
74+
name="vesync",
75+
update_method=async_update_data,
76+
update_interval=timedelta(seconds=30),
77+
)
78+
79+
# Fetch initial data so we have data when entities subscribe
80+
await coordinator.async_refresh()
81+
82+
# Store the coordinator instance in hass.data
83+
hass.data[DOMAIN][config_entry.entry_id]["coordinator"] = coordinator
84+
85+
device_dict = await async_process_devices(hass, manager)
86+
6387
for p, vs_p in PLATFORMS.items():
6488
hass.data[DOMAIN][config_entry.entry_id][vs_p] = []
6589
if device_dict[vs_p]:

custom_components/vesync/binary_sensor.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,39 +21,43 @@ async def async_setup_entry(
2121
) -> None:
2222
"""Set up binary sensors."""
2323

24+
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
25+
2426
@callback
2527
def discover(devices):
2628
"""Add new devices to platform."""
27-
_setup_entities(devices, async_add_entities)
29+
_setup_entities(devices, async_add_entities, coordinator)
2830

2931
config_entry.async_on_unload(
3032
async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_BINARY_SENSORS), discover)
3133
)
3234

3335
_setup_entities(
34-
hass.data[DOMAIN][config_entry.entry_id][VS_BINARY_SENSORS], async_add_entities
36+
hass.data[DOMAIN][config_entry.entry_id][VS_BINARY_SENSORS],
37+
async_add_entities,
38+
coordinator,
3539
)
3640

3741

3842
@callback
39-
def _setup_entities(devices, async_add_entities):
43+
def _setup_entities(devices, async_add_entities, coordinator):
4044
"""Check if device is online and add entity."""
4145
entities = []
4246
for dev in devices:
4347
if has_feature(dev, "details", "water_lacks"):
44-
entities.append(VeSyncOutOfWaterSensor(dev))
48+
entities.append(VeSyncOutOfWaterSensor(dev, coordinator))
4549
if has_feature(dev, "details", "water_tank_lifted"):
46-
entities.append(VeSyncWaterTankLiftedSensor(dev))
50+
entities.append(VeSyncWaterTankLiftedSensor(dev, coordinator))
4751

4852
async_add_entities(entities, update_before_add=True)
4953

5054

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

54-
def __init__(self, humidifier):
58+
def __init__(self, humidifier, coordinator):
5559
"""Initialize the VeSync humidifier device."""
56-
super().__init__(humidifier)
60+
super().__init__(humidifier, coordinator)
5761
self.smarthumidifier = humidifier
5862

5963
@property

custom_components/vesync/common.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from homeassistant.components.diagnostics import async_redact_data
55
from homeassistant.helpers.entity import Entity, ToggleEntity
6+
from homeassistant.helpers.update_coordinator import CoordinatorEntity
67
from pyvesync.vesyncfan import model_features
78

89
from .const import (
@@ -48,7 +49,6 @@ async def async_process_devices(hass, manager):
4849
VS_BINARY_SENSORS: [],
4950
}
5051

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

107107

108-
class VeSyncBaseEntity(Entity):
108+
class VeSyncBaseEntity(CoordinatorEntity, Entity):
109109
"""Base class for VeSync Entity Representations."""
110110

111-
def __init__(self, device):
111+
def __init__(self, device, coordinator):
112112
"""Initialize the VeSync device."""
113113
self.device = device
114+
super().__init__(coordinator, context=device)
114115

115116
@property
116117
def base_unique_id(self):
@@ -152,14 +153,20 @@ def device_info(self):
152153
"sw_version": self.device.current_firm_version,
153154
}
154155

155-
def update(self):
156-
"""Update vesync device."""
157-
self.device.update()
156+
async def async_added_to_hass(self):
157+
"""When entity is added to hass."""
158+
self.async_on_remove(
159+
self.coordinator.async_add_listener(self.async_write_ha_state)
160+
)
158161

159162

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

166+
def __init__(self, device, coordinator):
167+
"""Initialize the VeSync device."""
168+
super().__init__(device, coordinator)
169+
163170
@property
164171
def is_on(self):
165172
"""Return True if device is on."""

custom_components/vesync/fan.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,32 +33,38 @@ async def async_setup_entry(
3333
) -> None:
3434
"""Set up the VeSync fan platform."""
3535

36+
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
37+
3638
@callback
3739
def discover(devices):
3840
"""Add new devices to platform."""
39-
_setup_entities(devices, async_add_entities)
41+
_setup_entities(devices, async_add_entities, coordinator)
4042

4143
config_entry.async_on_unload(
4244
async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_FANS), discover)
4345
)
4446

4547
_setup_entities(
46-
hass.data[DOMAIN][config_entry.entry_id][VS_FANS], async_add_entities
48+
hass.data[DOMAIN][config_entry.entry_id][VS_FANS],
49+
async_add_entities,
50+
coordinator,
4751
)
4852

4953

5054
@callback
51-
def _setup_entities(devices, async_add_entities):
55+
def _setup_entities(devices, async_add_entities, coordinator):
5256
"""Check if device is online and add entity."""
53-
async_add_entities([VeSyncFanHA(dev) for dev in devices], update_before_add=True)
57+
async_add_entities(
58+
[VeSyncFanHA(dev, coordinator) for dev in devices], update_before_add=True
59+
)
5460

5561

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

59-
def __init__(self, fan):
65+
def __init__(self, fan, coordinator):
6066
"""Initialize the VeSync fan device."""
61-
super().__init__(fan)
67+
super().__init__(fan, coordinator)
6268
self.smartfan = fan
6369
self._speed_range = (1, 1)
6470
self._attr_preset_modes = [VS_MODE_MANUAL, VS_MODE_AUTO, VS_MODE_SLEEP]

custom_components/vesync/humidifier.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
DOMAIN,
2323
VS_DISCOVERY,
2424
VS_HUMIDIFIERS,
25+
VS_MODE_AUTO,
2526
VS_MODE_HUMIDITY,
2627
VS_MODE_MANUAL,
2728
VS_MODE_SLEEP,
@@ -36,8 +37,9 @@
3637

3738

3839
VS_TO_HA_MODE_MAP = {
39-
VS_MODE_MANUAL: MODE_NORMAL,
40+
VS_MODE_AUTO: MODE_AUTO,
4041
VS_MODE_HUMIDITY: MODE_AUTO,
42+
VS_MODE_MANUAL: MODE_NORMAL,
4143
VS_MODE_SLEEP: MODE_SLEEP,
4244
}
4345

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

56+
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
57+
5458
@callback
5559
def discover(devices):
5660
"""Add new devices to platform."""
57-
_setup_entities(devices, async_add_entities)
61+
_setup_entities(devices, async_add_entities, coordinator)
5862

5963
config_entry.async_on_unload(
6064
async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_HUMIDIFIERS), discover)
6165
)
6266

6367
_setup_entities(
64-
hass.data[DOMAIN][config_entry.entry_id][VS_HUMIDIFIERS], async_add_entities
68+
hass.data[DOMAIN][config_entry.entry_id][VS_HUMIDIFIERS],
69+
async_add_entities,
70+
coordinator,
6571
)
6672

6773

6874
@callback
69-
def _setup_entities(devices, async_add_entities):
75+
def _setup_entities(devices, async_add_entities, coordinator):
7076
"""Check if device is online and add entity."""
7177
async_add_entities(
72-
[VeSyncHumidifierHA(dev) for dev in devices], update_before_add=True
78+
[VeSyncHumidifierHA(dev, coordinator) for dev in devices],
79+
update_before_add=True,
7380
)
7481

7582

@@ -93,9 +100,9 @@ class VeSyncHumidifierHA(VeSyncDevice, HumidifierEntity):
93100
_attr_max_humidity = MAX_HUMIDITY
94101
_attr_min_humidity = MIN_HUMIDITY
95102

96-
def __init__(self, humidifier: VeSyncHumid200300S):
103+
def __init__(self, humidifier: VeSyncHumid200300S, coordinator):
97104
"""Initialize the VeSync humidifier device."""
98-
super().__init__(humidifier)
105+
super().__init__(humidifier, coordinator)
99106
self.smarthumidifier = humidifier
100107

101108
@property
@@ -157,21 +164,21 @@ def set_humidity(self, humidity: int) -> None:
157164
raise ValueError(
158165
"{humidity} is not between {self.min_humidity} and {self.max_humidity} (inclusive)"
159166
)
160-
success = self.smarthumidifier.set_humidity(humidity)
161-
if not success:
167+
if self.smarthumidifier.set_humidity(humidity):
168+
self.schedule_update_ha_state()
169+
else:
162170
raise ValueError("An error occurred while setting humidity.")
163-
self.schedule_update_ha_state()
164171

165172
def set_mode(self, mode: str) -> None:
166173
"""Set the mode of the device."""
167174
if mode not in self.available_modes:
168175
raise ValueError(
169176
"{mode} is not one of the valid available modes: {self.available_modes}"
170177
)
171-
success = self.smarthumidifier.set_humidity_mode(_get_vs_mode(mode))
172-
if not success:
178+
if self.smarthumidifier.set_humidity_mode(_get_vs_mode(mode)):
179+
self.schedule_update_ha_state()
180+
else:
173181
raise ValueError("An error occurred while setting mode.")
174-
self.schedule_update_ha_state()
175182

176183
def turn_on(
177184
self,

custom_components/vesync/light.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,31 +27,35 @@ async def async_setup_entry(
2727
) -> None:
2828
"""Set up lights."""
2929

30+
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
31+
3032
@callback
3133
def discover(devices):
3234
"""Add new devices to platform."""
33-
_setup_entities(devices, async_add_entities)
35+
_setup_entities(devices, async_add_entities, coordinator)
3436

3537
config_entry.async_on_unload(
3638
async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_LIGHTS), discover)
3739
)
3840

3941
_setup_entities(
40-
hass.data[DOMAIN][config_entry.entry_id][VS_LIGHTS], async_add_entities
42+
hass.data[DOMAIN][config_entry.entry_id][VS_LIGHTS],
43+
async_add_entities,
44+
coordinator,
4145
)
4246

4347

4448
@callback
45-
def _setup_entities(devices, async_add_entities):
49+
def _setup_entities(devices, async_add_entities, coordinator):
4650
"""Check if device is online and add entity."""
4751
entities = []
4852
for dev in devices:
4953
if DEV_TYPE_TO_HA.get(dev.device_type) in ("walldimmer", "bulb-dimmable"):
50-
entities.append(VeSyncDimmableLightHA(dev))
54+
entities.append(VeSyncDimmableLightHA(dev, coordinator))
5155
if DEV_TYPE_TO_HA.get(dev.device_type) in ("bulb-tunable-white",):
52-
entities.append(VeSyncTunableWhiteLightHA(dev))
56+
entities.append(VeSyncTunableWhiteLightHA(dev, coordinator))
5357
if hasattr(dev, "night_light") and dev.night_light:
54-
entities.append(VeSyncNightLightHA(dev))
58+
entities.append(VeSyncNightLightHA(dev, coordinator))
5559

5660
async_add_entities(entities, update_before_add=True)
5761

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

91+
def __init_(self, light, coordinator):
92+
"""Initialize the VeSync light device."""
93+
super().__init__(light, coordinator)
94+
8795
@property
8896
def brightness(self):
8997
"""Get light brightness."""
@@ -132,6 +140,10 @@ def turn_on(self, **kwargs):
132140
class VeSyncDimmableLightHA(VeSyncBaseLight, LightEntity):
133141
"""Representation of a VeSync dimmable light device."""
134142

143+
def __init__(self, device, coordinator):
144+
"""Initialize the VeSync dimmable light device."""
145+
super().__init__(device, coordinator)
146+
135147
@property
136148
def color_mode(self):
137149
"""Set color mode for this entity."""
@@ -146,6 +158,10 @@ def supported_color_modes(self):
146158
class VeSyncTunableWhiteLightHA(VeSyncBaseLight, LightEntity):
147159
"""Representation of a VeSync Tunable White Light device."""
148160

161+
def __init__(self, device, coordinator):
162+
"""Initialize the VeSync Tunable White Light device."""
163+
super().__init__(device, coordinator)
164+
149165
@property
150166
def color_temp(self):
151167
"""Get device white temperature."""
@@ -197,9 +213,9 @@ def supported_color_modes(self):
197213
class VeSyncNightLightHA(VeSyncDimmableLightHA):
198214
"""Representation of the night light on a VeSync device."""
199215

200-
def __init__(self, device):
216+
def __init__(self, device, coordinator):
201217
"""Initialize the VeSync device."""
202-
super().__init__(device)
218+
super().__init__(device, coordinator)
203219
self.device = device
204220
self.has_brightness = has_feature(
205221
self.device, "details", "night_light_brightness"

custom_components/vesync/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@
1313
"iot_class": "cloud_polling",
1414
"issue_tracker": "https://github.com/vlebourl/custom_vesync",
1515
"requirements": ["pyvesync==2.1.6"],
16-
"version": "0.2.5"
16+
"version": "1.0.0"
1717
}

0 commit comments

Comments
 (0)