Skip to content

Commit

Permalink
1.1.0b5
Browse files Browse the repository at this point in the history
improve aggregator exception handling
add new switch entity for app control
add new sensor entity to show account balance
add service to block/unblock apps
improve setup exception handling
  • Loading branch information
pantherale0 committed Sep 20, 2023
1 parent 6aaa541 commit fa3cca7
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 28 deletions.
11 changes: 8 additions & 3 deletions custom_components/family_safety/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import logging

from pyfamilysafety import FamilySafety
from pyfamilysafety.exceptions import HttpException
from pyfamilysafety.exceptions import HttpException, Unauthorized, AggregatorException

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
Expand All @@ -13,7 +13,7 @@
HomeAssistantError
)

from .const import DOMAIN
from .const import DOMAIN, AGG_ERROR
from .coordinator import FamilySafetyCoordinator

_LOGGER = logging.getLogger(__name__)
Expand All @@ -34,9 +34,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
familysafety,
entry.options.get("update_interval", entry.data["update_interval"]))
# no need to fetch initial data as this is already handled on creation
except AggregatorException as err:
_LOGGER.error(AGG_ERROR)
raise CannotConnect from err
except Unauthorized as err:
raise ConfigEntryAuthFailed from err
except HttpException as err:
_LOGGER.error(err)
raise ConfigEntryAuthFailed from err
raise CannotConnect from err
except Exception as err:
_LOGGER.error(err)
raise CannotConnect from err
Expand Down
6 changes: 5 additions & 1 deletion custom_components/family_safety/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@

NAME = "Microsoft Family Safety"
DOMAIN = "family_safety"
VERSION = "1.1.0b4"
VERSION = "1.1.0b5"

DEFAULT_OVERRIDE_ENTITIES = [OverrideTarget.MOBILE,
OverrideTarget.WINDOWS,
OverrideTarget.XBOX,
OverrideTarget.ALL_DEVICES]
AGG_TITLE = "Aggregator error occured. "
AGG_HELP = "This is an upstream issue with Microsoft and is usually temporary. "
AGG_FUTURE = "Try reloading the integration in 15 minutes."
AGG_ERROR = f"{AGG_TITLE}{AGG_HELP}{AGG_FUTURE}"
31 changes: 30 additions & 1 deletion custom_components/family_safety/entity_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from datetime import datetime, time, timedelta

from pyfamilysafety import Account
from pyfamilysafety.account import Device
from pyfamilysafety.application import Application
from pyfamilysafety.enum import OverrideTarget, OverrideType

import homeassistant.helpers.device_registry as dr
Expand Down Expand Up @@ -50,6 +50,35 @@ def device_info(self):
entry_type=dr.DeviceEntryType.SERVICE
)

async def async_block_application(self, name: str):
"""Blocks a application with a given app name."""
await [a for a in self._account.applications if a.name == name][0].block_app()

async def async_unblock_application(self, name: str):
"""Blocks a application with a given app name."""
await [a for a in self._account.applications if a.name == name][0].unblock_app()

class ApplicationEntity(ManagedAccountEntity):
"""Defines a application entity."""
def __init__(self,
coordinator: FamilySafetyCoordinator,
idx,
account_id,
app_id: str) -> None:
"""init entity."""
super().__init__(coordinator, idx, account_id, f"override_{str(app_id).lower()}")
self._app_id = app_id

@property
def _application(self) -> Application:
"""Gets the application."""
return self._account.get_application(self._app_id)

@property
def icon(self) -> str | None:
return self._application.icon


class PlatformOverrideEntity(ManagedAccountEntity):
"""Defines a managed device."""

Expand Down
2 changes: 1 addition & 1 deletion custom_components/family_safety/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
"requirements": ["pyfamilysafety==0.2.0"],
"ssdp": [],
"zeroconf": [],
"version": "1.1.0b4",
"version": "1.1.0b5",
"integration_type": "service"
}
71 changes: 50 additions & 21 deletions custom_components/family_safety/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@
import logging
from typing import Any

import voluptuous as vol

from pyfamilysafety import Account

from homeassistant.components.sensor import SensorEntity, SensorDeviceClass
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback, async_get_current_platform

from .coordinator import FamilySafetyCoordinator

from .const import (
DOMAIN
)

from .entity_base import ManagedAccountEntity
from .entity_base import ManagedAccountEntity, ApplicationEntity

_LOGGER = logging.getLogger(__name__)

