Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit b3c8f54d396067506ca83f3b3637b292dff52db8
Author: Ben Lebherz <[email protected]>
Date:   Sat Sep 4 12:27:06 2021 +0200

    implement the dau

commit 7b951cd810912ac6bf41b911672a27a663b9a506
Merge: 1024d83 b21e908
Author: Ben Lebherz <[email protected]>
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 <[email protected]>
Date:   Tue Aug 31 23:50:57 2021 +0200

    start dau implementation
  • Loading branch information
benleb committed Sep 4, 2021
1 parent b21e908 commit 59fabb8
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 251 deletions.
56 changes: 32 additions & 24 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
from random import choice
from typing import Any

import async_timeout
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from surepy import Surepy
from surepy.enums import LockState
from surepy.exceptions import SurePetcareAuthenticationError, SurePetcareError
Expand All @@ -26,7 +27,6 @@
SERVICE_SET_LOCK_STATE,
SPC,
SURE_API_TIMEOUT,
TOPIC_UPDATE,
)

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

spc = SurePetcareAPI(hass, entry, surepy)

async def async_update_data():

try:
# asyncio.TimeoutError and aiohttp.ClientError already handled

async with async_timeout.timeout(20):
return await spc.surepy.get_entities(refresh=True)

except SurePetcareAuthenticationError as err:
raise ConfigEntryAuthFailed from err
except SurePetcareError as err:
raise UpdateFailed(f"Error communicating with API: {err}") from err

spc.coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name="sureha_sensors",
update_method=async_update_data,
update_interval=timedelta(seconds=150),
)

await spc.coordinator.async_config_entry_first_refresh()

hass.data[DOMAIN][SPC] = spc

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

self.coordinator: DataUpdateCoordinator

self.hass = hass
self.config_entry = config_entry
self.surepy = surepy
self.states: dict[int, Any] = {}

async def async_update(self, _: Any = None) -> None:
"""Get the latest data from Sure Petcare."""

try:
self.states = await self.surepy.get_entities(refresh=True)
_LOGGER.info(
"🐾 \x1b[38;2;0;255;0m·\x1b[0m successfully updated %d entities",
len(self.states),
)
except SurePetcareError as error:
_LOGGER.error(
"🐾 \x1b[38;2;255;26;102m·\x1b[0m unable to fetch data: %s", error
)

async_dispatcher_send(self.hass, TOPIC_UPDATE)
self.states: dict[int, Any] = {}

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

await self.async_update()

async_track_time_interval(self.hass, self.async_update, SCAN_INTERVAL)

