Skip to content

Commit fb88584

Browse files
authored
Merge pull request #59 from plugwise/DataUpdater
First working attempt at dataupdater
2 parents 0d36051 + c08f049 commit fb88584

File tree

6 files changed

+127
-211
lines changed

6 files changed

+127
-211
lines changed

custom_components/plugwise-beta/__init__.py

Lines changed: 62 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@
66
from typing import Optional
77
import voluptuous as vol
88

9+
import async_timeout
10+
911
from homeassistant.config_entries import ConfigEntry
10-
from homeassistant.core import HomeAssistant, callback
11-
from homeassistant.exceptions import PlatformNotReady
12+
from homeassistant.core import HomeAssistant
13+
from homeassistant.exceptions import ConfigEntryNotReady
1214
from homeassistant.helpers import device_registry as dr
1315
from homeassistant.helpers.aiohttp_client import async_get_clientsession
16+
from homeassistant.helpers.entity import Entity
1417
from homeassistant.helpers.event import async_track_time_interval
18+
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
1519
from Plugwise_Smile.Smile import Smile
1620

1721
from .const import DOMAIN
@@ -23,13 +27,12 @@
2327
SENSOR_PLATFORMS = ["sensor"]
2428
ALL_PLATFORMS = ["binary_sensor", "climate", "sensor", "switch"]
2529

26-
2730
async def async_setup(hass: HomeAssistant, config: dict):
2831
"""Set up the Plugwise platform."""
2932
return True
3033

3134

32-
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
35+
async def async_setup_entry(hass, entry):
3336
"""Set up Plugwise Smiles from a config entry."""
3437
websession = async_get_clientsession(hass, verify_ssl=False)
3538
api = Smile(
@@ -56,16 +59,36 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
5659
else:
5760
update_interval = timedelta(seconds=60)
5861

62+
async def async_update_data():
63+
"""Update data via API endpoint."""
64+
_LOGGER.debug("Updating Smile %s", api.smile_type)
65+
try:
66+
async with async_timeout.timeout(10):
67+
await api.full_update_device()
68+
_LOGGER.debug("Succesfully updated Smile %s", api.smile_type)
69+
return True
70+
except Smile.XMLDataMissingError:
71+
_LOGGER.debug("Updating Smile failed %s", api.smile_type)
72+
raise UpdateFailed("Smile update failed %s", api.smile_type)
73+
74+
5975
api.get_all_devices()
6076

77+
coordinator = DataUpdateCoordinator(
78+
hass,
79+
_LOGGER,
80+
name="Smile",
81+
update_method=async_update_data,
82+
update_interval=update_interval)
83+
84+
await coordinator.async_refresh()
85+
86+
if not coordinator.last_update_success:
87+
raise ConfigEntryNotReady
88+
6189
_LOGGER.debug("Async update interval %s", update_interval)
6290

63-
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
64-
"api": api,
65-
"updater": SmileDataUpdater(
66-
hass, "device", entry.entry_id, api, "full_update_device", update_interval
67-
),
68-
}
91+
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { "api": api, "coordinator": coordinator }
6992

7093
_LOGGER.debug("Gateway is %s", api.gateway_id)
7194

@@ -95,13 +118,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
95118
hass.config_entries.async_forward_entry_setup(entry, component)
96119
)
97120

98-
async def async_refresh_all(_):
99-
"""Refresh all Smile data."""
100-
for info in hass.data[DOMAIN].values():
101-
await info["updater"].async_refresh_all()
102-
103-
hass.services.async_register(DOMAIN, "update", async_refresh_all)
104-
105121
return True
106122

107123

@@ -121,61 +137,34 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
121137
return unload_ok
122138

123139

124-
class SmileDataUpdater:
125-
"""Data storage for single Smile API endpoint."""
126-
127-
def __init__(
128-
self,
129-
hass: HomeAssistant,
130-
data_type: str,
131-
config_entry_id: str,
132-
api: Smile,
133-
update_method: str,
134-
update_interval: timedelta,
135-
):
136-
"""Initialize global data updater."""
137-
self.hass = hass
138-
self.data_type = data_type
139-
self.config_entry_id = config_entry_id
140-
self.api = api
141-
self.update_method = update_method
142-
self.update_interval = update_interval
143-
self.listeners = []
144-
self._unsub_interval = None
145-
146-
@callback
147-
def async_add_listener(self, update_callback):
148-
"""Listen for data updates."""
149-
if not self.listeners:
150-
self._unsub_interval = async_track_time_interval(
151-
self.hass, self.async_refresh_all, self.update_interval
152-
)
153-
154-
self.listeners.append(update_callback)
155-
156-
@callback
157-
def async_remove_listener(self, update_callback):
158-
"""Remove data update."""
159-
self.listeners.remove(update_callback)
160-
161-
if not self.listeners:
162-
self._unsub_interval()
163-
self._unsub_interval = None
164-
165-
async def async_refresh_all(self, _now: Optional[int] = None) -> None:
166-
"""Time to update."""
167-
_LOGGER.debug("Smile updating with interval: %s", self.update_interval)
168-
if not self.listeners:
169-
_LOGGER.error("Smile has no listeners, not updating")
170-
return
171-
172-
_LOGGER.debug("Smile updating data using: %s", self.update_method)
140+
class SmileGateway(Entity):
141+
"""Represent Smile Gateway."""
173142

