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
This commit is contained in:
RyanClark123
2023-05-09 16:37:25 +01:00
parent daff165593
commit a5a8336006
8 changed files with 153 additions and 99 deletions
+30 -19
View File
@@ -3,14 +3,24 @@ from __future__ import annotations
import logging import logging
from pyowletapi.owlet import Owlet from pyowletapi.api import OwletAPI
from pyowletapi.sock import Sock
from pyowletapi.exceptions import OwletAuthenticationError, OwletDevicesError from pyowletapi.exceptions import OwletAuthenticationError, OwletDevicesError
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession 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 from .coordinator import OwletCoordinator
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR] 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.""" """Set up Owlet Smart Sock from a config entry."""
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
owlet = Owlet( owlet_api = OwletAPI(
entry.data[CONF_OWLET_REGION], entry.data[CONF_OWLET_REGION],
entry.data[CONF_OWLET_USERNAME], entry.data[CONF_OWLET_USERNAME],
entry.data[CONF_OWLET_PASSWORD], entry.data[CONF_OWLET_PASSWORD],
entry.data[CONF_OWLET_TOKEN],
entry.data[CONF_OWLET_EXPIRY],
async_get_clientsession(hass), async_get_clientsession(hass),
) )
try: 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: except OwletAuthenticationError as err:
_LOGGER.error("Login failed %s", err) _LOGGER.error("Login failed %s", err)
return False return False
existing_socks = entry.data["devices"] coordinators = [
new_socks = [] OwletCoordinator(hass, sock, entry.options.get(CONF_OWLET_POLLINTERVAL))
try: for sock in socks.values()
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()]
for coordinator in coordinators: for coordinator in coordinators:
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
+45 -25
View File
@@ -4,7 +4,7 @@ from __future__ import annotations
import logging import logging
from typing import Any from typing import Any
from pyowletapi.owlet import Owlet from pyowletapi.api import OwletAPI
from pyowletapi.sock import Sock from pyowletapi.sock import Sock
from pyowletapi.exceptions import ( from pyowletapi.exceptions import (
OwletConnectionError, OwletConnectionError,
@@ -18,22 +18,27 @@ from homeassistant import config_entries
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import config_validation from homeassistant.core import callback
from .const import ( from .const import (
DOMAIN, DOMAIN,
CONF_OWLET_REGION, CONF_OWLET_REGION,
CONF_OWLET_USERNAME, CONF_OWLET_USERNAME,
CONF_OWLET_PASSWORD, CONF_OWLET_PASSWORD,
CONF_OWLET_POLLINTERVAL,
CONF_OWLET_TOKEN,
CONF_OWLET_EXPIRY,
POLLING_INTERVAL,
SUPPORTED_VERSIONS,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
STEP_USER_DATA_SCHEMA = vol.Schema( STEP_USER_DATA_SCHEMA = vol.Schema(
{ {
vol.Required(CONF_OWLET_REGION): vol.In(["europe", "world"]), vol.Required("region"): vol.In(["europe", "world"]),
vol.Required(CONF_OWLET_USERNAME): str, vol.Required("username"): str,
vol.Required(CONF_OWLET_PASSWORD): str, vol.Required("password"): str,
} }
) )
@@ -60,7 +65,7 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self._username = user_input[CONF_OWLET_USERNAME] self._username = user_input[CONF_OWLET_USERNAME]
self._password = user_input[CONF_OWLET_PASSWORD] self._password = user_input[CONF_OWLET_PASSWORD]
owlet = Owlet( owlet_api = OwletAPI(
self._region, self._region,
self._username, self._username,
self._password, self._password,
@@ -71,10 +76,20 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self._abort_if_unique_id_configured() self._abort_if_unique_id_configured()
try: try:
await owlet.authenticate() token = await owlet_api.authenticate()
try: try:
self._devices = await owlet.get_devices() await owlet_api.get_devices(SUPPORTED_VERSIONS)
return await self.async_step_socks() 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: except OwletDevicesError:
errors["base"] = "no_devices" 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 step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
) )
async def async_step_socks(self, user_input=None): @staticmethod
"""Allow the user to choose which devices to configure""" @callback
errors = {} 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: if user_input is not None:
return self.async_create_entry( return self.async_create_entry(title="", data=user_input)
title="Owlet",
data={
CONF_OWLET_REGION: self._region,
CONF_OWLET_USERNAME: self._username,
CONF_OWLET_PASSWORD: self._password,
"devices": user_input["socks"],
},
)
schema = vol.Schema( schema = vol.Schema(
{ {
vol.Required("socks"): config_validation.multi_select( vol.Required(
{sock: sock for sock in list(self._devices.keys())} 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)
+5 -1
View File
@@ -5,7 +5,11 @@ DOMAIN = "owlet"
CONF_OWLET_REGION = "region" CONF_OWLET_REGION = "region"
CONF_OWLET_USERNAME = "username" CONF_OWLET_USERNAME = "username"
CONF_OWLET_PASSWORD = "password" 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 POLLING_INTERVAL = 10
MANUFACTURER = "Owlet Baby Care" MANUFACTURER = "Owlet Baby Care"
+4 -3
View File
@@ -13,7 +13,6 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import ( from .const import (
DOMAIN, DOMAIN,
POLLING_INTERVAL,
MANUFACTURER, MANUFACTURER,
) )
@@ -23,18 +22,19 @@ _LOGGER = logging.getLogger(__name__)
class OwletCoordinator(DataUpdateCoordinator): class OwletCoordinator(DataUpdateCoordinator):
"""Coordinator is responsible for querying the device at a specified route.""" """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.""" """Initialise a custom coordinator."""
super().__init__( super().__init__(
hass, hass,
_LOGGER, _LOGGER,
name=DOMAIN, name=DOMAIN,
update_interval=timedelta(seconds=POLLING_INTERVAL), update_interval=timedelta(seconds=interval),
) )
assert self.config_entry is not None assert self.config_entry is not None
self._device_unique_id = sock.serial self._device_unique_id = sock.serial
self._model = sock.model self._model = sock.model
self._sw_version = sock.sw_version self._sw_version = sock.sw_version
self._hw_version = sock.version
self.sock = sock self.sock = sock
self.device_info = DeviceInfo( self.device_info = DeviceInfo(
identifiers={(DOMAIN, self._device_unique_id)}, identifiers={(DOMAIN, self._device_unique_id)},
@@ -42,6 +42,7 @@ class OwletCoordinator(DataUpdateCoordinator):
manufacturer=MANUFACTURER, manufacturer=MANUFACTURER,
model=self._model, model=self._model,
sw_version=self._sw_version, sw_version=self._sw_version,
hw_version=self._hw_version,
) )
async def _async_update_data(self) -> None: async def _async_update_data(self) -> None:
+2 -2
View File
@@ -10,7 +10,7 @@
"homekit": {}, "homekit": {},
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"requirements": [ "requirements": [
"pyowletapi==2023.5.7" "pyowletapi==2023.5.17"
], ],
"version":"1.1.0" "version":"1.2.0"
} }
+10 -4
View File
@@ -8,10 +8,6 @@
"username": "Email", "username": "Email",
"password": "Password" "password": "Password"
} }
},
"socks":{
"title": "Configure Socks",
"description":"Select socks to configure"
} }
}, },
"error": { "error": {
@@ -22,5 +18,15 @@
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]" "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"
}
}
}
} }
} }
+10 -4
View File
@@ -8,10 +8,6 @@
"username": "Email", "username": "Email",
"password": "Password" "password": "Password"
} }
},
"socks":{
"title": "Configure Socks",
"description":"Select socks to configure"
} }
}, },
"error": { "error": {
@@ -22,5 +18,15 @@
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]" "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"
}
}
}
} }
} }
+10 -4
View File
@@ -8,10 +8,6 @@
"username": "Email", "username": "Email",
"password": "Password" "password": "Password"
} }
},
"socks":{
"title": "Configure Socks",
"description":"Select socks to configure"
} }
}, },
"error": { "error": {
@@ -22,5 +18,15 @@
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]" "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"
}
}
}
} }
} }