diff --git a/custom_components/owlet/binary_sensor.py b/custom_components/owlet/binary_sensor.py index 7233407..0964182 100644 --- a/custom_components/owlet/binary_sensor.py +++ b/custom_components/owlet/binary_sensor.py @@ -31,7 +31,7 @@ class OwletBinarySensorEntityDescription( """Represent the owlet binary sensor entity description.""" -SENSOR_TYPES: tuple[OwletBinarySensorEntityDescription, ...] = ( +SENSORS: tuple[OwletBinarySensorEntityDescription, ...] = ( OwletBinarySensorEntityDescription( key="charging", name="Charging", @@ -98,9 +98,7 @@ async def async_setup_entry( coordinator: OwletCoordinator = hass.data[DOMAIN][config_entry.entry_id] - entities = [ - OwletBinarySensor(coordinator, description) for description in SENSOR_TYPES - ] + entities = [OwletBinarySensor(coordinator, sensor) for sensor in SENSORS] async_add_entities(entities) @@ -111,10 +109,10 @@ class OwletBinarySensor(OwletBaseEntity, BinarySensorEntity): def __init__( self, coordinator: OwletCoordinator, - description: OwletBinarySensorEntityDescription, + sensor_description: OwletBinarySensorEntityDescription, ) -> None: """Initialize the binary sensor.""" - self.entity_description = description + self.entity_description = sensor_description self._attr_unique_id = ( f"{coordinator.config_entry.entry_id}-{self.entity_description.name}" ) @@ -122,4 +120,5 @@ class OwletBinarySensor(OwletBaseEntity, BinarySensorEntity): @property def is_on(self) -> bool: + """Return true if the binary sensor is on.""" return self.sock.properties[self.entity_description.element] diff --git a/custom_components/owlet/config_flow.py b/custom_components/owlet/config_flow.py index f93bdf5..5fdd918 100644 --- a/custom_components/owlet/config_flow.py +++ b/custom_components/owlet/config_flow.py @@ -5,6 +5,7 @@ import logging from typing import Any from pyowletapi.owlet import Owlet +from pyowletapi.sock import Sock from pyowletapi.exceptions import ( OwletConnectionError, OwletAuthenticationError, @@ -17,6 +18,7 @@ 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 .const import ( DOMAIN, @@ -46,6 +48,7 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._region: str self._username: str self._password: str + self._devices: dict[str, Sock] async def async_step_user( self, user_input: dict[str, Any] | None = None @@ -70,7 +73,8 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): try: await owlet.authenticate() try: - devices = await owlet.get_devices() + self._devices = await owlet.get_devices() + return await self.async_step_socks() except OwletDevicesError: errors["base"] = "no_devices" @@ -81,17 +85,32 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" - else: - return self.async_create_entry( - title="Owlet", - data={ - CONF_OWLET_REGION: self._region, - CONF_OWLET_USERNAME: self._username, - CONF_OWLET_PASSWORD: self._password, - "devices": list(devices.keys()), - }, - ) 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( + 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( + { + 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) diff --git a/custom_components/owlet/const.py b/custom_components/owlet/const.py index e41ed82..fb56063 100644 --- a/custom_components/owlet/const.py +++ b/custom_components/owlet/const.py @@ -7,5 +7,5 @@ CONF_OWLET_USERNAME = "username" CONF_OWLET_PASSWORD = "password" CONF_SOCK_SERIAL = "sock_serial" -POLLING_INTERVAL = 60 +POLLING_INTERVAL = 10 MANUFACTURER = "Owlet Baby Care" diff --git a/custom_components/owlet/coordinator.py b/custom_components/owlet/coordinator.py index eeabe8a..51a7a90 100644 --- a/custom_components/owlet/coordinator.py +++ b/custom_components/owlet/coordinator.py @@ -7,7 +7,7 @@ import logging from pyowletapi.sock import Sock 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.update_coordinator import DataUpdateCoordinator, UpdateFailed diff --git a/custom_components/owlet/manifest.json b/custom_components/owlet/manifest.json index 0a34229..c713380 100644 --- a/custom_components/owlet/manifest.json +++ b/custom_components/owlet/manifest.json @@ -12,5 +12,5 @@ "requirements": [ "pyowletapi==2023.5.7" ], - "version":"1.0.0" + "version":"1.1.0" } diff --git a/custom_components/owlet/sensor.py b/custom_components/owlet/sensor.py index ff1a33b..a10a72d 100644 --- a/custom_components/owlet/sensor.py +++ b/custom_components/owlet/sensor.py @@ -6,6 +6,7 @@ from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, SensorEntityDescription, + SensorStateClass, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -35,32 +36,37 @@ class OwletSensorEntityDescription( """Represent the owlet sensor entity description.""" -SENSOR_TYPES: tuple[OwletSensorEntityDescription, ...] = ( +SENSORS: tuple[OwletSensorEntityDescription, ...] = ( OwletSensorEntityDescription( key="batterypercentage", name="Battery", native_unit_of_measurement=PERCENTAGE, device_class=SensorDeviceClass.BATTERY, + state_class=SensorStateClass.MEASUREMENT, element="battery_percentage", ), OwletSensorEntityDescription( key="oxygensaturation", name="O2 Saturation", native_unit_of_measurement=PERCENTAGE, + state_class=SensorStateClass.MEASUREMENT, element="oxygen_saturation", icon="mdi:leaf", ), OwletSensorEntityDescription( key="heartrate", name="Heart rate", + native_unit_of_measurement="bpm", + state_class=SensorStateClass.MEASUREMENT, element="heart_rate", icon="mdi:heart-pulse", ), OwletSensorEntityDescription( key="batteryminutes", - name="Battery Minutes Remaining", + name="Battery Remaining", native_unit_of_measurement=UnitOfTime.MINUTES, device_class=SensorDeviceClass.DURATION, + state_class=SensorStateClass.MEASUREMENT, element="battery_minutes", ), OwletSensorEntityDescription( @@ -68,6 +74,7 @@ SENSOR_TYPES: tuple[OwletSensorEntityDescription, ...] = ( name="Singal Strength", native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, device_class=SensorDeviceClass.SIGNAL_STRENGTH, + state_class=SensorStateClass.MEASUREMENT, element="signal_strength", ), ) @@ -78,11 +85,11 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> 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] - entities = [OwletSensor(coordinator, description) for description in SENSOR_TYPES] + entities = [OwletSensor(coordinator, sensor) for sensor in SENSORS] async_add_entities(entities) @@ -91,16 +98,30 @@ class OwletSensor(OwletBaseEntity, SensorEntity): """Representation of an Owlet sensor.""" def __init__( - self, coordinator: OwletCoordinator, description: OwletSensorEntityDescription + self, + coordinator: OwletCoordinator, + sensor_description: OwletSensorEntityDescription, ) -> None: - """Initialize the binary sensor.""" - self.entity_description = description + """Initialize the sensor.""" + self.entity_description = sensor_description self._attr_unique_id = ( f"{coordinator.config_entry.entry_id}-{self.entity_description.name}" ) super().__init__(coordinator) @property - def native_value(self) -> float: - """Return if motion is detected.""" + def native_value(self): + """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] diff --git a/custom_components/owlet/strings.json b/custom_components/owlet/strings.json index 544726c..a1a346f 100644 --- a/custom_components/owlet/strings.json +++ b/custom_components/owlet/strings.json @@ -1,12 +1,17 @@ { "config": { "step": { - "user": { - "data": { - "region": "[%key:common::config_flow::data::host%]", - "username": "[%key:common::config_flow::data::username%]", - "password": "[%key:common::config_flow::data::password%]" + "user":{ + "title": "Enter login details", + "data":{ + "region": "Region", + "username": "Email", + "password": "Password" } + }, + "socks":{ + "title": "Configure Socks", + "description":"Select socks to configure" } }, "error": { diff --git a/custom_components/owlet/translations/en.json b/custom_components/owlet/translations/en.json index d7d0160..378566d 100644 --- a/custom_components/owlet/translations/en.json +++ b/custom_components/owlet/translations/en.json @@ -1,21 +1,26 @@ { "config": { - "abort": { - "already_configured": "Device is already configured" + "step": { + "user":{ + "title": "Enter login details", + "data":{ + "region": "Region", + "username": "Email", + "password": "Password" + } }, - "error": { - "cannot_connect": "Failed to connect", - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected error" - }, - "step": { - "user": { - "data": { - "password": "Password", - "region": "Host", - "username": "Username" - } - } + "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%]" + } } -} \ 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 new file mode 100644 index 0000000..378566d --- /dev/null +++ b/custom_components/owlet/translations/uk.json @@ -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%]" + } + } + } \ No newline at end of file