diff --git a/homeassistant/components/surepetcare/__init__.py b/homeassistant/components/surepetcare/__init__.py index 4a65931d3f0..3873d17343d 100644 --- a/homeassistant/components/surepetcare/__init__.py +++ b/homeassistant/components/surepetcare/__init__.py @@ -1,26 +1,17 @@ -"""Support for Sure Petcare cat/pet flaps.""" +"""The surepetcare integration.""" from __future__ import annotations +from datetime import timedelta import logging from typing import Any -from surepy import ( - MESTART_RESOURCE, - SureLockStateID, - SurePetcare, - SurePetcareAuthenticationError, - SurePetcareError, - SurepyProduct, -) +from surepy import Surepy +from surepy.enums import LockState +from surepy.exceptions import SurePetcareAuthenticationError, SurePetcareError import voluptuous as vol -from homeassistant.const import ( - CONF_ID, - CONF_PASSWORD, - CONF_SCAN_INTERVAL, - CONF_TYPE, - CONF_USERNAME, -) +from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME +from homeassistant.core import HomeAssistant 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 @@ -31,11 +22,7 @@ from .const import ( ATTR_LOCK_STATE, CONF_FEEDERS, CONF_FLAPS, - CONF_PARENT, CONF_PETS, - CONF_PRODUCT_ID, - DATA_SURE_PETCARE, - DEFAULT_SCAN_INTERVAL, DOMAIN, SERVICE_SET_LOCK_STATE, SPC, @@ -45,50 +32,49 @@ from .const import ( _LOGGER = logging.getLogger(__name__) +PLATFORMS = ["binary_sensor", "sensor"] +SCAN_INTERVAL = timedelta(minutes=3) CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( - { - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_FEEDERS, default=[]): vol.All( - cv.ensure_list, [cv.positive_int] - ), - vol.Optional(CONF_FLAPS, default=[]): vol.All( - cv.ensure_list, [cv.positive_int] - ), - vol.Optional(CONF_PETS): vol.All(cv.ensure_list, [cv.positive_int]), - vol.Optional( - CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL - ): cv.time_period, - } + vol.All( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_FEEDERS): vol.All( + cv.ensure_list, [cv.positive_int] + ), + vol.Optional(CONF_FLAPS): vol.All( + cv.ensure_list, [cv.positive_int] + ), + vol.Optional(CONF_PETS): vol.All(cv.ensure_list, [cv.positive_int]), + vol.Optional(CONF_SCAN_INTERVAL): cv.time_period, + }, + cv.deprecated(CONF_FEEDERS), + cv.deprecated(CONF_FLAPS), + cv.deprecated(CONF_PETS), + cv.deprecated(CONF_SCAN_INTERVAL), + ) ) }, extra=vol.ALLOW_EXTRA, ) -async def async_setup(hass, config) -> bool: - """Initialize the Sure Petcare component.""" +async def async_setup(hass: HomeAssistant, config: dict) -> bool: + """Set up the Sure Petcare integration.""" conf = config[DOMAIN] + hass.data.setdefault(DOMAIN, {}) - # update interval - scan_interval = conf[CONF_SCAN_INTERVAL] - - # shared data - hass.data[DOMAIN] = hass.data[DATA_SURE_PETCARE] = {} - - # sure petcare api connection try: - surepy = SurePetcare( + surepy = Surepy( conf[CONF_USERNAME], conf[CONF_PASSWORD], - hass.loop, - async_get_clientsession(hass), + auth_token=None, api_timeout=SURE_API_TIMEOUT, + session=async_get_clientsession(hass), ) - except SurePetcareAuthenticationError: _LOGGER.error("Unable to connect to surepetcare.io: Wrong credentials!") return False @@ -96,50 +82,12 @@ async def async_setup(hass, config) -> bool: _LOGGER.error("Unable to connect to surepetcare.io: Wrong %s!", error) return False - # add feeders - things = [ - {CONF_ID: feeder, CONF_TYPE: SurepyProduct.FEEDER} - for feeder in conf[CONF_FEEDERS] - ] + spc = SurePetcareAPI(hass, surepy) + hass.data[DOMAIN][SPC] = spc - # add flaps (don't differentiate between CAT and PET for now) - things.extend( - [ - {CONF_ID: flap, CONF_TYPE: SurepyProduct.PET_FLAP} - for flap in conf[CONF_FLAPS] - ] - ) - - # discover hubs the flaps/feeders are connected to - hub_ids = set() - for device in things.copy(): - device_data = await surepy.device(device[CONF_ID]) - if ( - CONF_PARENT in device_data - and device_data[CONF_PARENT][CONF_PRODUCT_ID] == SurepyProduct.HUB - and device_data[CONF_PARENT][CONF_ID] not in hub_ids - ): - things.append( - { - CONF_ID: device_data[CONF_PARENT][CONF_ID], - CONF_TYPE: SurepyProduct.HUB, - } - ) - hub_ids.add(device_data[CONF_PARENT][CONF_ID]) - - # add pets - things.extend( - [{CONF_ID: pet, CONF_TYPE: SurepyProduct.PET} for pet in conf[CONF_PETS]] - ) - - _LOGGER.debug("Devices and Pets to setup: %s", things) - - spc = hass.data[DATA_SURE_PETCARE][SPC] = SurePetcareAPI(hass, surepy, things) - - # initial update await spc.async_update() - async_track_time_interval(hass, spc.async_update, scan_interval) + async_track_time_interval(hass, spc.async_update, SCAN_INTERVAL) # load platforms hass.async_create_task( @@ -164,10 +112,12 @@ async def async_setup(hass, config) -> bool: vol.Lower, vol.In( [ - SureLockStateID.UNLOCKED.name.lower(), - SureLockStateID.LOCKED_IN.name.lower(), - SureLockStateID.LOCKED_OUT.name.lower(), - SureLockStateID.LOCKED_ALL.name.lower(), + # https://github.com/PyCQA/pylint/issues/2062 + # pylint: disable=no-member + LockState.UNLOCKED.name.lower(), + LockState.LOCKED_IN.name.lower(), + LockState.LOCKED_OUT.name.lower(), + LockState.LOCKED_ALL.name.lower(), ] ), ), @@ -187,50 +137,32 @@ async def async_setup(hass, config) -> bool: class SurePetcareAPI: """Define a generic Sure Petcare object.""" - def __init__(self, hass, surepy: SurePetcare, ids: list[dict[str, Any]]) -> None: + def __init__(self, hass: HomeAssistant, surepy: Surepy) -> None: """Initialize the Sure Petcare object.""" self.hass = hass self.surepy = surepy - self.ids = ids - self.states: dict[str, Any] = {} + self.states = {} - async def async_update(self, arg: Any = None) -> None: - """Refresh Sure Petcare data.""" + async def async_update(self, _: Any = None) -> None: + """Get the latest data from Sure Petcare.""" - # Fetch all data from SurePet API, refreshing the surepy cache - # TODO: get surepy upstream to add a method to clear the cache explicitly pylint: disable=fixme - await self.surepy._get_resource( # pylint: disable=protected-access - resource=MESTART_RESOURCE - ) - for thing in self.ids: - sure_id = thing[CONF_ID] - sure_type = thing[CONF_TYPE] - - try: - type_state = self.states.setdefault(sure_type, {}) - - if sure_type in [ - SurepyProduct.CAT_FLAP, - SurepyProduct.PET_FLAP, - SurepyProduct.FEEDER, - SurepyProduct.HUB, - ]: - type_state[sure_id] = await self.surepy.device(sure_id) - elif sure_type == SurepyProduct.PET: - type_state[sure_id] = await self.surepy.pet(sure_id) - - except SurePetcareError as error: - _LOGGER.error("Unable to retrieve data from surepetcare.io: %s", error) + try: + self.states = await self.surepy.get_entities() + except SurePetcareError as error: + _LOGGER.error("Unable to fetch data: %s", error) async_dispatcher_send(self.hass, TOPIC_UPDATE) async def set_lock_state(self, flap_id: int, state: str) -> None: """Update the lock state of a flap.""" - if state == SureLockStateID.UNLOCKED.name.lower(): + + # https://github.com/PyCQA/pylint/issues/2062 + # pylint: disable=no-member + if state == LockState.UNLOCKED.name.lower(): await self.surepy.unlock(flap_id) - elif state == SureLockStateID.LOCKED_IN.name.lower(): + elif state == LockState.LOCKED_IN.name.lower(): await self.surepy.lock_in(flap_id) - elif state == SureLockStateID.LOCKED_OUT.name.lower(): + elif state == LockState.LOCKED_OUT.name.lower(): await self.surepy.lock_out(flap_id) - elif state == SureLockStateID.LOCKED_ALL.name.lower(): + elif state == LockState.LOCKED_ALL.name.lower(): await self.surepy.lock(flap_id) diff --git a/homeassistant/components/surepetcare/binary_sensor.py b/homeassistant/components/surepetcare/binary_sensor.py index e96a5eaf35e..5f6a82839e1 100644 --- a/homeassistant/components/surepetcare/binary_sensor.py +++ b/homeassistant/components/surepetcare/binary_sensor.py @@ -1,23 +1,22 @@ """Support for Sure PetCare Flaps/Pets binary sensors.""" from __future__ import annotations -from datetime import datetime import logging from typing import Any -from surepy import SureLocationID, SurepyProduct +from surepy.entities import SurepyEntity +from surepy.enums import EntityType, Location, SureEnum from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, DEVICE_CLASS_PRESENCE, BinarySensorEntity, ) -from homeassistant.const import CONF_ID, CONF_TYPE from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from . import SurePetcareAPI -from .const import DATA_SURE_PETCARE, SPC, TOPIC_UPDATE +from .const import DOMAIN, SPC, TOPIC_UPDATE _LOGGER = logging.getLogger(__name__) @@ -29,30 +28,27 @@ async def async_setup_platform( if discovery_info is None: return - entities = [] + entities: list[SurepyEntity] = [] - spc = hass.data[DATA_SURE_PETCARE][SPC] + spc: SurePetcareAPI = hass.data[DOMAIN][SPC] - for thing in spc.ids: - sure_id = thing[CONF_ID] - sure_type = thing[CONF_TYPE] + for surepy_entity in spc.states.values(): # connectivity - if sure_type in [ - SurepyProduct.CAT_FLAP, - SurepyProduct.PET_FLAP, - SurepyProduct.FEEDER, + if surepy_entity.type in [ + EntityType.CAT_FLAP, + EntityType.PET_FLAP, + EntityType.FEEDER, + EntityType.FELAQUA, ]: - entities.append(DeviceConnectivity(sure_id, sure_type, spc)) + entities.append( + DeviceConnectivity(surepy_entity.id, surepy_entity.type, spc) + ) - if sure_type == SurepyProduct.PET: - entity = Pet(sure_id, spc) - elif sure_type == SurepyProduct.HUB: - entity = Hub(sure_id, spc) - else: - continue - - entities.append(entity) + if surepy_entity.type == EntityType.PET: + entities.append(Pet(surepy_entity.id, spc)) + elif surepy_entity.type == EntityType.HUB: + entities.append(Hub(surepy_entity.id, spc)) async_add_entities(entities, True) @@ -65,35 +61,29 @@ class SurePetcareBinarySensor(BinarySensorEntity): _id: int, spc: SurePetcareAPI, device_class: str, - sure_type: SurepyProduct, + sure_type: EntityType, ): """Initialize a Sure Petcare binary sensor.""" + self._id = _id - self._sure_type = sure_type self._device_class = device_class self._spc: SurePetcareAPI = spc - self._spc_data: dict[str, Any] = self._spc.states[self._sure_type].get(self._id) - self._state: dict[str, Any] = {} + + self._surepy_entity: SurepyEntity = self._spc.states[self._id] + self._state: SureEnum | dict[str, Any] = None # cover special case where a device has no name set - if "name" in self._spc_data: - name = self._spc_data["name"] + if self._surepy_entity.name: + name = self._surepy_entity.name else: - name = f"Unnamed {self._sure_type.name.capitalize()}" + name = f"Unnamed {self._surepy_entity.type.name.capitalize()}" - self._name = f"{self._sure_type.name.capitalize()} {name.capitalize()}" - - self._async_unsub_dispatcher_connect = None - - @property - def is_on(self) -> bool | None: - """Return true if entity is on/unlocked.""" - return bool(self._state) + self._name = f"{self._surepy_entity.type.name.capitalize()} {name.capitalize()}" @property def should_poll(self) -> bool: - """Return true.""" + """Return if the entity should use default polling.""" return False @property @@ -109,30 +99,21 @@ class SurePetcareBinarySensor(BinarySensorEntity): @property def unique_id(self) -> str: """Return an unique ID.""" - return f"{self._spc_data['household_id']}-{self._id}" + return f"{self._surepy_entity.household_id}-{self._id}" - async def async_update(self) -> None: + @callback + def _async_update(self) -> None: """Get the latest data and update the state.""" - self._spc_data = self._spc.states[self._sure_type].get(self._id) - self._state = self._spc_data.get("status") + self._surepy_entity = self._spc.states[self._id] + self._state = self._surepy_entity.raw_data()["status"] _LOGGER.debug("%s -> self._state: %s", self._name, self._state) async def async_added_to_hass(self) -> None: """Register callbacks.""" - - @callback - def update() -> None: - """Update the state.""" - self.async_schedule_update_ha_state(True) - - self._async_unsub_dispatcher_connect = async_dispatcher_connect( - self.hass, TOPIC_UPDATE, update + self.async_on_remove( + async_dispatcher_connect(self.hass, TOPIC_UPDATE, self._async_update) ) - - async def async_will_remove_from_hass(self) -> None: - """Disconnect dispatcher listener when removed.""" - if self._async_unsub_dispatcher_connect: - self._async_unsub_dispatcher_connect() + self._async_update() class Hub(SurePetcareBinarySensor): @@ -140,7 +121,7 @@ class Hub(SurePetcareBinarySensor): def __init__(self, _id: int, spc: SurePetcareAPI) -> None: """Initialize a Sure Petcare Hub.""" - super().__init__(_id, spc, DEVICE_CLASS_CONNECTIVITY, SurepyProduct.HUB) + super().__init__(_id, spc, DEVICE_CLASS_CONNECTIVITY, EntityType.HUB) @property def available(self) -> bool: @@ -156,10 +137,12 @@ class Hub(SurePetcareBinarySensor): def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the device.""" attributes = None - if self._state: + if self._surepy_entity.raw_data(): attributes = { - "led_mode": int(self._state["led_mode"]), - "pairing_mode": bool(self._state["pairing_mode"]), + "led_mode": int(self._surepy_entity.raw_data()["status"]["led_mode"]), + "pairing_mode": bool( + self._surepy_entity.raw_data()["status"]["pairing_mode"] + ), } return attributes @@ -170,13 +153,13 @@ class Pet(SurePetcareBinarySensor): def __init__(self, _id: int, spc: SurePetcareAPI) -> None: """Initialize a Sure Petcare Pet.""" - super().__init__(_id, spc, DEVICE_CLASS_PRESENCE, SurepyProduct.PET) + super().__init__(_id, spc, DEVICE_CLASS_PRESENCE, EntityType.PET) @property def is_on(self) -> bool: """Return true if entity is at home.""" try: - return bool(SureLocationID(self._state["where"]) == SureLocationID.INSIDE) + return bool(Location(self._state.where) == Location.INSIDE) except (KeyError, TypeError): return False @@ -185,19 +168,15 @@ class Pet(SurePetcareBinarySensor): """Return the state attributes of the device.""" attributes = None if self._state: - attributes = { - "since": str( - datetime.fromisoformat(self._state["since"]).replace(tzinfo=None) - ), - "where": SureLocationID(self._state["where"]).name.capitalize(), - } + attributes = {"since": self._state.since, "where": self._state.where} return attributes - async def async_update(self) -> None: + @callback + def _async_update(self) -> None: """Get the latest data and update the state.""" - self._spc_data = self._spc.states[self._sure_type].get(self._id) - self._state = self._spc_data.get("position") + self._surepy_entity = self._spc.states[self._id] + self._state = self._surepy_entity.location _LOGGER.debug("%s -> self._state: %s", self._name, self._state) @@ -207,7 +186,7 @@ class DeviceConnectivity(SurePetcareBinarySensor): def __init__( self, _id: int, - sure_type: SurepyProduct, + sure_type: EntityType, spc: SurePetcareAPI, ) -> None: """Initialize a Sure Petcare Device.""" @@ -221,7 +200,7 @@ class DeviceConnectivity(SurePetcareBinarySensor): @property def unique_id(self) -> str: """Return an unique ID.""" - return f"{self._spc_data['household_id']}-{self._id}-connectivity" + return f"{self._surepy_entity.household_id}-{self._id}-connectivity" @property def available(self) -> bool: diff --git a/homeassistant/components/surepetcare/const.py b/homeassistant/components/surepetcare/const.py index 86215c12ade..cb5a78a3c1e 100644 --- a/homeassistant/components/surepetcare/const.py +++ b/homeassistant/components/surepetcare/const.py @@ -1,24 +1,11 @@ """Constants for the Sure Petcare component.""" -from datetime import timedelta - DOMAIN = "surepetcare" -DEFAULT_DEVICE_CLASS = "lock" -DEFAULT_ICON = "mdi:cat" -DEFAULT_SCAN_INTERVAL = timedelta(minutes=3) -DATA_SURE_PETCARE = f"data_{DOMAIN}" SPC = "spc" -SUREPY = "surepy" -CONF_HOUSEHOLD_ID = "household_id" CONF_FEEDERS = "feeders" CONF_FLAPS = "flaps" -CONF_PARENT = "parent" CONF_PETS = "pets" -CONF_PRODUCT_ID = "product_id" -CONF_DATA = "data" - -SURE_IDS = "sure_ids" # platforms TOPIC_UPDATE = f"{DOMAIN}_data_update" @@ -27,7 +14,6 @@ TOPIC_UPDATE = f"{DOMAIN}_data_update" SURE_API_TIMEOUT = 60 # flap -BATTERY_ICON = "mdi:battery" SURE_BATT_VOLTAGE_FULL = 1.6 # voltage SURE_BATT_VOLTAGE_LOW = 1.25 # voltage SURE_BATT_VOLTAGE_DIFF = SURE_BATT_VOLTAGE_FULL - SURE_BATT_VOLTAGE_LOW diff --git a/homeassistant/components/surepetcare/manifest.json b/homeassistant/components/surepetcare/manifest.json index 6c5b0616be7..231ede6474f 100644 --- a/homeassistant/components/surepetcare/manifest.json +++ b/homeassistant/components/surepetcare/manifest.json @@ -3,6 +3,6 @@ "name": "Sure Petcare", "documentation": "https://www.home-assistant.io/integrations/surepetcare", "codeowners": ["@benleb"], - "requirements": ["surepy==0.4.0"], + "requirements": ["surepy==0.6.0"], "iot_class": "cloud_polling" } diff --git a/homeassistant/components/surepetcare/sensor.py b/homeassistant/components/surepetcare/sensor.py index 0a49781767b..33396e25267 100644 --- a/homeassistant/components/surepetcare/sensor.py +++ b/homeassistant/components/surepetcare/sensor.py @@ -4,22 +4,17 @@ from __future__ import annotations import logging from typing import Any -from surepy import SureLockStateID, SurepyProduct +from surepy.entities import SurepyEntity +from surepy.enums import EntityType from homeassistant.components.sensor import SensorEntity -from homeassistant.const import ( - ATTR_VOLTAGE, - CONF_ID, - CONF_TYPE, - DEVICE_CLASS_BATTERY, - PERCENTAGE, -) +from homeassistant.const import ATTR_VOLTAGE, DEVICE_CLASS_BATTERY, PERCENTAGE from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from . import SurePetcareAPI from .const import ( - DATA_SURE_PETCARE, + DOMAIN, SPC, SURE_BATT_VOLTAGE_DIFF, SURE_BATT_VOLTAGE_LOW, @@ -34,56 +29,39 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= if discovery_info is None: return - entities = [] + entities: list[SurepyEntity] = [] - spc = hass.data[DATA_SURE_PETCARE][SPC] + spc: SurePetcareAPI = hass.data[DOMAIN][SPC] - for entity in spc.ids: - sure_type = entity[CONF_TYPE] + for surepy_entity in spc.states.values(): - if sure_type in [ - SurepyProduct.CAT_FLAP, - SurepyProduct.PET_FLAP, - SurepyProduct.FEEDER, + if surepy_entity.type in [ + EntityType.CAT_FLAP, + EntityType.PET_FLAP, + EntityType.FEEDER, + EntityType.FELAQUA, ]: - entities.append(SureBattery(entity[CONF_ID], sure_type, spc)) + entities.append(SureBattery(surepy_entity.id, spc)) - if sure_type in [SurepyProduct.CAT_FLAP, SurepyProduct.PET_FLAP]: - entities.append(Flap(entity[CONF_ID], sure_type, spc)) - - async_add_entities(entities, True) + async_add_entities(entities) class SurePetcareSensor(SensorEntity): """A binary sensor implementation for Sure Petcare Entities.""" - def __init__(self, _id: int, sure_type: SurepyProduct, spc: SurePetcareAPI): + def __init__(self, _id: int, spc: SurePetcareAPI): """Initialize a Sure Petcare sensor.""" self._id = _id - self._sure_type = sure_type + self._spc: SurePetcareAPI = spc - self._spc = spc - self._spc_data: dict[str, Any] = self._spc.states[self._sure_type].get(self._id) + self._surepy_entity: SurepyEntity = self._spc.states[_id] self._state: dict[str, Any] = {} - self._name = ( - f"{self._sure_type.name.capitalize()} " - f"{self._spc_data['name'].capitalize()}" + f"{self._surepy_entity.type.name.capitalize()} " + f"{self._surepy_entity.name.capitalize()}" ) - self._async_unsub_dispatcher_connect = None - - @property - def name(self) -> str: - """Return the name of the device if any.""" - return self._name - - @property - def unique_id(self) -> str: - """Return an unique ID.""" - return f"{self._spc_data['household_id']}-{self._id}" - @property def available(self) -> bool: """Return true if entity is available.""" @@ -94,46 +72,19 @@ class SurePetcareSensor(SensorEntity): """Return true.""" return False - async def async_update(self) -> None: + @callback + def _async_update(self) -> None: """Get the latest data and update the state.""" - self._spc_data = self._spc.states[self._sure_type].get(self._id) - self._state = self._spc_data.get("status") + self._surepy_entity = self._spc.states[self._id] + self._state = self._surepy_entity.raw_data()["status"] _LOGGER.debug("%s -> self._state: %s", self._name, self._state) async def async_added_to_hass(self) -> None: """Register callbacks.""" - - @callback - def update() -> None: - """Update the state.""" - self.async_schedule_update_ha_state(True) - - self._async_unsub_dispatcher_connect = async_dispatcher_connect( - self.hass, TOPIC_UPDATE, update + self.async_on_remove( + async_dispatcher_connect(self.hass, TOPIC_UPDATE, self._async_update) ) - - async def async_will_remove_from_hass(self) -> None: - """Disconnect dispatcher listener when removed.""" - if self._async_unsub_dispatcher_connect: - self._async_unsub_dispatcher_connect() - - -class Flap(SurePetcareSensor): - """Sure Petcare Flap.""" - - @property - def state(self) -> int | None: - """Return battery level in percent.""" - return SureLockStateID(self._state["locking"]["mode"]).name.capitalize() - - @property - def extra_state_attributes(self) -> dict[str, Any] | None: - """Return the state attributes of the device.""" - attributes = None - if self._state: - attributes = {"learn_mode": bool(self._state["learn_mode"])} - - return attributes + self._async_update() class SureBattery(SurePetcareSensor): @@ -160,7 +111,7 @@ class SureBattery(SurePetcareSensor): @property def unique_id(self) -> str: """Return an unique ID.""" - return f"{self._spc_data['household_id']}-{self._id}-battery" + return f"{self._surepy_entity.household_id}-{self._surepy_entity.id}-battery" @property def device_class(self) -> str: diff --git a/requirements_all.txt b/requirements_all.txt index 660a2c474cf..d2fd9fb6155 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2178,7 +2178,7 @@ sucks==0.9.4 sunwatcher==0.2.1 # homeassistant.components.surepetcare -surepy==0.4.0 +surepy==0.6.0 # homeassistant.components.swiss_hydrological_data swisshydrodata==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8fffd4c0176..499b9f1b364 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1165,7 +1165,7 @@ subarulink==0.3.12 sunwatcher==0.2.1 # homeassistant.components.surepetcare -surepy==0.4.0 +surepy==0.6.0 # homeassistant.components.synology_dsm synologydsm-api==1.0.2 diff --git a/tests/components/surepetcare/__init__.py b/tests/components/surepetcare/__init__.py index d4af323d063..7dda9e23d90 100644 --- a/tests/components/surepetcare/__init__.py +++ b/tests/components/surepetcare/__init__.py @@ -1,11 +1,9 @@ """Tests for Sure Petcare integration.""" -from unittest.mock import patch - from homeassistant.components.surepetcare.const import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -HOUSEHOLD_ID = "household-id" -HUB_ID = "hub-id" +HOUSEHOLD_ID = 987654321 +HUB_ID = 123456789 MOCK_HUB = { "id": HUB_ID, @@ -79,10 +77,3 @@ MOCK_CONFIG = { "pets": [24680], }, } - - -def _patch_sensor_setup(): - return patch( - "homeassistant.components.surepetcare.sensor.async_setup_platform", - return_value=True, - ) diff --git a/tests/components/surepetcare/conftest.py b/tests/components/surepetcare/conftest.py index 44e2a722406..43738f22587 100644 --- a/tests/components/surepetcare/conftest.py +++ b/tests/components/surepetcare/conftest.py @@ -1,22 +1,18 @@ """Define fixtures available for all tests.""" -from unittest.mock import AsyncMock, patch +from unittest.mock import patch -from pytest import fixture -from surepy import SurePetcare +import pytest +from surepy import MESTART_RESOURCE -from homeassistant.helpers.aiohttp_client import async_get_clientsession +from . import MOCK_API_DATA -@fixture -async def surepetcare(hass): +@pytest.fixture +async def surepetcare(): """Mock the SurePetcare for easier testing.""" - with patch("homeassistant.components.surepetcare.SurePetcare") as mock_surepetcare: - instance = mock_surepetcare.return_value = SurePetcare( - "test-username", - "test-password", - hass.loop, - async_get_clientsession(hass), - api_timeout=1, - ) - instance._get_resource = AsyncMock(return_value=None) - yield mock_surepetcare + with patch("surepy.SureAPIClient", autospec=True) as mock_client_class, patch( + "surepy.find_token" + ): + client = mock_client_class.return_value + client.resources = {MESTART_RESOURCE: {"data": MOCK_API_DATA}} + yield client diff --git a/tests/components/surepetcare/test_binary_sensor.py b/tests/components/surepetcare/test_binary_sensor.py index 67755dbd645..cd0445dd6d5 100644 --- a/tests/components/surepetcare/test_binary_sensor.py +++ b/tests/components/surepetcare/test_binary_sensor.py @@ -1,34 +1,32 @@ """The tests for the Sure Petcare binary sensor platform.""" -from surepy import MESTART_RESOURCE from homeassistant.components.surepetcare.const import DOMAIN from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component -from . import MOCK_API_DATA, MOCK_CONFIG, _patch_sensor_setup +from . import HOUSEHOLD_ID, HUB_ID, MOCK_CONFIG EXPECTED_ENTITY_IDS = { - "binary_sensor.pet_flap_pet_flap_connectivity": "household-id-13576-connectivity", - "binary_sensor.pet_flap_cat_flap_connectivity": "household-id-13579-connectivity", - "binary_sensor.feeder_feeder_connectivity": "household-id-12345-connectivity", - "binary_sensor.pet_pet": "household-id-24680", - "binary_sensor.hub_hub": "household-id-hub-id", + "binary_sensor.pet_flap_pet_flap_connectivity": f"{HOUSEHOLD_ID}-13576-connectivity", + "binary_sensor.cat_flap_cat_flap_connectivity": f"{HOUSEHOLD_ID}-13579-connectivity", + "binary_sensor.feeder_feeder_connectivity": f"{HOUSEHOLD_ID}-12345-connectivity", + "binary_sensor.pet_pet": f"{HOUSEHOLD_ID}-24680", + "binary_sensor.hub_hub": f"{HOUSEHOLD_ID}-{HUB_ID}", } async def test_binary_sensors(hass, surepetcare) -> None: """Test the generation of unique ids.""" - instance = surepetcare.return_value - instance._resource[MESTART_RESOURCE] = {"data": MOCK_API_DATA} - - with _patch_sensor_setup(): - assert await async_setup_component(hass, DOMAIN, MOCK_CONFIG) - await hass.async_block_till_done() + assert await async_setup_component(hass, DOMAIN, MOCK_CONFIG) + await hass.async_block_till_done() entity_registry = er.async_get(hass) state_entity_ids = hass.states.async_entity_ids() for entity_id, unique_id in EXPECTED_ENTITY_IDS.items(): assert entity_id in state_entity_ids + state = hass.states.get(entity_id) + assert state + assert state.state == "on" entity = entity_registry.async_get(entity_id) assert entity.unique_id == unique_id diff --git a/tests/components/surepetcare/test_sensor.py b/tests/components/surepetcare/test_sensor.py new file mode 100644 index 00000000000..8e7160364ea --- /dev/null +++ b/tests/components/surepetcare/test_sensor.py @@ -0,0 +1,29 @@ +"""Test the surepetcare sensor platform.""" +from homeassistant.components.surepetcare.const import DOMAIN +from homeassistant.helpers import entity_registry as er +from homeassistant.setup import async_setup_component + +from . import HOUSEHOLD_ID, MOCK_CONFIG + +EXPECTED_ENTITY_IDS = { + "sensor.pet_flap_pet_flap_battery_level": f"{HOUSEHOLD_ID}-13576-battery", + "sensor.cat_flap_cat_flap_battery_level": f"{HOUSEHOLD_ID}-13579-battery", + "sensor.feeder_feeder_battery_level": f"{HOUSEHOLD_ID}-12345-battery", +} + + +async def test_binary_sensors(hass, surepetcare) -> None: + """Test the generation of unique ids.""" + assert await async_setup_component(hass, DOMAIN, MOCK_CONFIG) + await hass.async_block_till_done() + + entity_registry = er.async_get(hass) + state_entity_ids = hass.states.async_entity_ids() + + for entity_id, unique_id in EXPECTED_ENTITY_IDS.items(): + assert entity_id in state_entity_ids + state = hass.states.get(entity_id) + assert state + assert state.state == "100" + entity = entity_registry.async_get(entity_id) + assert entity.unique_id == unique_id