1
mirror of https://github.com/home-assistant/core synced 2024-08-02 23:40:32 +02:00
ha-core/homeassistant/components/surepetcare/__init__.py
Daniel Hjelseth Høyer d5bda3ac14
Surepetcare reauthorize (#56402)
Co-authored-by: J. Nick Koston <nick@koston.org>
2021-09-30 10:11:45 -05:00

218 lines
7.1 KiB
Python

"""The surepetcare integration."""
from __future__ import annotations
from datetime import timedelta
import logging
from surepy import Surepy, SurepyEntity
from surepy.enums import EntityType, Location, LockState
from surepy.exceptions import SurePetcareAuthenticationError, SurePetcareError
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_TOKEN,
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.service import ServiceCall
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import (
ATTR_FLAP_ID,
ATTR_LOCATION,
ATTR_LOCK_STATE,
ATTR_PET_NAME,
CONF_FEEDERS,
CONF_FLAPS,
CONF_PETS,
DOMAIN,
SERVICE_SET_LOCK_STATE,
SERVICE_SET_PET_LOCATION,
SURE_API_TIMEOUT,
)
_LOGGER = logging.getLogger(__name__)
PLATFORMS = ["binary_sensor", "lock", "sensor"]
SCAN_INTERVAL = timedelta(minutes=3)
CONFIG_SCHEMA = vol.Schema(
vol.All(
cv.deprecated(DOMAIN),
{
DOMAIN: vol.Schema(
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: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Sure Petcare integration."""
if DOMAIN not in config:
return True
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data=config[DOMAIN],
)
)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Sure Petcare from a config entry."""
hass.data.setdefault(DOMAIN, {})
try:
hass.data[DOMAIN][entry.entry_id] = coordinator = SurePetcareDataCoordinator(
entry,
hass,
)
except SurePetcareAuthenticationError as error:
_LOGGER.error("Unable to connect to surepetcare.io: Wrong credentials!")
raise ConfigEntryAuthFailed from error
except SurePetcareError as error:
raise ConfigEntryNotReady from error
await coordinator.async_config_entry_first_refresh()
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
lock_state_service_schema = vol.Schema(
{
vol.Required(ATTR_FLAP_ID): vol.All(
cv.positive_int, vol.In(coordinator.data.keys())
),
vol.Required(ATTR_LOCK_STATE): vol.All(
cv.string,
vol.Lower,
vol.In(coordinator.lock_states_callbacks.keys()),
),
}
)
hass.services.async_register(
DOMAIN,
SERVICE_SET_LOCK_STATE,
coordinator.handle_set_lock_state,
schema=lock_state_service_schema,
)
set_pet_location_schema = vol.Schema(
{
vol.Required(ATTR_PET_NAME): vol.In(coordinator.get_pets().keys()),
vol.Required(ATTR_LOCATION): vol.In(
[
Location.INSIDE.name.title(),
Location.OUTSIDE.name.title(),
]
),
}
)
hass.services.async_register(
DOMAIN,
SERVICE_SET_PET_LOCATION,
coordinator.handle_set_pet_location,
schema=set_pet_location_schema,
)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
class SurePetcareDataCoordinator(DataUpdateCoordinator):
"""Handle Surepetcare data."""
def __init__(self, entry: ConfigEntry, hass: HomeAssistant) -> None:
"""Initialize the data handler."""
self.surepy = Surepy(
entry.data[CONF_USERNAME],
entry.data[CONF_PASSWORD],
auth_token=entry.data[CONF_TOKEN],
api_timeout=SURE_API_TIMEOUT,
session=async_get_clientsession(hass),
)
self.lock_states_callbacks = {
LockState.UNLOCKED.name.lower(): self.surepy.sac.unlock,
LockState.LOCKED_IN.name.lower(): self.surepy.sac.lock_in,
LockState.LOCKED_OUT.name.lower(): self.surepy.sac.lock_out,
LockState.LOCKED_ALL.name.lower(): self.surepy.sac.lock,
}
super().__init__(
hass,
_LOGGER,
name=DOMAIN,
update_interval=SCAN_INTERVAL,
)
async def _async_update_data(self) -> dict[int, SurepyEntity]:
"""Get the latest data from Sure Petcare."""
try:
return await self.surepy.get_entities(refresh=True)
except SurePetcareAuthenticationError as err:
raise ConfigEntryAuthFailed("Invalid username/password") from err
except SurePetcareError as err:
raise UpdateFailed(f"Unable to fetch data: {err}") from err
async def handle_set_lock_state(self, call: ServiceCall) -> None:
"""Call when setting the lock state."""
flap_id = call.data[ATTR_FLAP_ID]
state = call.data[ATTR_LOCK_STATE]
await self.lock_states_callbacks[state](flap_id)
await self.async_request_refresh()
def get_pets(self) -> dict[str, int]:
"""Get pets."""
pets = {}
for surepy_entity in self.data.values():
if surepy_entity.type == EntityType.PET and surepy_entity.name:
pets[surepy_entity.name] = surepy_entity.id
return pets
async def handle_set_pet_location(self, call: ServiceCall) -> None:
"""Call when setting the pet location."""
pet_name = call.data[ATTR_PET_NAME]
location = call.data[ATTR_LOCATION]
device_id = self.get_pets()[pet_name]
await self.surepy.sac.set_pet_location(device_id, Location[location.upper()])
await self.async_request_refresh()