diff --git a/.gitignore b/.gitignore index 94fa16c..6501f93 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ __pycache__ # misc .coverage -.vscode coverage.xml diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..4f4a588 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + // Example of attaching to local debug server + "name": "Python: Attach Local", + "type": "python", + "request": "attach", + "justMyCode": false, + "port": 5678, + "host": "127.0.0.1", // same as the browser that was opened by Docker Desktop. + "pathMappings": [ + { + "localRoot": "${workspaceFolder}", + "remoteRoot": "." + } + ] + } + ] +} diff --git a/config/configuration.yaml b/config/configuration.yaml index 05a6888..e7c4cf5 100644 --- a/config/configuration.yaml +++ b/config/configuration.yaml @@ -6,6 +6,7 @@ logger: default: info logs: custom_components.family_safety: debug + pyfamilysafety: debug # Enable VSCode debugging debugpy: diff --git a/custom_components/family_safety/__init__.py b/custom_components/family_safety/__init__.py index 184633f..a8af3d4 100644 --- a/custom_components/family_safety/__init__.py +++ b/custom_components/family_safety/__init__.py @@ -24,17 +24,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data.setdefault(DOMAIN, {}) _LOGGER.debug("Got request to setup entry.") try: - familysafety = None - if len(entry.data["refresh_token"]) > 0: - familysafety = await FamilySafety.create( - token=entry.data["refresh_token"], - use_refresh_token=True - ) - else: - familysafety = await FamilySafety.create( - token=entry.data["response_url"], - use_refresh_token=False - ) + familysafety = await FamilySafety.create( + token=entry.data["refresh_token"], + use_refresh_token=True + ) _LOGGER.debug("Login successful, setting up coordinator.") hass.data[DOMAIN][entry.entry_id] = FamilySafetyCoordinator( hass, diff --git a/custom_components/family_safety/config_flow.py b/custom_components/family_safety/config_flow.py index be403d3..3f08ebd 100644 --- a/custom_components/family_safety/config_flow.py +++ b/custom_components/family_safety/config_flow.py @@ -3,12 +3,12 @@ import logging from typing import Any -from pyfamilysafety import FamilySafetyAPI +from pyfamilysafety.authenticator import Authenticator from pyfamilysafety.exceptions import HttpException import voluptuous as vol from homeassistant import config_entries -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError @@ -18,27 +18,20 @@ CONFIG_SCHEMA = vol.Schema( { - vol.Optional("refresh_token"): str, - vol.Optional("response_url"): str, + vol.Required("response_url"): str, vol.Required("update_interval", default=60): int } ) async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]: """Validate the input.""" - familysafety: FamilySafetyAPI = None + auth: Authenticator = None try: _LOGGER.debug("Config flow received -> test credentials") - if len(data["refresh_token"])>0: - familysafety = await FamilySafetyAPI.create( - token=data["refresh_token"], - use_refresh_token=True - ) - else: - familysafety = await FamilySafetyAPI.create( - token=data["response_url"], - use_refresh_token=False - ) + auth = await Authenticator.create( + token=data["response_url"], + use_refresh_token=False + ) except HttpException as err: _LOGGER.error(err) raise InvalidAuth from err @@ -46,10 +39,10 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, _LOGGER.error(err) raise CannotConnect from err - _LOGGER.debug("Authentication success, returning refresh_token.") + _LOGGER.debug("Authentication success, expiry time %s, returning refresh_token.", auth.expires) return { "title": "Microsoft Family Safety", - "refresh_token": familysafety.authenticator.refresh_token + "refresh_token": auth.refresh_token } class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -57,6 +50,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 + @staticmethod + @callback + def async_get_options_flow( + config_entry: config_entries.ConfigEntry + ) -> config_entries.OptionsFlow: + """Create the options flow.""" + return OptionsFlow(config_entry) + async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: @@ -65,17 +66,17 @@ async def async_step_user( if user_input is not None: try: info = await validate_input(self.hass, user_input) + user_input["refresh_token"] = info["refresh_token"] + return self.async_create_entry(title=info["title"], data=user_input) except InvalidAuth as err: + _LOGGER.warning("Invalid authentication received: %s", err) errors["base"] = "invalid_auth" except CannotConnect as err: + _LOGGER.warning("Cannot connect: %s", err) errors["base"] = "cannot_connect" except Exception as err: _LOGGER.error(err) errors["base"] = "unknown" - else: - user_input["response_url"] = "" - user_input["refresh_token"] = info["refresh_token"] - return self.async_create_entry(title=info["title"], data=user_input) return self.async_show_form( step_id="user", @@ -83,6 +84,40 @@ async def async_step_user( errors=errors ) +class OptionsFlow(config_entries.OptionsFlow): + """An options flow for HASS.""" + def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + """Initialize options flow.""" + self.config_entry = config_entry + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> config_entries.FlowResult: + """Initial step.""" + if user_input is not None: + return self.async_create_entry( + title=self.config_entry.title, + data={ + "refresh_token": user_input["refresh_token"], + "update_interval": user_input["update_interval"] + } + ) + + update_interval = self.config_entry.data["update_interval"] + if self.config_entry.options: + update_interval = self.config_entry.options.get("update_interval") + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Required("update_interval", default=update_interval): int, + vol.Required("refresh_token", + default=self.config_entry.data["refresh_token"]): str + } + ) + ) + class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/custom_components/family_safety/const.py b/custom_components/family_safety/const.py index 68b601c..f7861a0 100644 --- a/custom_components/family_safety/const.py +++ b/custom_components/family_safety/const.py @@ -6,7 +6,7 @@ NAME = "Microsoft Family Safety" DOMAIN = "family_safety" -VERSION = "0.0.0" +VERSION = "1.1.0b0" DEFAULT_OVERRIDE_ENTITIES = [OverrideTarget.MOBILE, OverrideTarget.WINDOWS, diff --git a/custom_components/family_safety/manifest.json b/custom_components/family_safety/manifest.json index 2440774..520eb0b 100644 --- a/custom_components/family_safety/manifest.json +++ b/custom_components/family_safety/manifest.json @@ -8,9 +8,9 @@ "documentation": "https://github.com/pantherale0/ha-familysafety", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/pantherale0/ha-familysafety/issues", - "requirements": ["pyfamilysafety==0.0.5"], + "requirements": ["pyfamilysafety==0.1.0"], "ssdp": [], "zeroconf": [], - "version": "1.0.0", + "version": "1.1.0b0", "integration_type": "service" } \ No newline at end of file diff --git a/custom_components/family_safety/translations/en.json b/custom_components/family_safety/translations/en.json index 35b1514..ac0201f 100644 --- a/custom_components/family_safety/translations/en.json +++ b/custom_components/family_safety/translations/en.json @@ -16,5 +16,16 @@ "connection": "Unable to connect to the server.", "unknown": "Unknown error occurred." } + }, + "options": { + "step": { + "init": { + "description": "Update Microsoft Family Safety configuration", + "data": { + "update_interval": "Update interval (seconds)", + "refresh_token": "Refresh token" + } + } + } } } \ No newline at end of file