Skip to content

Commit 59fabb8

Browse files
committed
Squashed commit of the following:
commit b3c8f54d396067506ca83f3b3637b292dff52db8 Author: Ben Lebherz <git@benleb.de> Date: Sat Sep 4 12:27:06 2021 +0200 implement the dau commit 7b951cd810912ac6bf41b911672a27a663b9a506 Merge: 1024d83 b21e908 Author: Ben Lebherz <git@benleb.de> Date: Thu Sep 2 18:04:05 2021 +0200 Merge branch 'dev' into dau * dev: remove duplicated state attr in battery sensor remove unneeded None's use mac address if a device has somehow no serial number use tag id as "serial" of the pet commit 1024d83279d0c47e63d0096a97cf11721f19bda7 Author: Ben Lebherz <git@benleb.de> Date: Tue Aug 31 23:50:57 2021 +0200 start dau implementation
1 parent b21e908 commit 59fabb8

File tree

3 files changed

+189
-251
lines changed

3 files changed

+189
-251
lines changed

__init__.py

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
from random import choice
77
from typing import Any
88

9+
import async_timeout
910
from homeassistant.config_entries import ConfigEntry
1011
from homeassistant.const import CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME
1112
from homeassistant.core import HomeAssistant
13+
from homeassistant.exceptions import ConfigEntryAuthFailed
1214
from homeassistant.helpers import config_validation as cv
1315
from homeassistant.helpers.aiohttp_client import async_get_clientsession
14-
from homeassistant.helpers.dispatcher import async_dispatcher_send
15-
from homeassistant.helpers.event import async_track_time_interval
16+
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
1617
from surepy import Surepy
1718
from surepy.enums import LockState
1819
from surepy.exceptions import SurePetcareAuthenticationError, SurePetcareError
@@ -26,7 +27,6 @@
2627
SERVICE_SET_LOCK_STATE,
2728
SPC,
2829
SURE_API_TIMEOUT,
29-
TOPIC_UPDATE,
3030
)
3131

3232
_LOGGER = logging.getLogger(__name__)
@@ -84,6 +84,30 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
8484
return False
8585

8686
spc = SurePetcareAPI(hass, entry, surepy)
87+
88+
async def async_update_data():
89+
90+
try:
91+
# asyncio.TimeoutError and aiohttp.ClientError already handled
92+
93+
async with async_timeout.timeout(20):
94+
return await spc.surepy.get_entities(refresh=True)
95+
96+
except SurePetcareAuthenticationError as err:
97+
raise ConfigEntryAuthFailed from err
98+
except SurePetcareError as err:
99+
raise UpdateFailed(f"Error communicating with API: {err}") from err
100+
101+
spc.coordinator = DataUpdateCoordinator(
102+
hass,
103+
_LOGGER,
104+
name="sureha_sensors",
105+
update_method=async_update_data,
106+
update_interval=timedelta(seconds=150),
107+
)
108+
109+
await spc.coordinator.async_config_entry_first_refresh()
110+
87111
hass.data[DOMAIN][SPC] = spc
88112

89113
return await spc.async_setup()
@@ -97,26 +121,13 @@ def __init__(
97121
) -> None:
98122
"""Initialize the Sure Petcare object."""
99123

124+
self.coordinator: DataUpdateCoordinator
125+
100126
self.hass = hass
101127
self.config_entry = config_entry
102128
self.surepy = surepy
103-
self.states: dict[int, Any] = {}
104129

105-
async def async_update(self, _: Any = None) -> None:
106-
"""Get the latest data from Sure Petcare."""
107-
108-
try:
109-
self.states = await self.surepy.get_entities(refresh=True)
110-
_LOGGER.info(
111-
"🐾 \x1b[38;2;0;255;0m·\x1b[0m successfully updated %d entities",
112-
len(self.states),
113-
)
114-
except SurePetcareError as error:
115-
_LOGGER.error(
116-
"🐾 \x1b[38;2;255;26;102m·\x1b[0m unable to fetch data: %s", error
117-
)
118-
119-
async_dispatcher_send(self.hass, TOPIC_UPDATE)
130+
self.states: dict[int, Any] = {}
120131