174-
try:
175-
await self.api.full_update_device()
176-
except Smile.XMLDataMissingError as e:
177-
_LOGGER.error("Smile update failed")
178-
raise e
143+
def __init__(self, api, coordinator):
144+
"""Initialise the sensor."""
145+
self._api = api
146+
self._coordinator = coordinator
179147

180-
for update_callback in self.listeners:
181-
update_callback()
148+
@property
149+
def should_poll(self):
150+
"""Return False, updates are controlled via coordinator."""
151+
return False
152+
153+
@property
154+
def available(self):
155+
"""Return True if entity is available."""
156+
return self._coordinator.last_update_success
157+
158+
async def async_added_to_hass(self):
159+
"""Subscribe to updates."""
160+
self.async_on_remove(
161+
self._coordinator.async_add_listener(self._process_data)
162+
)
163+
164+
def _process_data(self):
165+
"""Interpret and process API data."""
166+
raise NotImplementedError
167+
168+
async def async_update(self):
169+
"""Update the entity."""
170+
await self._coordinator.async_request_refresh()

custom_components/plugwise-beta/binary_sensor.py

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77
DEVICE_CLASS_OPENING,
88
BinarySensorDevice,
99
)
10-
1110
from homeassistant.const import STATE_OFF, STATE_ON
12-
from homeassistant.core import callback
1311

1412
from .const import (
1513
DOMAIN,
@@ -19,6 +17,8 @@
1917
WATER_ICON,
2018
)
2119

20+
from . import SmileGateway
21+
2222
BINARY_SENSOR_LIST = [
2323
"dhw_state",
2424
"slave_boiler_state",
@@ -31,7 +31,7 @@
3131
async def async_setup_entry(hass, config_entry, async_add_entities):
3232
"""Set up the Smile binary_sensors from a config entry."""
3333
api = hass.data[DOMAIN][config_entry.entry_id]["api"]
34-
updater = hass.data[DOMAIN][config_entry.entry_id]["updater"]
34+
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
3535

3636
devices = []
3737
binary_sensor_classes = [
@@ -51,7 +51,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
5151
devices.append(
5252
PwBinarySensor(
5353
api,
54-
updater,
54+
coordinator,
5555
device["name"],
5656
binary_sensor,
5757
dev_id,
@@ -63,13 +63,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
6363
async_add_entities(devices, True)
6464

6565

66-
class PwBinarySensor(BinarySensorDevice):
66+
class PwBinarySensor(SmileGateway, BinarySensorDevice):
6767
"""Representation of a Plugwise binary_sensor."""
6868

69-
def __init__(self, api, updater, name, binary_sensor, dev_id, model):
69+
def __init__(self, api, coordinator, name, binary_sensor, dev_id, model):
7070
"""Set up the Plugwise API."""
71+
super().__init__(api, coordinator)
72+
7173
self._api = api
72-
self._updater = updater
7374
self._dev_id = dev_id
7475
self._model = model
7576
self._name = name
@@ -94,30 +95,11 @@ def unique_id(self):
9495
"""Return a unique ID."""
9596
return self._unique_id
9697

97-
async def async_added_to_hass(self):
98-
"""Register callbacks."""
99-
self._updater.async_add_listener(self._update_callback)
100-
101-
async def async_will_remove_from_hass(self):
102-
"""Disconnect callbacks."""
103-
self._updater.async_remove_listener(self._update_callback)
104-
105-
@callback
106-
def _update_callback(self):
107-
"""Call update method."""
108-
self.update()
109-
self.async_write_ha_state()
110-
11198
@property
11299
def name(self):
113100
"""Return the name of the thermostat, if any."""
114101
return self._sensorname
115102

116-
@property
117-
def should_poll(self):
118-
"""No need to poll. Coordinator notifies entity of updates."""
119-
return False
120-
121103
@property
122104
def is_on(self):
123105
"""Return true if the binary sensor is on."""
@@ -160,7 +142,7 @@ def device_class(self):
160142
return DEVICE_CLASS_OPENING
161143
return "none"
162144

163-
def update(self):
145+
def _process_data(self):
164146
"""Update the entity."""
165147
_LOGGER.debug("Update binary_sensor called")
166148
data = self._api.get_device_data(self._dev_id)
@@ -172,3 +154,5 @@ def update(self):
172154
if isinstance(data[self._binary_sensor], float):
173155
self._is_on = data[self._binary_sensor] == 1.0
174156
self._is_on = data[self._binary_sensor]
157+
158+
self.async_write_ha_state()

0 commit comments

Comments
 (0)