From a5a8336006828ed243ccbf687494f3092e053a8b Mon Sep 17 00:00:00 2001 From: RyanClark123 <42173121+RyanClark123@users.noreply.github.com> Date: Tue, 9 May 2023 16:37:25 +0100 Subject: [PATCH] Amended config flow, hardcoded support for v3 -Amended config flow, took out second step to select socks -Added configuration to allow users to change polling interval -Hardcoded support for only v3 sock, I do not own any other socks to test with --- custom_components/owlet/__init__.py | 49 ++++++++------ custom_components/owlet/config_flow.py | 70 +++++++++++++------- custom_components/owlet/const.py | 6 +- custom_components/owlet/coordinator.py | 7 +- custom_components/owlet/manifest.json | 4 +- custom_components/owlet/strings.json | 16 +++-- custom_components/owlet/translations/en.json | 50 ++++++++------ custom_components/owlet/translations/uk.json | 50 ++++++++------ 8 files changed, 153 insertions(+), 99 deletions(-) diff --git a/custom_components/owlet/__init__.py b/custom_components/owlet/__init__.py index 9f75324..03a809f 100644 --- a/custom_components/owlet/__init__.py +++ b/custom_components/owlet/__init__.py @@ -3,14 +3,24 @@ from __future__ import annotations import logging -from pyowletapi.owlet import Owlet +from pyowletapi.api import OwletAPI +from pyowletapi.sock import Sock from pyowletapi.exceptions import OwletAuthenticationError, OwletDevicesError from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession -from .const import DOMAIN, CONF_OWLET_REGION, CONF_OWLET_USERNAME, CONF_OWLET_PASSWORD +from .const import ( + DOMAIN, + CONF_OWLET_REGION, + CONF_OWLET_USERNAME, + CONF_OWLET_PASSWORD, + CONF_OWLET_POLLINTERVAL, + CONF_OWLET_EXPIRY, + CONF_OWLET_TOKEN, + SUPPORTED_VERSIONS, +) from .coordinator import OwletCoordinator PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR] @@ -22,34 +32,35 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Owlet Smart Sock from a config entry.""" hass.data.setdefault(DOMAIN, {}) - owlet = Owlet( + owlet_api = OwletAPI( entry.data[CONF_OWLET_REGION], entry.data[CONF_OWLET_USERNAME], entry.data[CONF_OWLET_PASSWORD], + entry.data[CONF_OWLET_TOKEN], + entry.data[CONF_OWLET_EXPIRY], async_get_clientsession(hass), ) try: - await owlet.authenticate() + token = await owlet_api.authenticate() + + if token: + entry.data[CONF_OWLET_TOKEN] = token[CONF_OWLET_TOKEN] + entry.data[CONF_OWLET_EXPIRY] = token[CONF_OWLET_EXPIRY] + + socks = { + device["device"]["dsn"]: Sock(owlet_api, device["device"]) + for device in await owlet_api.get_devices(SUPPORTED_VERSIONS) + } + except OwletAuthenticationError as err: _LOGGER.error("Login failed %s", err) return False - existing_socks = entry.data["devices"] - new_socks = [] - try: - socks = await owlet.get_devices() - except OwletDevicesError: - pass - - [new_socks.append(sock) for sock in socks if sock not in existing_socks] - - if new_socks: - hass.config_entries.async_update_entry( - entry, data={**entry.data, **{"devices": socks}} - ) - - coordinators = [OwletCoordinator(hass, sock) for sock in socks.values()] + coordinators = [ + OwletCoordinator(hass, sock, entry.options.get(CONF_OWLET_POLLINTERVAL)) + for sock in socks.values() + ] for coordinator in coordinators: await coordinator.async_config_entry_first_refresh() diff --git a/custom_components/owlet/config_flow.py b/custom_components/owlet/config_flow.py index 5fdd918..f3708a6 100644 --- a/custom_components/owlet/config_flow.py +++ b/custom_components/owlet/config_flow.py @@ -4,7 +4,7 @@ from __future__ import annotations import logging from typing import Any -from pyowletapi.owlet import Owlet +from pyowletapi.api import OwletAPI from pyowletapi.sock import Sock from pyowletapi.exceptions import ( OwletConnectionError, @@ -18,22 +18,27 @@ from homeassistant import config_entries from homeassistant.data_entry_flow import FlowResult from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers import config_validation +from homeassistant.core import callback from .const import ( DOMAIN, CONF_OWLET_REGION, CONF_OWLET_USERNAME, CONF_OWLET_PASSWORD, + CONF_OWLET_POLLINTERVAL, + CONF_OWLET_TOKEN, + CONF_OWLET_EXPIRY, + POLLING_INTERVAL, + SUPPORTED_VERSIONS, ) _LOGGER = logging.getLogger(__name__) STEP_USER_DATA_SCHEMA = vol.Schema( { - vol.Required(CONF_OWLET_REGION): vol.In(["europe", "world"]), - vol.Required(CONF_OWLET_USERNAME): str, - vol.Required(CONF_OWLET_PASSWORD): str, + vol.Required("region"): vol.In(["europe", "world"]), + vol.Required("username"): str, + vol.Required("password"): str, } ) @@ -60,7 +65,7 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._username = user_input[CONF_OWLET_USERNAME] self._password = user_input[CONF_OWLET_PASSWORD] - owlet = Owlet( + owlet_api = OwletAPI( self._region, self._username, self._password, @@ -71,10 +76,20 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._abort_if_unique_id_configured() try: - await owlet.authenticate() + token = await owlet_api.authenticate() try: - self._devices = await owlet.get_devices() - return await self.async_step_socks() + await owlet_api.get_devices(SUPPORTED_VERSIONS) + return self.async_create_entry( + title=self._username, + data={ + CONF_OWLET_REGION: self._region, + CONF_OWLET_USERNAME: self._username, + CONF_OWLET_PASSWORD: self._password, + CONF_OWLET_TOKEN: token[CONF_OWLET_TOKEN], + CONF_OWLET_EXPIRY: token[CONF_OWLET_EXPIRY], + }, + options={CONF_OWLET_POLLINTERVAL: POLLING_INTERVAL}, + ) except OwletDevicesError: errors["base"] = "no_devices" @@ -90,27 +105,32 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors ) - async def async_step_socks(self, user_input=None): - """Allow the user to choose which devices to configure""" - errors = {} + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return OptionsFlowHandler(config_entry) + +class OptionsFlowHandler(config_entries.OptionsFlow): + """Handle a options flow for owlet""" + + def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + """Initialise options flow""" + self.config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Handle options flow""" if user_input is not None: - return self.async_create_entry( - title="Owlet", - data={ - CONF_OWLET_REGION: self._region, - CONF_OWLET_USERNAME: self._username, - CONF_OWLET_PASSWORD: self._password, - "devices": user_input["socks"], - }, - ) + return self.async_create_entry(title="", data=user_input) schema = vol.Schema( { - vol.Required("socks"): config_validation.multi_select( - {sock: sock for sock in list(self._devices.keys())} - ), + vol.Required( + CONF_OWLET_POLLINTERVAL, + default=self.config_entry.options.get(CONF_OWLET_POLLINTERVAL), + ): vol.All(vol.Coerce(int), vol.Range(min=10)), } ) - return self.async_show_form(step_id="socks", data_schema=schema, errors=errors) + return self.async_show_form(step_id="init", data_schema=schema) diff --git a/custom_components/owlet/const.py b/custom_components/owlet/const.py index fb56063..6fe9003 100644 --- a/custom_components/owlet/const.py +++ b/custom_components/owlet/const.py @@ -5,7 +5,11 @@ DOMAIN = "owlet" CONF_OWLET_REGION = "region" CONF_OWLET_USERNAME = "username" CONF_OWLET_PASSWORD = "password" -CONF_SOCK_SERIAL = "sock_serial" +CONF_OWLET_DEVICES = "devices" +CONF_OWLET_POLLINTERVAL = "pollinterval" +CONF_OWLET_TOKEN = "token" +CONF_OWLET_EXPIRY = "expiry" +SUPPORTED_VERSIONS = [3] POLLING_INTERVAL = 10 MANUFACTURER = "Owlet Baby Care" diff --git a/custom_components/owlet/coordinator.py b/custom_components/owlet/coordinator.py index 51a7a90..15a00c9 100644 --- a/custom_components/owlet/coordinator.py +++ b/custom_components/owlet/coordinator.py @@ -13,7 +13,6 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda from .const import ( DOMAIN, - POLLING_INTERVAL, MANUFACTURER, ) @@ -23,18 +22,19 @@ _LOGGER = logging.getLogger(__name__) class OwletCoordinator(DataUpdateCoordinator): """Coordinator is responsible for querying the device at a specified route.""" - def __init__(self, hass: HomeAssistant, sock: Sock) -> None: + def __init__(self, hass: HomeAssistant, sock: Sock, interval: int) -> None: """Initialise a custom coordinator.""" super().__init__( hass, _LOGGER, name=DOMAIN, - update_interval=timedelta(seconds=POLLING_INTERVAL), + update_interval=timedelta(seconds=interval), ) assert self.config_entry is not None self._device_unique_id = sock.serial self._model = sock.model self._sw_version = sock.sw_version + self._hw_version = sock.version self.sock = sock self.device_info = DeviceInfo( identifiers={(DOMAIN, self._device_unique_id)}, @@ -42,6 +42,7 @@ class OwletCoordinator(DataUpdateCoordinator): manufacturer=MANUFACTURER, model=self._model, sw_version=self._sw_version, + hw_version=self._hw_version, ) async def _async_update_data(self) -> None: diff --git a/custom_components/owlet/manifest.json b/custom_components/owlet/manifest.json index c713380..11ba518 100644 --- a/custom_components/owlet/manifest.json +++ b/custom_components/owlet/manifest.json @@ -10,7 +10,7 @@ "homekit": {}, "iot_class": "cloud_polling", "requirements": [ - "pyowletapi==2023.5.7" + "pyowletapi==2023.5.17" ], - "version":"1.1.0" + "version":"1.2.0" } diff --git a/custom_components/owlet/strings.json b/custom_components/owlet/strings.json index a1a346f..0e2d550 100644 --- a/custom_components/owlet/strings.json +++ b/custom_components/owlet/strings.json @@ -8,10 +8,6 @@ "username": "Email", "password": "Password" } - }, - "socks":{ - "title": "Configure Socks", - "description":"Select socks to configure" } }, "error": { @@ -22,5 +18,15 @@ "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } + }, + "options": { + "step": { + "init":{ + "title":"Configure options for Owlet", + "data":{ + "pollinterval": "Polling interval in seconds, min 10" + } + } + } } -} +} \ No newline at end of file diff --git a/custom_components/owlet/translations/en.json b/custom_components/owlet/translations/en.json index 378566d..0e2d550 100644 --- a/custom_components/owlet/translations/en.json +++ b/custom_components/owlet/translations/en.json @@ -1,26 +1,32 @@ { - "config": { - "step": { - "user":{ - "title": "Enter login details", - "data":{ - "region": "Region", - "username": "Email", - "password": "Password" - } - }, - "socks":{ - "title": "Configure Socks", - "description":"Select socks to configure" + "config": { + "step": { + "user":{ + "title": "Enter login details", + "data":{ + "region": "Region", + "username": "Email", + "password": "Password" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } + }, + "options": { + "step": { + "init":{ + "title":"Configure options for Owlet", + "data":{ + "pollinterval": "Polling interval in seconds, min 10" } - }, - "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", - "unknown": "[%key:common::config_flow::error::unknown%]" - }, - "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/custom_components/owlet/translations/uk.json b/custom_components/owlet/translations/uk.json index 378566d..0e2d550 100644 --- a/custom_components/owlet/translations/uk.json +++ b/custom_components/owlet/translations/uk.json @@ -1,26 +1,32 @@ { - "config": { - "step": { - "user":{ - "title": "Enter login details", - "data":{ - "region": "Region", - "username": "Email", - "password": "Password" - } - }, - "socks":{ - "title": "Configure Socks", - "description":"Select socks to configure" + "config": { + "step": { + "user":{ + "title": "Enter login details", + "data":{ + "region": "Region", + "username": "Email", + "password": "Password" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } + }, + "options": { + "step": { + "init":{ + "title":"Configure options for Owlet", + "data":{ + "pollinterval": "Polling interval in seconds, min 10" } - }, - "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", - "unknown": "[%key:common::config_flow::error::unknown%]" - }, - "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } - } \ No newline at end of file + } +} \ No newline at end of file