async def position( ctx: click.Context, pet_id: int, position: str, token: str | None = None ) -> None: """set pet position""" token = token if token else ctx.obj.get("token", None) sp = Surepy(auth_token=str(token)) pet: Pet | None location: Location | None if (pet := await sp.pet(pet_id=pet_id)) and (type(pet) == Pet): if position == "in": location = Location.INSIDE elif position == "out": location = Location.OUTSIDE else: return if location: if await sp.sac.set_position(pet.id, location): console.print(f"{pet.name} set to '{location.name}' 🐾") else: console.print( f"setting to '{location.name}' probably worked but something else is fishy...!" )
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up.""" hass.data.setdefault(DOMAIN, {}) try: surepy = Surepy( entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD], auth_token=entry.data[CONF_TOKEN] if CONF_TOKEN in entry.data else None, api_timeout=SURE_API_TIMEOUT, session=async_get_clientsession(hass), ) except SurePetcareAuthenticationError: _LOGGER.error( "🐾 \x1b[38;2;255;26;102m·\x1b[0m unable to auth. to surepetcare.io: wrong credentials" ) return False except SurePetcareError as error: _LOGGER.error( "🐾 \x1b[38;2;255;26;102m·\x1b[0m unable to connect to surepetcare.io: %s", error, ) 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()
async def pets(ctx: click.Context, token: str | None) -> None: """get pets""" token = token if token else ctx.obj.get("token", None) async with ClientSession(connector=TCPConnector(ssl=False)) as session: sp = Surepy(auth_token=token, session=session) pets: list[Pet] = await sp.get_pets() table = Table(box=box.MINIMAL) table.add_column("Name", style="bold") table.add_column("Where", justify="right") table.add_column("Feeding A", justify="right", style="bold") table.add_column("Feeding B", justify="right", style="bold") table.add_column("Lunch Time", justify="right", style="bold") table.add_column("Drinking", justify="right", style="bold") table.add_column("Drink Time", justify="right", style="bold") table.add_column("ID 👤 ", justify="right") table.add_column("Household 🏡", justify="right") for pet in pets: feeding_a = feeding_b = lunch_time = None drinking_change = drink_time = None if pet.feeding: feeding_a = f"{pet.feeding.change[0]}g" feeding_b = f"{pet.feeding.change[1]}g" lunch_time = pet.feeding.at.time() if pet.feeding.at else None if pet.drinking: drinking_change = f"{pet.drinking.change[0]}ml" drink_time = pet.drinking.at.time() if pet.drinking.at else None table.add_row( str(pet.name), str(pet.location), f"{feeding_a}", f"{feeding_b}", str(lunch_time), f"{drinking_change}", str(drink_time), str(pet.pet_id), str(pet.household_id), ) console.print(table, "", sep="\n")
async def token(ctx: click.Context, user: str, password: str) -> None: """get a token""" surepy_token: str | None = None with Halo(text="fetching token", spinner="dots", color="magenta") as spinner: async with ClientSession(connector=TCPConnector(ssl=False)) as session: sp = Surepy(email=user, password=password, session=session) if surepy_token := await sp.sac.get_token(): spinner.succeed("token received!") if token_file.exists() and token != token_file.read_text(encoding="utf-8"): copyfile(token_file, old_token_file) token_file.write_text(surepy_token, encoding="utf-8")
async def locking( ctx: click.Context, device_id: int, mode: str, token: str | None = None ) -> None: """lock control""" token = token if token else ctx.obj.get("token", None) sp = Surepy(auth_token=str(token)) flap: Flap | None if (flap := await sp.flap(flap_id=device_id)) and (type(flap) == Flap): lock_control: Callable[..., Coroutine[Any, Any, Any]] | None = None if mode == "lock": lock_control = flap.lock state = "locked" elif mode == "in": lock_control = flap.lock_in state = "locked in" elif mode == "out": lock_control = flap.lock_out state = "locked out" elif mode == "unlock": lock_control = flap.unlock state = "unlocked" else: return if lock_control: with Halo( text=f"setting {flap.name} to '{state}'", spinner="dots", color="red" ) as spinner: if await lock_control(device_id=device_id) and ( device := await sp.flap(flap_id=device_id) ): spinner.succeed(f"{device.name} set to '{state}' 🐾") else: spinner.fail( f"setting to '{state}' probably worked but something else is fishy...!" )
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 is_valid(hass: core.HomeAssistant, user_input: dict[str, Any]) -> str | None: """Check if we can log in with the supplied credentials.""" try: surepy = Surepy( user_input[CONF_USERNAME], user_input[CONF_PASSWORD], auth_token=None, api_timeout=SURE_API_TIMEOUT, session=async_get_clientsession(hass), ) return await surepy.sac.get_token() except SurePetcareAuthenticationError: _LOGGER.error( "Unable to connect to surepetcare.io: Wrong credentials!") return None except SurePetcareError as error: _LOGGER.error("Unable to connect to surepetcare.io: %s", error) return None
async def notification(ctx: click.Context, token: str | None = None) -> None: """get notifications""" token = token if token else ctx.obj.get("token", None) async with ClientSession(connector=TCPConnector(ssl=False)) as session: sp = Surepy(auth_token=token, session=session) json_data = await sp.get_notification() or None if json_data and (data := json_data.get("data")): table = Table(box=box.MINIMAL) all_keys: set[str] = set() all_keys.update(*[entry.keys() for entry in data]) for key in all_keys: table.add_column(str(key)) for entry in data: table.add_row(*([str(e) for e in entry.values()])) console.print(table, "", sep="\n")
async def devices(ctx: click.Context, token: str | None) -> None: """get devices""" token = token if token else ctx.obj.get("token", None) async with ClientSession(connector=TCPConnector(ssl=False)) as session: sp = Surepy(auth_token=token, session=session) devices: list[SurepyDevice] = await sp.get_devices() # await json_response(devices, ctx) # table = Table(title="[bold][#ff1d5e]·[/] Devices [#ff1d5e]·[/]", box=box.MINIMAL) table = Table(box=box.MINIMAL) table.add_column("ID", justify="right", style="") table.add_column("Household", justify="right", style="") table.add_column("Name", style="bold") table.add_column("Type", style="") table.add_column("Serial", style="") # sorted_devices = sorted(devices, key=lambda x: int(devices[x]["household_id"])) # devices = await sp.sac.get_devices() # devices = await sp.get_entities() for device in devices: table.add_row( str(device.id), str(device.household_id), str(device.name), str(device.type.name.replace("_", " ").title()), str(device.serial) or "-", ) console.print(table, "", sep="\n")
async def main(): surepy = Surepy(auth_token=token) devices: List[SurepyDevice] = await surepy.get_devices() return devices
async def async_setup(hass: HomeAssistant, config: dict) -> bool: """Set up the Sure Petcare integration.""" conf = config[DOMAIN] hass.data.setdefault(DOMAIN, {}) try: surepy = Surepy( conf[CONF_USERNAME], conf[CONF_PASSWORD], 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 except SurePetcareError as error: _LOGGER.error("Unable to connect to surepetcare.io: Wrong %s!", error) return False spc = SurePetcareAPI(hass, surepy) hass.data[DOMAIN][SPC] = spc await spc.async_update() async_track_time_interval(hass, spc.async_update, SCAN_INTERVAL) # load platforms for platform in PLATFORMS: hass.async_create_task( hass.helpers.discovery.async_load_platform(platform, DOMAIN, {}, config)) async def handle_set_lock_state(call): """Call when setting the lock state.""" await spc.set_lock_state(call.data[ATTR_FLAP_ID], call.data[ATTR_LOCK_STATE]) await spc.async_update() lock_state_service_schema = vol.Schema({ vol.Required(ATTR_FLAP_ID): vol.All(cv.positive_int, vol.In(spc.states.keys())), vol.Required(ATTR_LOCK_STATE): vol.All( cv.string, vol.Lower, vol.In([ # 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(), ]), ), }) hass.services.async_register( DOMAIN, SERVICE_SET_LOCK_STATE, handle_set_lock_state, schema=lock_state_service_schema, ) return True
async def report( ctx: click.Context, household_id: int, pet_id: int | None = None, token: str | None = None ) -> None: """get pet/household report""" token = token if token else ctx.obj.get("token", None) async with ClientSession(connector=TCPConnector(ssl=False)) as session: sp = Surepy(auth_token=token, session=session) entities = await sp.get_entities() json_data = await sp.get_report(pet_id=pet_id, household_id=household_id) if data := json_data.get("data"): table = Table(box=box.MINIMAL) all_keys: list[str] = ["pet", "from", "to", "duration", "entry_device", "exit_device"] for key in all_keys: table.add_column(str(key)) for pet in data: datapoints_drinking: list[dict[str, Any]] = pet.get("drinking", {}).get( "datapoints", [] ) datapoints_feeding: list[dict[str, Any]] = pet.get("feeding", {}).get( "datapoints", [] ) datapoints_movement: list[dict[str, Any]] = pet.get("movement", {}).get( "datapoints", [] ) datapoints = datapoints_drinking + datapoints_feeding + datapoints_movement if datapoints: # datapoints.sort(key=lambda x: datetime.fromisoformat(x["from"]), reverse=True) for datapoint in datapoints[:25]: from_time = datetime.fromisoformat(datapoint["from"]) to_time = ( datetime.fromisoformat(datapoint["to"]) if "active" not in datapoint else None ) if "active" in datapoint: datapoint["duration"] = ( datetime.now(tz=from_time.tzinfo) - from_time ).total_seconds() entry_device = entities.get(datapoint.get("entry_device_id", 0), None) exit_device = entities.pop(datapoint.get("exit_device_id", 0), None) table.add_row( str(entities[pet["pet_id"]].name), str(from_time.strftime("%d/%m %H:%M")), str(to_time.strftime("%d/%m %H:%M") if to_time else "-"), str(natural_time(datapoint["duration"])), str(entry_device.name if entry_device else "-"), str(exit_device.name if exit_device else "-"), ) console.print(table, "", sep="\n")