self.hass.async_add_job(
self.hass.config_entries.async_forward_entry_setup( # type: ignore
self.config_entry, "binary_sensor"
Expand All @@ -171,7 +178,8 @@ async def handle_set_lock_state(call: Any) -> None:
await self.set_lock_state(
call.data[ATTR_FLAP_ID], call.data[ATTR_LOCK_STATE]
)
await self.async_update()

await self.coordinator.async_request_refresh()

lock_state_service_schema = vol.Schema(
{
Expand Down
133 changes: 45 additions & 88 deletions binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@
BinarySensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from surepy.entities import PetLocation, SurepyEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from surepy.entities import SurepyEntity
from surepy.entities.devices import Hub as SureHub
from surepy.entities.pet import Pet as SurePet
from surepy.enums import EntityType, Location

# pylint: disable=relative-beyond-top-level
from . import SurePetcareAPI
from .const import DOMAIN, SPC, SURE_MANUFACTURER, TOPIC_UPDATE
from .const import DOMAIN, SPC, SURE_MANUFACTURER

PARALLEL_UPDATES = 2

Expand All @@ -45,13 +46,13 @@ async def async_setup_entry(

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

for surepy_entity in spc.states.values():
for surepy_entity in spc.coordinator.data.values():

if surepy_entity.type == EntityType.PET:
entities.append(Pet(surepy_entity.id, spc))
entities.append(Pet(spc.coordinator, surepy_entity.id, spc))

elif surepy_entity.type == EntityType.HUB:
entities.append(Hub(surepy_entity.id, spc))
entities.append(Hub(spc.coordinator, surepy_entity.id, spc))

# connectivity
elif surepy_entity.type in [
Expand All @@ -60,28 +61,32 @@ async def async_setup_entry(
EntityType.FEEDER,
EntityType.FELAQUA,
]:
entities.append(DeviceConnectivity(surepy_entity.id, spc))
entities.append(DeviceConnectivity(spc.coordinator, surepy_entity.id, spc))

async_add_entities(entities, True)


class SurePetcareBinarySensor(BinarySensorEntity): # type: ignore
class SurePetcareBinarySensor(CoordinatorEntity, BinarySensorEntity):
"""A binary sensor implementation for Sure Petcare Entities."""

_attr_should_poll = False

def __init__(
self,
coordinator,
_id: int,
spc: SurePetcareAPI,
device_class: str,
):
"""Initialize a Sure Petcare binary sensor."""
super().__init__(coordinator)

self._id: int = _id
self._spc: SurePetcareAPI = spc

self._surepy_entity: SurepyEntity = self._spc.states[self._id]
self._coordinator = coordinator

self._surepy_entity: SurepyEntity = self._coordinator.data[self._id]
self._state: Any = self._surepy_entity.raw_data().get("status", {})

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

device = {
"identifiers": {(DOMAIN, self._id)},
"name": self._surepy_entity.name.capitalize(), # type: ignore
"name": self._surepy_entity.name.capitalize(),
"manufacturer": SURE_MANUFACTURER,
"model": model,
}
Expand All @@ -145,46 +150,13 @@ def device_info(self):

return device

@callback
def _async_update(self) -> None:
"""Get the latest data and update the state."""

self._surepy_entity = self._spc.states[self._id]
self._state = self._surepy_entity.raw_data()["status"]

_LOGGER.debug(
"🐾 \x1b[38;2;0;255;0m·\x1b[0m %s updated",
self._attr_name.replace(
f"{self._surepy_entity.type.name.replace('_', ' ').title()} ", ""
),
)

async def async_added_to_hass(self) -> None:
"""Register callbacks."""

self.async_on_remove(
async_dispatcher_connect(self.hass, TOPIC_UPDATE, self._async_update)
)

@callback
def update() -> None:
"""Update the state."""
self.async_schedule_update_ha_state(True)

# pylint: disable=attribute-defined-outside-init
self._async_unsub_dispatcher_connect = async_dispatcher_connect(
self.hass, TOPIC_UPDATE, update
)

self._async_update()


class Hub(SurePetcareBinarySensor):
"""Sure Petcare Pet."""

def __init__(self, _id: int, spc: SurePetcareAPI) -> None:
def __init__(self, coordinator, _id: int, spc: SurePetcareAPI) -> None:
"""Initialize a Sure Petcare Hub."""
super().__init__(_id, spc, DEVICE_CLASS_CONNECTIVITY)
super().__init__(coordinator, _id, spc, DEVICE_CLASS_CONNECTIVITY)

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

if self._state:
hub: SureHub

if hub := self.coordinator.data[self._id]:

self._attr_extra_state_attributes = {
"led_mode": int(self._surepy_entity.raw_data()["status"]["led_mode"]),
"pairing_mode": bool(
self._surepy_entity.raw_data()["status"]["pairing_mode"]
),
"led_mode": int(hub.raw_data()["status"]["led_mode"]),
"pairing_mode": bool(hub.raw_data()["status"]["pairing_mode"]),
}

return bool(self._state["online"])
return bool(hub.online)


class Pet(SurePetcareBinarySensor):
"""Sure Petcare Pet."""

def __init__(self, _id: int, spc: SurePetcareAPI) -> None:
def __init__(self, coordinator, _id: int, spc: SurePetcareAPI) -> None:
"""Initialize a Sure Petcare Pet."""
super().__init__(_id, spc, DEVICE_CLASS_PRESENCE)
super().__init__(coordinator, _id, spc, DEVICE_CLASS_PRESENCE)

self._surepy_entity: SurePet
self._state: PetLocation

self._attr_entity_picture = self._surepy_entity.photo_url

if self._state:
if self._surepy_entity:
self._attr_extra_state_attributes = {
"since": self._surepy_entity.location.since,
"where": self._surepy_entity.location.where,
Expand All @@ -228,49 +200,34 @@ def __init__(self, _id: int, spc: SurePetcareAPI) -> None:
@property
def is_on(self) -> bool:
"""Return True if the pet is at home."""
return self._attr_is_on

@callback
def _async_update(self) -> None:
"""Get the latest data and update the state."""

self._surepy_entity = self._spc.states[self._id]
self._state = self._surepy_entity.location

try:
self._attr_is_on: bool = bool(
Location(self._surepy_entity.location.where) == Location.INSIDE
)
except (KeyError, TypeError):
self._attr_is_on: bool = False

_LOGGER.debug(
"🐾 \x1b[38;2;0;255;0m·\x1b[0m %s updated",
self._attr_name.replace(
f"{self._surepy_entity.type.name.replace('_', ' ').title()} ", ""
),
)
pet: SurePet
if pet := self.coordinator.data[self._id]:
return bool(Location(pet.location.where) == Location.INSIDE)


class DeviceConnectivity(SurePetcareBinarySensor):
"""Sure Petcare Pet."""

def __init__(self, _id: int, spc: SurePetcareAPI) -> None:
def __init__(self, coordinator, _id: int, spc: SurePetcareAPI) -> None:
"""Initialize a Sure Petcare device connectivity sensor."""
super().__init__(_id, spc, DEVICE_CLASS_CONNECTIVITY)
super().__init__(coordinator, _id, spc, DEVICE_CLASS_CONNECTIVITY)

self._attr_name = f"{self._name} Connectivity"
self._attr_unique_id = (
f"{self._surepy_entity.household_id}-{self._id}-connectivity"
)

if self._state:
self._attr_extra_state_attributes = {
"device_rssi": f'{self._state["signal"]["device_rssi"]:.2f}',
"hub_rssi": f'{self._state["signal"]["hub_rssi"]:.2f}',
@property
def extra_state_attributes(self) -> dict[str, Any]:
if (data := self._surepy_entity.raw_data()) and (state := data.get("status")):
return {
"device_rssi": f'{state["signal"]["device_rssi"]:.2f}',
"hub_rssi": f'{state["signal"]["hub_rssi"]:.2f}',
}

@callback
def _async_update(self) -> None:
super()._async_update()
self._attr_is_on = bool(self._attr_extra_state_attributes)
return {}

@property
def is_on(self) -> bool:
"""Return True if the pet is at home."""
return bool(self.extra_state_attributes)
Loading

0 comments on commit 59fabb8

Please sign in to comment.