Updated config flow

-Config flow now allows user to select socks to configure
-Added strings to config
-Amended sensor state classes
-General Tidying up
This commit is contained in:
RyanClark123
2023-05-05 16:23:52 +01:00
parent 1ede92c48f
commit daff165593
9 changed files with 125 additions and 50 deletions
+5 -6
View File
@@ -31,7 +31,7 @@ class OwletBinarySensorEntityDescription(
"""Represent the owlet binary sensor entity description.""" """Represent the owlet binary sensor entity description."""
SENSOR_TYPES: tuple[OwletBinarySensorEntityDescription, ...] = ( SENSORS: tuple[OwletBinarySensorEntityDescription, ...] = (
OwletBinarySensorEntityDescription( OwletBinarySensorEntityDescription(
key="charging", key="charging",
name="Charging", name="Charging",
@@ -98,9 +98,7 @@ async def async_setup_entry(
coordinator: OwletCoordinator = hass.data[DOMAIN][config_entry.entry_id] coordinator: OwletCoordinator = hass.data[DOMAIN][config_entry.entry_id]
entities = [ entities = [OwletBinarySensor(coordinator, sensor) for sensor in SENSORS]
OwletBinarySensor(coordinator, description) for description in SENSOR_TYPES
]
async_add_entities(entities) async_add_entities(entities)
@@ -111,10 +109,10 @@ class OwletBinarySensor(OwletBaseEntity, BinarySensorEntity):
def __init__( def __init__(
self, self,
coordinator: OwletCoordinator, coordinator: OwletCoordinator,
description: OwletBinarySensorEntityDescription, sensor_description: OwletBinarySensorEntityDescription,
) -> None: ) -> None:
"""Initialize the binary sensor.""" """Initialize the binary sensor."""
self.entity_description = description self.entity_description = sensor_description
self._attr_unique_id = ( self._attr_unique_id = (
f"{coordinator.config_entry.entry_id}-{self.entity_description.name}" f"{coordinator.config_entry.entry_id}-{self.entity_description.name}"
) )
@@ -122,4 +120,5 @@ class OwletBinarySensor(OwletBaseEntity, BinarySensorEntity):
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:
"""Return true if the binary sensor is on."""
return self.sock.properties[self.entity_description.element] return self.sock.properties[self.entity_description.element]
+24 -5
View File
@@ -5,6 +5,7 @@ import logging
from typing import Any from typing import Any
from pyowletapi.owlet import Owlet from pyowletapi.owlet import Owlet
from pyowletapi.sock import Sock
from pyowletapi.exceptions import ( from pyowletapi.exceptions import (
OwletConnectionError, OwletConnectionError,
OwletAuthenticationError, OwletAuthenticationError,
@@ -17,6 +18,7 @@ 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 .const import ( from .const import (
DOMAIN, DOMAIN,
@@ -46,6 +48,7 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self._region: str self._region: str
self._username: str self._username: str
self._password: str self._password: str
self._devices: dict[str, Sock]
async def async_step_user( async def async_step_user(
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
@@ -70,7 +73,8 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
try: try:
await owlet.authenticate() await owlet.authenticate()
try: try:
devices = await owlet.get_devices() self._devices = await owlet.get_devices()
return await self.async_step_socks()
except OwletDevicesError: except OwletDevicesError:
errors["base"] = "no_devices" errors["base"] = "no_devices"
@@ -81,17 +85,32 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception") _LOGGER.exception("Unexpected exception")
errors["base"] = "unknown" errors["base"] = "unknown"
else:
return self.async_show_form(
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 = {}
if user_input is not None:
return self.async_create_entry( return self.async_create_entry(
title="Owlet", title="Owlet",
data={ data={
CONF_OWLET_REGION: self._region, CONF_OWLET_REGION: self._region,
CONF_OWLET_USERNAME: self._username, CONF_OWLET_USERNAME: self._username,
CONF_OWLET_PASSWORD: self._password, CONF_OWLET_PASSWORD: self._password,
"devices": list(devices.keys()), "devices": user_input["socks"],
}, },
) )
return self.async_show_form( schema = vol.Schema(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors {
vol.Required("socks"): config_validation.multi_select(
{sock: sock for sock in list(self._devices.keys())}
),
}
) )
return self.async_show_form(step_id="socks", data_schema=schema, errors=errors)
+1 -1
View File
@@ -7,5 +7,5 @@ CONF_OWLET_USERNAME = "username"
CONF_OWLET_PASSWORD = "password" CONF_OWLET_PASSWORD = "password"
CONF_SOCK_SERIAL = "sock_serial" CONF_SOCK_SERIAL = "sock_serial"
POLLING_INTERVAL = 60 POLLING_INTERVAL = 10
MANUFACTURER = "Owlet Baby Care" MANUFACTURER = "Owlet Baby Care"
+1 -1
View File
@@ -7,7 +7,7 @@ import logging
from pyowletapi.sock import Sock from pyowletapi.sock import Sock
from pyowletapi.exceptions import OwletError from pyowletapi.exceptions import OwletError
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
+1 -1
View File
@@ -12,5 +12,5 @@
"requirements": [ "requirements": [
"pyowletapi==2023.5.7" "pyowletapi==2023.5.7"
], ],
"version":"1.0.0" "version":"1.1.0"
} }
+30 -9
View File
@@ -6,6 +6,7 @@ from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
SensorEntity, SensorEntity,
SensorEntityDescription, SensorEntityDescription,
SensorStateClass,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@@ -35,32 +36,37 @@ class OwletSensorEntityDescription(
"""Represent the owlet sensor entity description.""" """Represent the owlet sensor entity description."""
SENSOR_TYPES: tuple[OwletSensorEntityDescription, ...] = ( SENSORS: tuple[OwletSensorEntityDescription, ...] = (
OwletSensorEntityDescription( OwletSensorEntityDescription(
key="batterypercentage", key="batterypercentage",
name="Battery", name="Battery",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY, device_class=SensorDeviceClass.BATTERY,
state_class=SensorStateClass.MEASUREMENT,
element="battery_percentage", element="battery_percentage",
), ),
OwletSensorEntityDescription( OwletSensorEntityDescription(
key="oxygensaturation", key="oxygensaturation",
name="O2 Saturation", name="O2 Saturation",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
element="oxygen_saturation", element="oxygen_saturation",
icon="mdi:leaf", icon="mdi:leaf",
), ),
OwletSensorEntityDescription( OwletSensorEntityDescription(
key="heartrate", key="heartrate",
name="Heart rate", name="Heart rate",
native_unit_of_measurement="bpm",
state_class=SensorStateClass.MEASUREMENT,
element="heart_rate", element="heart_rate",
icon="mdi:heart-pulse", icon="mdi:heart-pulse",
), ),
OwletSensorEntityDescription( OwletSensorEntityDescription(
key="batteryminutes", key="batteryminutes",
name="Battery Minutes Remaining", name="Battery Remaining",
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
device_class=SensorDeviceClass.DURATION, device_class=SensorDeviceClass.DURATION,
state_class=SensorStateClass.MEASUREMENT,
element="battery_minutes", element="battery_minutes",
), ),
OwletSensorEntityDescription( OwletSensorEntityDescription(
@@ -68,6 +74,7 @@ SENSOR_TYPES: tuple[OwletSensorEntityDescription, ...] = (
name="Singal Strength", name="Singal Strength",
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
device_class=SensorDeviceClass.SIGNAL_STRENGTH, device_class=SensorDeviceClass.SIGNAL_STRENGTH,
state_class=SensorStateClass.MEASUREMENT,
element="signal_strength", element="signal_strength",
), ),
) )
@@ -78,11 +85,11 @@ async def async_setup_entry(
config_entry: ConfigEntry, config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the IP Webcam sensors from config entry.""" """Set up the owlet sensors from config entry."""
coordinator: OwletCoordinator = hass.data[DOMAIN][config_entry.entry_id] coordinator: OwletCoordinator = hass.data[DOMAIN][config_entry.entry_id]
entities = [OwletSensor(coordinator, description) for description in SENSOR_TYPES] entities = [OwletSensor(coordinator, sensor) for sensor in SENSORS]
async_add_entities(entities) async_add_entities(entities)
@@ -91,16 +98,30 @@ class OwletSensor(OwletBaseEntity, SensorEntity):
"""Representation of an Owlet sensor.""" """Representation of an Owlet sensor."""
def __init__( def __init__(
self, coordinator: OwletCoordinator, description: OwletSensorEntityDescription self,
coordinator: OwletCoordinator,
sensor_description: OwletSensorEntityDescription,
) -> None: ) -> None:
"""Initialize the binary sensor.""" """Initialize the sensor."""
self.entity_description = description self.entity_description = sensor_description
self._attr_unique_id = ( self._attr_unique_id = (
f"{coordinator.config_entry.entry_id}-{self.entity_description.name}" f"{coordinator.config_entry.entry_id}-{self.entity_description.name}"
) )
super().__init__(coordinator) super().__init__(coordinator)
@property @property
def native_value(self) -> float: def native_value(self):
"""Return if motion is detected.""" """Return sensor value"""
if (
self.entity_description.element
in [
"heart_rate",
"battery_minutes",
"oxygen_saturation",
]
and self.sock.properties["charging"]
):
return None
return self.sock.properties[self.entity_description.element] return self.sock.properties[self.entity_description.element]
+8 -3
View File
@@ -2,11 +2,16 @@
"config": { "config": {
"step": { "step": {
"user":{ "user":{
"title": "Enter login details",
"data":{ "data":{
"region": "[%key:common::config_flow::data::host%]", "region": "Region",
"username": "[%key:common::config_flow::data::username%]", "username": "Email",
"password": "[%key:common::config_flow::data::password%]" "password": "Password"
} }
},
"socks":{
"title": "Configure Socks",
"description":"Select socks to configure"
} }
}, },
"error": { "error": {
+18 -13
View File
@@ -1,21 +1,26 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "Device is already configured"
},
"error": {
"cannot_connect": "Failed to connect",
"invalid_auth": "Invalid authentication",
"unknown": "Unexpected error"
},
"step": { "step": {
"user":{ "user":{
"title": "Enter login details",
"data":{ "data":{
"password": "Password", "region": "Region",
"region": "Host", "username": "Email",
"username": "Username" "password": "Password"
} }
} },
"socks":{
"title": "Configure Socks",
"description":"Select socks to configure"
}
},
"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%]"
} }
} }
} }
@@ -0,0 +1,26 @@
{
"config": {
"step": {
"user":{
"title": "Enter login details",
"data":{
"region": "Region",
"username": "Email",
"password": "Password"
}
},
"socks":{
"title": "Configure Socks",
"description":"Select socks to configure"
}
},
"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%]"
}
}
}