121132
async def set_lock_state(self, flap_id: int, state: str) -> None:
122133
"""Update the lock state of a flap."""
@@ -144,10 +155,6 @@ async def async_setup(self) -> bool:
144155
_LOGGER.info(" \x1b[38;2;255;26;102m·\x1b[0m" * 30)
145156
_LOGGER.info("")
146157

147-
await self.async_update()
148-
149-
async_track_time_interval(self.hass, self.async_update, SCAN_INTERVAL)
150-
151158
self.hass.async_add_job(
152159
self.hass.config_entries.async_forward_entry_setup( # type: ignore
153160
self.config_entry, "binary_sensor"
@@ -171,7 +178,8 @@ async def handle_set_lock_state(call: Any) -> None:
171178
await self.set_lock_state(
172179
call.data[ATTR_FLAP_ID], call.data[ATTR_LOCK_STATE]
173180
)
174-
await self.async_update()
181+
182+
await self.coordinator.async_request_refresh()
175183

176184
lock_state_service_schema = vol.Schema(
177185
{

binary_sensor.py

Lines changed: 45 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@
1010
BinarySensorEntity,
1111
)
1212
from homeassistant.config_entries import ConfigEntry
13-
from homeassistant.core import HomeAssistant, callback
14-
from homeassistant.helpers.dispatcher import async_dispatcher_connect
15-
from surepy.entities import PetLocation, SurepyEntity
13+
from homeassistant.core import HomeAssistant
14+
from homeassistant.helpers.update_coordinator import CoordinatorEntity
15+
from surepy.entities import SurepyEntity
16+
from surepy.entities.devices import Hub as SureHub
1617
from surepy.entities.pet import Pet as SurePet
1718
from surepy.enums import EntityType, Location
1819

1920
# pylint: disable=relative-beyond-top-level
2021
from . import SurePetcareAPI
21-
from .const import DOMAIN, SPC, SURE_MANUFACTURER, TOPIC_UPDATE
22+
from .const import DOMAIN, SPC, SURE_MANUFACTURER
2223

2324
PARALLEL_UPDATES = 2
2425

@@ -45,13 +46,13 @@ async def async_setup_entry(
4546

4647
spc: SurePetcareAPI = hass.data[DOMAIN][SPC]
4748

48-
for surepy_entity in spc.states.values():
49+
for surepy_entity in spc.coordinator.data.values():
4950

5051
if surepy_entity.type == EntityType.PET:
51-
entities.append(Pet(surepy_entity.id, spc))
52+
entities.append(Pet(spc.coordinator, surepy_entity.id, spc))
5253

5354
elif surepy_entity.type == EntityType.HUB:
54-
entities.append(Hub(surepy_entity.id, spc))
55+
entities.append(Hub(spc.coordinator, surepy_entity.id, spc))
5556

5657
# connectivity
5758
elif surepy_entity.type in [
@@ -60,28 +61,32 @@ async def async_setup_entry(
6061
EntityType.FEEDER,
6162
EntityType.FELAQUA,
6263
]:
63-
entities.append(DeviceConnectivity(surepy_entity.id, spc))
64+
entities.append(DeviceConnectivity(spc.coordinator, surepy_entity.id, spc))
6465

6566
async_add_entities(entities, True)
6667

6768

68-
class SurePetcareBinarySensor(BinarySensorEntity): # type: ignore
69+
class SurePetcareBinarySensor(CoordinatorEntity, BinarySensorEntity):
6970
"""A binary sensor implementation for Sure Petcare Entities."""
7071

7172
_attr_should_poll = False
7273

7374
def __init__(
7475
self,
76+
coordinator,
7577
_id: int,
7678
spc: SurePetcareAPI,
7779
device_class: str,
7880
):
7981
"""Initialize a Sure Petcare binary sensor."""
82+
super().__init__(coordinator)
8083

8184
self._id: int = _id
8285
self._spc: SurePetcareAPI = spc
8386

84-
self._surepy_entity: SurepyEntity = self._spc.states[self._id]
87+
self._coordinator = coordinator
88+
89+
self._surepy_entity: SurepyEntity = self._coordinator.data[self._id]
8590
self._state: Any = self._surepy_entity.raw_data().get("status", {})
8691

8792
type_name = self._surepy_entity.type.name.replace("_", " ").title()
@@ -120,7 +125,7 @@ def device_info(self):
120125

121126
device = {
122127
"identifiers": {(DOMAIN, self._id)},
123-
"name": self._surepy_entity.name.capitalize(), # type: ignore
128+
"name": self._surepy_entity.name.capitalize(),
124129
"manufacturer": SURE_MANUFACTURER,
125130
"model": model,
126131
}
@@ -145,46 +150,13 @@ def device_info(self):
145150

146151
return device
147152

148-
@callback
149-
def _async_update(self) -> None:
150-
"""Get the latest data and update the state."""
151-
152-
self._surepy_entity = self._spc.states[self._id]
153-
self._state = self._surepy_entity.raw_data()["status"]
154-
155-
_LOGGER.debug(
156-
"🐾 \x1b[38;2;0;255;0m·\x1b[0m %s updated",
157-
self._attr_name.replace(
158-
f"{self._surepy_entity.type.name.replace('_', ' ').title()} ", ""
159-
),
160-
)
161-
162-
async def async_added_to_hass(self) -> None:
163-
"""Register callbacks."""
164-
165-
self.async_on_remove(
166-
async_dispatcher_connect(self.hass, TOPIC_UPDATE, self._async_update)
167-
)
168-
169-
@callback
170-
def update() -> None:
171-
"""Update the state."""
172-
self.async_schedule_update_ha_state(True)
173-
174-
# pylint: disable=attribute-defined-outside-init
175-
self._async_unsub_dispatcher_connect = async_dispatcher_connect(
176-
self.hass, TOPIC_UPDATE, update
177-
)
178-
179-
self._async_update()
180-
181153

182154
class Hub(SurePetcareBinarySensor):
183155
"""Sure Petcare Pet."""
184156

185-
def __init__(self, _id: int, spc: SurePetcareAPI) -> None:
157+
def __init__(self, coordinator, _id: int, spc: SurePetcareAPI) -> None:
186158
"""Initialize a Sure Petcare Hub."""
187-
super().__init__(_id, spc, DEVICE_CLASS_CONNECTIVITY)
159+
super().__init__(coordinator, _id, spc, DEVICE_CLASS_CONNECTIVITY)
188160

189161
if self._attr_device_info:
190162
self._attr_device_info["identifiers"] = {(DOMAIN, str(self._id))}
@@ -195,30 +167,30 @@ def __init__(self, _id: int, spc: SurePetcareAPI) -> None:
195167
def is_on(self) -> bool:
196168
"""Return True if the hub is on."""
197169

198-
if self._state:
170+
hub: SureHub
171+
172+
if hub := self.coordinator.data[self._id]:
173+
199174
self._attr_extra_state_attributes = {
200-
"led_mode": int(self._surepy_entity.raw_data()["status"]["led_mode"]),
201-
"pairing_mode": bool(
202-
self._surepy_entity.raw_data()["status"]["pairing_mode"]
203-
),
175+
"led_mode": int(hub.raw_data()["status"]["led_mode"]),
176+
"pairing_mode": bool(hub.raw_data()["status"]["pairing_mode"]),
204177
}
205178

206-
return bool(self._state["online"])
179+
return bool(hub.online)
207180

208181

209182
class Pet(SurePetcareBinarySensor):
210183
"""Sure Petcare Pet."""
211184

212-
def __init__(self, _id: int, spc: SurePetcareAPI) -> None:
185+
def __init__(self, coordinator, _id: int, spc: SurePetcareAPI) -> None:
213186
"""Initialize a Sure Petcare Pet."""
214-
super().__init__(_id, spc, DEVICE_CLASS_PRESENCE)
187+
super().__init__(coordinator, _id, spc, DEVICE_CLASS_PRESENCE)
215188

216189
self._surepy_entity: SurePet
217-
self._state: PetLocation
218190

219191
self._attr_entity_picture = self._surepy_entity.photo_url
220192

221-
if self._state:
193+
if self._surepy_entity:
222194
self._attr_extra_state_attributes = {
223195
"since": self._surepy_entity.location.since,
224196
"where": self._surepy_entity.location.where,
@@ -228,49 +200,34 @@ def __init__(self, _id: int, spc: SurePetcareAPI) -> None:
228200
@property
229201
def is_on(self) -> bool:
230202
"""Return True if the pet is at home."""
231-
return self._attr_is_on
232-
233-
@callback
234-
def _async_update(self) -> None:
235-
"""Get the latest data and update the state."""
236-
237-
self._surepy_entity = self._spc.states[self._id]
238-
self._state = self._surepy_entity.location
239-
240-
try:
241-
self._attr_is_on: bool = bool(
242-
Location(self._surepy_entity.location.where) == Location.INSIDE
243-
)
244-
except (KeyError, TypeError):
245-
self._attr_is_on: bool = False
246-
247-
_LOGGER.debug(
248-
"🐾 \x1b[38;2;0;255;0m·\x1b[0m %s updated",
249-
self._attr_name.replace(
250-
f"{self._surepy_entity.type.name.replace('_', ' ').title()} ", ""
251-
),
252-
)
203+
pet: SurePet
204+
if pet := self.coordinator.data[self._id]:
205+
return bool(Location(pet.location.where) == Location.INSIDE)
253206

254207

255208
class DeviceConnectivity(SurePetcareBinarySensor):
256209
"""Sure Petcare Pet."""
257210

258-
def __init__(self, _id: int, spc: SurePetcareAPI) -> None:
211+
def __init__(self, coordinator, _id: int, spc: SurePetcareAPI) -> None:
259212
"""Initialize a Sure Petcare device connectivity sensor."""
260-
super().__init__(_id, spc, DEVICE_CLASS_CONNECTIVITY)
213+
super().__init__(coordinator, _id, spc, DEVICE_CLASS_CONNECTIVITY)
261214

262215
self._attr_name = f"{self._name} Connectivity"
263216
self._attr_unique_id = (
264217
f"{self._surepy_entity.household_id}-{self._id}-connectivity"
265218
)
266219

267-
if self._state:
268-
self._attr_extra_state_attributes = {
269-
"device_rssi": f'{self._state["signal"]["device_rssi"]:.2f}',
270-
"hub_rssi": f'{self._state["signal"]["hub_rssi"]:.2f}',
220+
@property
221+
def extra_state_attributes(self) -> dict[str, Any]:
222+
if (data := self._surepy_entity.raw_data()) and (state := data.get("status")):
223+
return {
224+
"device_rssi": f'{state["signal"]["device_rssi"]:.2f}',
225+
"hub_rssi": f'{state["signal"]["hub_rssi"]:.2f}',
271226
}
272227

273-
@callback
274-
def _async_update(self) -> None:
275-
super()._async_update()
276-
self._attr_is_on = bool(self._attr_extra_state_attributes)
228+
return {}
229+
230+
@property
231+
def is_on(self) -> bool:
232+
"""Return True if the pet is at home."""
233+
return bool(self.extra_state_attributes)

0 commit comments

Comments
 (0)