Expand All @@ -40,6 +42,13 @@ async def async_setup_entry(
account_id=account.user_id
)
)
entities.append(
AccountBalanceSensor(
coordinator=hass.data[DOMAIN][config_entry.entry_id],
idx=None,
account_id=account.user_id
)
)
for app in config_entry.options.get("tracked_applications", []):
entities.append(
ApplicationScreentimeSensor(
Expand All @@ -51,6 +60,44 @@ async def async_setup_entry(
)

async_add_entities(entities, True)
# register services
platform = async_get_current_platform()
platform.async_register_entity_service(
name="block_app",
schema={
vol.Required("name"): str
},
func="async_block_application"
)
platform.async_register_entity_service(
name="unblock_app",
schema={
vol.Required("name"): str
},
func="async_unblock_application"
)

class AccountBalanceSensor(ManagedAccountEntity, SensorEntity):
"""A balance sensor for the account."""
def __init__(self, coordinator: FamilySafetyCoordinator, idx, account_id) -> None:
super().__init__(coordinator, idx, account_id, "balance")

@property
def name(self) -> str:
return "Available Balance"

@property
def native_value(self) -> float:
"""Return balance"""
return self._account.account_balance

@property
def native_unit_of_measurement(self) -> str | None:
return self._account.account_currency

@property
def device_class(self) -> SensorDeviceClass | None:
return SensorDeviceClass.MONETARY

class AccountScreentimeSensor(ManagedAccountEntity, SensorEntity):
"""Aggregate screentime sensor."""
Expand Down Expand Up @@ -91,26 +138,12 @@ def extra_state_attributes(self) -> Mapping[str, Any] | None:
"device_usage": devices
}

class ApplicationScreentimeSensor(ManagedAccountEntity, SensorEntity):
class ApplicationScreentimeSensor(ApplicationEntity, SensorEntity):
"""Application specific screentime sensor"""

def __init__(self,
coordinator: FamilySafetyCoordinator,
idx,
account_id,
app_id) -> None:
super().__init__(coordinator, idx, account_id, f"{app_id}_screentime")
self._app_id = app_id

@property
def name(self) -> str:
return f"{self._application.name} Used Screen Time"

@property
def _application(self):
"""Return the application."""
return self._account.get_application(self._app_id)

@property
def native_value(self) -> float:
"""Return duration (minutes)"""
Expand All @@ -124,10 +157,6 @@ def native_unit_of_measurement(self) -> str | None:
def device_class(self) -> SensorDeviceClass | None:
return SensorDeviceClass.DURATION

@property
def icon(self) -> str | None:
return self._application.icon

@property
def extra_state_attributes(self) -> Mapping[str, Any] | None:
return {
Expand Down
20 changes: 20 additions & 0 deletions custom_components/family_safety/services.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
block_app:
target:
entity:
integration: family_safety
fields:
name:
required: True
selector:
text:
multiline: False
unblock_app:
target:
entity:
integration: family_safety
fields:
name:
required: True
selector:
text:
multiline: False
33 changes: 32 additions & 1 deletion custom_components/family_safety/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
DEFAULT_OVERRIDE_ENTITIES
)

from .entity_base import PlatformOverrideEntity
from .entity_base import PlatformOverrideEntity, ApplicationEntity

_LOGGER = logging.getLogger(__name__)

Expand All @@ -43,9 +43,40 @@ async def async_setup_entry(
platform=platform
)
)
for app in config_entry.options.get("tracked_applications", []):
entities.append(
ApplicationBlockSwitch(
coordinator=hass.data[DOMAIN][config_entry.entry_id],
idx=None,
account_id=account.user_id,
app_id=app
)
)

async_add_entities(entities, True)

class ApplicationBlockSwitch(ApplicationEntity, SwitchEntity):
"""Defines a application switch."""
@property
def name(self) -> str:
return f"Block {self._application.name}"

@property
def is_on(self) -> bool:
return self._application.blocked

@property
def device_class(self) -> SwitchDeviceClass | None:
return SwitchDeviceClass.SWITCH

async def async_turn_off(self, **kwargs: Any) -> None:
await self._application.unblock_app()
await self.coordinator.async_request_refresh()

async def async_turn_on(self, **kwargs: Any) -> None:
await self._application.block_app()
await self.coordinator.async_request_refresh()

class PlatformOverrideSwitch(PlatformOverrideEntity, SwitchEntity):
"""Platform override switch."""

Expand Down

0 comments on commit fa3cca7

Please sign in to comment.