Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc28ebb02f | ||
|
|
52710ba7de | ||
|
|
d8449c14a4 | ||
|
|
3610262855 | ||
|
|
904023e93a | ||
|
|
ceade24851 | ||
|
|
ab486d5519 | ||
|
|
5e17ecdeb2 | ||
|
|
4b90ce0d61 | ||
|
|
50c55dcfd3 | ||
|
|
faefd0b18b | ||
|
|
1192b833ca | ||
|
|
d440fed621 | ||
|
|
50fe1a8765 | ||
|
|
1cfff537d7 | ||
|
|
3acf847352 | ||
|
|
c0bf404f6a | ||
|
|
0a7f703100 | ||
|
|
cab737cae4 | ||
|
|
092321cbae | ||
|
|
835786b89b | ||
|
|
2cd46c18f8 | ||
|
|
575b213ddd | ||
|
|
3afa43c82c |
62
CHANGELOG.md
62
CHANGELOG.md
@@ -1,6 +1,54 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
<!--next-version-placeholder-->
|
<!--next-version-placeholder-->
|
||||||
|
## 2024.6.1 (2024-06-17)
|
||||||
|
### Fix
|
||||||
|
* Bumping pyowletapi to 2024.6.1 to resolve setup errors ([`52710ba`](https://github.com/ryanbdclark/owlet/commit/52710ba7de53fd07195537c2a5fd2f95bc7dfd1a))
|
||||||
|
|
||||||
|
## 2024.5.1 (2024-05-13)
|
||||||
|
### Feature
|
||||||
|
* As per HA core patterns, certain sensors will now show as unavailable when sock is charging ([`ceade24`](https://github.com/ryanbdclark/owlet/commit/ceade24851479b8c9bc60b7b8bed74a7bdb927e9))
|
||||||
|
* Oxygen 10 minute average now only shows a figure if it is between 0 and 100 this avoids skewing by 255 values before the 10 minutes is reached, thanks @coreywillwhat ([`5e17ecd`](https://github.com/ryanbdclark/owlet/commit/5e17ecdeb2aca5bbb35f19ca5795a2c5e0f776ab))
|
||||||
|
### Fix
|
||||||
|
* Refactoring as per core maintainers suggestions ([`ceade24`](https://github.com/ryanbdclark/owlet/commit/ceade24851479b8c9bc60b7b8bed74a7bdb927e9))
|
||||||
|
|
||||||
|
|
||||||
|
## 2024.3.1
|
||||||
|
### Feature
|
||||||
|
* Base station on added as binary sensor ([`50c55dc`](https://github.com/ryanbdclark/owlet/commit/50c55dcfd30d15027155a8f1d05340238501522d))
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
* Bumping pyowletapi to 2024.3.2 ([`50c55dc`](https://github.com/ryanbdclark/owlet/commit/50c55dcfd30d15027155a8f1d05340238501522d))
|
||||||
|
* UI config now allows you to set interval to 5 seconds, previously the minimum was 10 ([`50c55dc`](https://github.com/ryanbdclark/owlet/commit/50c55dcfd30d15027155a8f1d05340238501522d))
|
||||||
|
|
||||||
|
## 2023.11.2 (2023-11-23)
|
||||||
|
### Feature
|
||||||
|
* Support added for V2 sock ([`50fe1a8`](https://github.com/ryanbdclark/owlet/commit/50fe1a87656b7d6413d06f06f3650fd0bfb48e02))
|
||||||
|
* Added tests for binary sensors ([`50fe1a8`](https://github.com/ryanbdclark/owlet/commit/50fe1a87656b7d6413d06f06f3650fd0bfb48e02))
|
||||||
|
### Fix
|
||||||
|
* Bumping pyowletapi to 2023.11.4 to allow V2 support ([`50fe1a8`](https://github.com/ryanbdclark/owlet/commit/50fe1a87656b7d6413d06f06f3650fd0bfb48e02))
|
||||||
|
* Refactoring ([`50fe1a8`](https://github.com/ryanbdclark/owlet/commit/50fe1a87656b7d6413d06f06f3650fd0bfb48e02))
|
||||||
|
* Corrected spelling of sock disconnected sensor ([`50fe1a8`](https://github.com/ryanbdclark/owlet/commit/50fe1a87656b7d6413d06f06f3650fd0bfb48e02))
|
||||||
|
|
||||||
|
## 2023.11.1 (2023-11-16)
|
||||||
|
### Fix
|
||||||
|
* Bumping pyowletapi to 2023.11.1 ([`3acf847`](https://github.com/ryanbdclark/owlet/commit/3acf8473526665382b44ef6325d708a6c62fff45))
|
||||||
|
* Sensors and binary sensors are now only created where the sock contains that property, this stops errors where different sock versions have different properties ([`3acf847`](https://github.com/ryanbdclark/owlet/commit/3acf8473526665382b44ef6325d708a6c62fff45))
|
||||||
|
|
||||||
|
## 2023.9.1 (2023-09-20)
|
||||||
|
### Fix
|
||||||
|
* Bumping pyowletapi to 2023.9.1 to allow for revisions ([`0a7f703`](https://github.com/ryanbdclark/owlet/commit/0a7f70310080a129c988e9607331baa2f6c691e0))
|
||||||
|
* New revision of sock, revision 5 doesn't report all vitals as before, this would cause the integration to fail to update. Have adjusted the integration to detect the revision and ignore the vitals that are no longer reported ([`0a7f703`](https://github.com/ryanbdclark/owlet/commit/0a7f70310080a129c988e9607331baa2f6c691e0))
|
||||||
|
|
||||||
|
|
||||||
|
## 2023.8.1 (2023-08-21)
|
||||||
|
### Feature
|
||||||
|
* 2 new sensors, movement and movement bucket disabled by default, thanks [`@seanford`](https://github.com/seanford) ([`575b213`](https://github.com/ryanbdclark/owlet/commit/575b213ddd732779cd7938e575fc87c8881a69b0))
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
* Various refactoring tasks completed to make this integration more inline with home assistants style guidelines ([`2cd46c1`](https://github.com/ryanbdclark/owlet/commit/c45959b123a6e5f77747475f11d3d3ab67859756))
|
||||||
|
* Added new sensors to strings jsons ([`2cd46c1`](https://github.com/ryanbdclark/owlet/commit/c45959b123a6e5f77747475f11d3d3ab67859756))
|
||||||
|
|
||||||
## 2023.7.2 (2023-07-04)
|
## 2023.7.2 (2023-07-04)
|
||||||
### Fix
|
### Fix
|
||||||
* Bumping pyowletapi version to 2023.7.2 ([`c45959b`](https://github.com/ryanbdclark/owlet/commit/c45959b123a6e5f77747475f11d3d3ab67859756))
|
* Bumping pyowletapi version to 2023.7.2 ([`c45959b`](https://github.com/ryanbdclark/owlet/commit/c45959b123a6e5f77747475f11d3d3ab67859756))
|
||||||
@@ -9,11 +57,11 @@
|
|||||||
### Fix
|
### Fix
|
||||||
* Bumping pyowletapi to 2023.7.1 ([`c693fef`](https://github.com/ryanbdclark/owlet/commit/c693fefbf3dba8f35802b87d064401dadbb211b5))
|
* Bumping pyowletapi to 2023.7.1 ([`c693fef`](https://github.com/ryanbdclark/owlet/commit/c693fefbf3dba8f35802b87d064401dadbb211b5))
|
||||||
|
|
||||||
## 2023.05.7 (2023-05-30)
|
## 2023.5.7 (2023-05-30)
|
||||||
### Fix
|
### Fix
|
||||||
* Fixed issue with binary sensors not loading, caused by change to way the coordinators are stored ([`8d17317`](https://github.com/ryanbdclark/owlet/commit/8d173174e286b0451cbb2c0d4ae3087028d1ea23))
|
* Fixed issue with binary sensors not loading, caused by change to way the coordinators are stored ([`8d17317`](https://github.com/ryanbdclark/owlet/commit/8d173174e286b0451cbb2c0d4ae3087028d1ea23))
|
||||||
|
|
||||||
## 2023.05.6 (2023-05-30)
|
## 2023.5.6 (2023-05-30)
|
||||||
### Fix
|
### Fix
|
||||||
* In light of submitting this as a pull request to the core of HA there have been some refactoring changes to comply with HA's style requirements
|
* In light of submitting this as a pull request to the core of HA there have been some refactoring changes to comply with HA's style requirements
|
||||||
* Sensor names now moved to strings file to allow for translations
|
* Sensor names now moved to strings file to allow for translations
|
||||||
@@ -23,25 +71,25 @@
|
|||||||
### Feature
|
### Feature
|
||||||
* Tests added
|
* Tests added
|
||||||
|
|
||||||
## 2023.05.5 (2023-05-19)
|
## 2023.5.5 (2023-05-19)
|
||||||
#### Fix
|
#### Fix
|
||||||
* Owlet refresh token becomes invalid after 24 hours. Meant that after 1 day the integration would stop working. Moved to pyowletapi v2023.5.28 which uses different refresh token, should no longer need reconfiguring after 24 hours ([`dc58b19`](https://github.com/ryanbdclark/owlet/commit/0141f7d01a9ac9b3e1dcc74cabb896e19bd4a821))
|
* Owlet refresh token becomes invalid after 24 hours. Meant that after 1 day the integration would stop working. Moved to pyowletapi v2023.5.28 which uses different refresh token, should no longer need reconfiguring after 24 hours ([`dc58b19`](https://github.com/ryanbdclark/owlet/commit/0141f7d01a9ac9b3e1dcc74cabb896e19bd4a821))
|
||||||
|
|
||||||
## 2023.05.4 (2023-05-17)
|
## 2023.5.4 (2023-05-17)
|
||||||
#### Fix
|
#### Fix
|
||||||
* Bumping to pyowletapi 2023.5.25
|
* Bumping to pyowletapi 2023.5.25
|
||||||
|
|
||||||
## 2023.05.3 (2023-05-17)
|
## 2023.5.3 (2023-05-17)
|
||||||
#### Fix
|
#### Fix
|
||||||
* Bumping to pyowletapi 2023.5.24
|
* Bumping to pyowletapi 2023.5.24
|
||||||
* Reauthing now no longer re adds users' password to config entry
|
* Reauthing now no longer re adds users' password to config entry
|
||||||
|
|
||||||
## 2023.05.2 (2023-05-16)
|
## 2023.5.2 (2023-05-16)
|
||||||
#### Feature
|
#### Feature
|
||||||
* Integration now makes use of refresh token from pyowletapi to reauthenticate, user password in no longer stored by integration ([`dc710a1`](https://github.com/ryanbdclark/owlet/commit/dc710a1783a4cad9d6cf355240fe12ac779a87ef))
|
* Integration now makes use of refresh token from pyowletapi to reauthenticate, user password in no longer stored by integration ([`dc710a1`](https://github.com/ryanbdclark/owlet/commit/dc710a1783a4cad9d6cf355240fe12ac779a87ef))
|
||||||
* New sensors create for baby sleep state ([`9b3392b`](https://github.com/ryanbdclark/owlet/commit/9b3392bdbcd82015ed31d3a50a517e4e22905684))
|
* New sensors create for baby sleep state ([`9b3392b`](https://github.com/ryanbdclark/owlet/commit/9b3392bdbcd82015ed31d3a50a517e4e22905684))
|
||||||
|
|
||||||
## 2023.05.1 (2023-05-15)
|
## 2023.5.1 (2023-05-15)
|
||||||
#### Feature
|
#### Feature
|
||||||
* Changed versioning to date based
|
* Changed versioning to date based
|
||||||
### Fix
|
### Fix
|
||||||
|
|||||||
@@ -8,13 +8,11 @@
|
|||||||
[![hacs][hacsbadge]][hacs]
|
[![hacs][hacsbadge]][hacs]
|
||||||
[![Project Maintenance][maintenance-shield]][user_profile]
|
[![Project Maintenance][maintenance-shield]][user_profile]
|
||||||
|
|
||||||
A custom component for the Owlet smart sock, currently this only supports the owlet smart sock 3.
|
A custom component for the Owlet smart sock
|
||||||
|
|
||||||
If you have a smart sock 2 and would like to contribute then please do so.
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Use [HACS](https://hacs.xyz/docs/setup/download), in `HACS > Integrations > Explore & Add Repositories` search for "Owlet". After adding this `https://github.com/ryanbdclark/owlet` as a custom repository.
|
1. Use [HACS](https://hacs.xyz/docs/setup/download), in `HACS > Integrations > Explore & Add Repositories` search for "Owlet".
|
||||||
2. Restart Home Assistant.
|
2. Restart Home Assistant.
|
||||||
3. [![Add Integration][add-integration-badge]][add-integration] or in the HA UI go to "Settings" -> "Devices & Services" then click "+" and search for "Owlet Smart Sock".
|
3. [![Add Integration][add-integration-badge]][add-integration] or in the HA UI go to "Settings" -> "Devices & Services" then click "+" and search for "Owlet Smart Sock".
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"""The Owlet Smart Sock integration."""
|
"""The Owlet Smart Sock integration."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -72,15 +73,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
entry, data={**entry.data, **devices["tokens"]}
|
entry, data={**entry.data, **devices["tokens"]}
|
||||||
)
|
)
|
||||||
|
|
||||||
socks = {
|
|
||||||
device["device"]["dsn"]: Sock(owlet_api, device["device"])
|
|
||||||
for device in devices["response"]
|
|
||||||
}
|
|
||||||
|
|
||||||
scan_interval = entry.options.get(CONF_SCAN_INTERVAL)
|
scan_interval = entry.options.get(CONF_SCAN_INTERVAL)
|
||||||
coordinators = {
|
coordinators = {
|
||||||
serial: OwletCoordinator(hass, sock, scan_interval)
|
device["device"]["dsn"]: OwletCoordinator(
|
||||||
for (serial, sock) in socks.items()
|
hass, Sock(owlet_api, device["device"]), scan_interval, entry
|
||||||
|
)
|
||||||
|
for device in devices["response"]
|
||||||
}
|
}
|
||||||
|
|
||||||
await asyncio.gather(
|
await asyncio.gather(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"""Support for Owlet binary sensors."""
|
"""Support for Owlet binary sensors."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
@@ -17,80 +18,85 @@ from .coordinator import OwletCoordinator
|
|||||||
from .entity import OwletBaseEntity
|
from .entity import OwletBaseEntity
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass(kw_only=True)
|
||||||
class OwletBinarySensorEntityMixin:
|
class OwletBinarySensorEntityDescription(BinarySensorEntityDescription):
|
||||||
"""Owlet binary sensor element mixin"""
|
|
||||||
|
|
||||||
element: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class OwletBinarySensorEntityDescription(
|
|
||||||
BinarySensorEntityDescription, OwletBinarySensorEntityMixin
|
|
||||||
):
|
|
||||||
"""Represent the owlet binary sensor entity description."""
|
"""Represent the owlet binary sensor entity description."""
|
||||||
|
|
||||||
|
available_during_charging: bool
|
||||||
|
|
||||||
|
|
||||||
SENSORS: tuple[OwletBinarySensorEntityDescription, ...] = (
|
SENSORS: tuple[OwletBinarySensorEntityDescription, ...] = (
|
||||||
OwletBinarySensorEntityDescription(
|
OwletBinarySensorEntityDescription(
|
||||||
key="charging",
|
key="charging",
|
||||||
translation_key="charging",
|
translation_key="charging",
|
||||||
device_class=BinarySensorDeviceClass.BATTERY_CHARGING,
|
device_class=BinarySensorDeviceClass.BATTERY_CHARGING,
|
||||||
element="charging",
|
available_during_charging=True,
|
||||||
),
|
),
|
||||||
OwletBinarySensorEntityDescription(
|
OwletBinarySensorEntityDescription(
|
||||||
key="highhr",
|
key="high_heart_rate_alert",
|
||||||
translation_key="high_hr_alrt",
|
translation_key="high_hr_alrt",
|
||||||
device_class=BinarySensorDeviceClass.SOUND,
|
device_class=BinarySensorDeviceClass.SOUND,
|
||||||
element="high_heart_rate_alert",
|
available_during_charging=True,
|
||||||
),
|
),
|
||||||
OwletBinarySensorEntityDescription(
|
OwletBinarySensorEntityDescription(
|
||||||
key="lowhr",
|
key="low_heart_rate_alert",
|
||||||
translation_key="low_hr_alrt",
|
translation_key="low_hr_alrt",
|
||||||
device_class=BinarySensorDeviceClass.SOUND,
|
device_class=BinarySensorDeviceClass.SOUND,
|
||||||
element="low_heart_rate_alert",
|
available_during_charging=True,
|
||||||
),
|
),
|
||||||
OwletBinarySensorEntityDescription(
|
OwletBinarySensorEntityDescription(
|
||||||
key="higho2",
|
key="high_oxygen_alert",
|
||||||
translation_key="high_ox_alrt",
|
translation_key="high_ox_alrt",
|
||||||
device_class=BinarySensorDeviceClass.SOUND,
|
device_class=BinarySensorDeviceClass.SOUND,
|
||||||
element="high_oxygen_alert",
|
available_during_charging=True,
|
||||||
),
|
),
|
||||||
OwletBinarySensorEntityDescription(
|
OwletBinarySensorEntityDescription(
|
||||||
key="lowo2",
|
key="low_oxygen_alert",
|
||||||
translation_key="low_ox_alrt",
|
translation_key="low_ox_alrt",
|
||||||
device_class=BinarySensorDeviceClass.SOUND,
|
device_class=BinarySensorDeviceClass.SOUND,
|
||||||
element="low_oxygen_alert",
|
available_during_charging=True,
|
||||||
),
|
),
|
||||||
OwletBinarySensorEntityDescription(
|
OwletBinarySensorEntityDescription(
|
||||||
key="lowbattery",
|
key="critical_oxygen_alert",
|
||||||
|
translation_key="crit_ox_alrt",
|
||||||
|
device_class=BinarySensorDeviceClass.SOUND,
|
||||||
|
available_during_charging=True,
|
||||||
|
),
|
||||||
|
OwletBinarySensorEntityDescription(
|
||||||
|
key="low_battery_alert",
|
||||||
translation_key="low_batt_alrt",
|
translation_key="low_batt_alrt",
|
||||||
device_class=BinarySensorDeviceClass.SOUND,
|
device_class=BinarySensorDeviceClass.SOUND,
|
||||||
element="low_battery_alert",
|
available_during_charging=True,
|
||||||
),
|
),
|
||||||
OwletBinarySensorEntityDescription(
|
OwletBinarySensorEntityDescription(
|
||||||
key="lostpower",
|
key="critical_battery_alert",
|
||||||
|
translation_key="crit_batt_alrt",
|
||||||
|
device_class=BinarySensorDeviceClass.SOUND,
|
||||||
|
available_during_charging=True,
|
||||||
|
),
|
||||||
|
OwletBinarySensorEntityDescription(
|
||||||
|
key="lost_power_alert",
|
||||||
translation_key="lost_pwr_alrt",
|
translation_key="lost_pwr_alrt",
|
||||||
device_class=BinarySensorDeviceClass.SOUND,
|
device_class=BinarySensorDeviceClass.SOUND,
|
||||||
element="lost_power_alert",
|
available_during_charging=True,
|
||||||
),
|
),
|
||||||
OwletBinarySensorEntityDescription(
|
OwletBinarySensorEntityDescription(
|
||||||
key="sockdisconnected",
|
key="sock_disconnected",
|
||||||
translation_key="sock_discon_alrt",
|
translation_key="sock_discon_alrt",
|
||||||
device_class=BinarySensorDeviceClass.SOUND,
|
device_class=BinarySensorDeviceClass.SOUND,
|
||||||
element="sock_disconnected",
|
available_during_charging=True,
|
||||||
),
|
),
|
||||||
OwletBinarySensorEntityDescription(
|
OwletBinarySensorEntityDescription(
|
||||||
key="sock_off",
|
key="sock_off",
|
||||||
translation_key="sock_off",
|
translation_key="sock_off",
|
||||||
device_class=BinarySensorDeviceClass.POWER,
|
device_class=BinarySensorDeviceClass.POWER,
|
||||||
element="sock_off",
|
available_during_charging=True,
|
||||||
),
|
),
|
||||||
OwletBinarySensorEntityDescription(
|
OwletBinarySensorEntityDescription(
|
||||||
key="awake",
|
key="base_station_on",
|
||||||
translation_key="awake",
|
translation_key="base_on",
|
||||||
element="sleep_state",
|
device_class=BinarySensorDeviceClass.POWER,
|
||||||
icon="mdi:sleep",
|
available_during_charging=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -104,11 +110,16 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
coordinators: OwletCoordinator = hass.data[DOMAIN][config_entry.entry_id].values()
|
coordinators: OwletCoordinator = hass.data[DOMAIN][config_entry.entry_id].values()
|
||||||
|
|
||||||
async_add_entities(
|
sensors = []
|
||||||
OwletBinarySensor(coordinator, sensor)
|
for coordinator in coordinators:
|
||||||
for coordinator in coordinators
|
for sensor in SENSORS:
|
||||||
for sensor in SENSORS
|
if sensor.key in coordinator.sock.properties:
|
||||||
)
|
sensors.append(OwletBinarySensor(coordinator, sensor))
|
||||||
|
|
||||||
|
if OwletAwakeSensor.entity_description.key in coordinator.sock.properties:
|
||||||
|
sensors.append(OwletAwakeSensor(coordinator))
|
||||||
|
|
||||||
|
async_add_entities(sensors)
|
||||||
|
|
||||||
|
|
||||||
class OwletBinarySensor(OwletBaseEntity, BinarySensorEntity):
|
class OwletBinarySensor(OwletBaseEntity, BinarySensorEntity):
|
||||||
@@ -117,19 +128,25 @@ class OwletBinarySensor(OwletBaseEntity, BinarySensorEntity):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: OwletCoordinator,
|
coordinator: OwletCoordinator,
|
||||||
sensor_description: OwletBinarySensorEntityDescription,
|
description: OwletBinarySensorEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the binary sensor."""
|
"""Initialize the binary sensor."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self.entity_description = sensor_description
|
self.entity_description = description
|
||||||
self._attr_unique_id = f"{self.sock.serial}-{self.entity_description.translation_key}"
|
self._attr_unique_id = f"{self.sock.serial}-{description.key}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return if entity is available."""
|
||||||
|
return super().available and (
|
||||||
|
not self.sock.properties["charging"]
|
||||||
|
or self.entity_description.available_during_charging
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return true if the binary sensor is on."""
|
"""Return true if the binary sensor is on."""
|
||||||
state = self.sock.properties[self.entity_description.element]
|
if self.entity_description.key == "sleep_state":
|
||||||
|
|
||||||
if self.entity_description.element == "sleep_state":
|
|
||||||
if self.sock.properties["charging"]:
|
if self.sock.properties["charging"]:
|
||||||
return None
|
return None
|
||||||
if state in [8, 15]:
|
if state in [8, 15]:
|
||||||
@@ -137,4 +154,27 @@ class OwletBinarySensor(OwletBaseEntity, BinarySensorEntity):
|
|||||||
else:
|
else:
|
||||||
state = True
|
state = True
|
||||||
|
|
||||||
return state
|
return self.sock.properties[self.entity_description.key]
|
||||||
|
|
||||||
|
|
||||||
|
class OwletAwakeSensor(OwletBinarySensor):
|
||||||
|
"""Representation of an Owlet sleep sensor."""
|
||||||
|
|
||||||
|
entity_description = OwletBinarySensorEntityDescription(
|
||||||
|
key="sleep_state",
|
||||||
|
translation_key="awake",
|
||||||
|
icon="mdi:sleep",
|
||||||
|
available_during_charging=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: OwletCoordinator,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the sensor."""
|
||||||
|
super().__init__(coordinator, self.entity_description)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool:
|
||||||
|
"""Return true if the binary sensor is on."""
|
||||||
|
return False if self.sock.properties[self.entity_description.key] in [8, 15] else True
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ from pyowletapi.exceptions import (
|
|||||||
OwletEmailError,
|
OwletEmailError,
|
||||||
OwletPasswordError,
|
OwletPasswordError,
|
||||||
)
|
)
|
||||||
from pyowletapi.sock import Sock
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries, exceptions
|
from homeassistant import config_entries, exceptions
|
||||||
@@ -34,9 +33,9 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required("region"): vol.In(["europe", "world"]),
|
vol.Required(CONF_REGION): vol.In(["europe", "world"]),
|
||||||
vol.Required("username"): str,
|
vol.Required(CONF_USERNAME): str,
|
||||||
vol.Required("password"): str,
|
vol.Required(CONF_PASSWORD): str,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,15 +44,10 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
"""Handle a config flow for Owlet Smart Sock."""
|
"""Handle a config flow for Owlet Smart Sock."""
|
||||||
|
|
||||||
VERSION = 1
|
VERSION = 1
|
||||||
|
reauth_entry: ConfigEntry | None = None
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""Initialise config flow."""
|
"""Initialise config flow."""
|
||||||
self._entry: ConfigEntry
|
|
||||||
self._region: str
|
|
||||||
self._username: str
|
|
||||||
self._password: str
|
|
||||||
self._devices: dict[str, Sock]
|
|
||||||
self.reauth_entry: ConfigEntry | None = None
|
|
||||||
|
|
||||||
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
|
||||||
@@ -61,18 +55,14 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
"""Handle the initial step."""
|
"""Handle the initial step."""
|
||||||
errors: dict[str, str] = {}
|
errors: dict[str, str] = {}
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
self._region = user_input[CONF_REGION]
|
|
||||||
self._username = user_input[CONF_USERNAME]
|
|
||||||
self._password = user_input[CONF_PASSWORD]
|
|
||||||
|
|
||||||
owlet_api = OwletAPI(
|
owlet_api = OwletAPI(
|
||||||
region=self._region,
|
region=user_input[CONF_REGION],
|
||||||
user=self._username,
|
user=user_input[CONF_USERNAME],
|
||||||
password=self._password,
|
password=user_input[CONF_PASSWORD],
|
||||||
session=async_get_clientsession(self.hass),
|
session=async_get_clientsession(self.hass),
|
||||||
)
|
)
|
||||||
|
|
||||||
await self.async_set_unique_id(self._username.lower())
|
await self.async_set_unique_id(user_input[CONF_USERNAME].lower())
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -82,9 +72,9 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
except OwletDevicesError:
|
except OwletDevicesError:
|
||||||
errors["base"] = "no_devices"
|
errors["base"] = "no_devices"
|
||||||
except OwletEmailError:
|
except OwletEmailError:
|
||||||
errors["base"] = "invalid_email"
|
errors[CONF_USERNAME] = "invalid_email"
|
||||||
except OwletPasswordError:
|
except OwletPasswordError:
|
||||||
errors["base"] = "invalid_password"
|
errors[CONF_PASSWORD] = "invalid_password"
|
||||||
except OwletCredentialsError:
|
except OwletCredentialsError:
|
||||||
errors["base"] = "invalid_credentials"
|
errors["base"] = "invalid_credentials"
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
@@ -92,13 +82,11 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
errors["base"] = "unknown"
|
errors["base"] = "unknown"
|
||||||
else:
|
else:
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=self._username,
|
title=user_input[CONF_USERNAME],
|
||||||
data={
|
data={
|
||||||
CONF_REGION: self._region,
|
CONF_REGION: user_input[CONF_REGION],
|
||||||
CONF_USERNAME: self._username,
|
CONF_USERNAME: user_input[CONF_USERNAME],
|
||||||
CONF_API_TOKEN: token[CONF_API_TOKEN],
|
**token,
|
||||||
CONF_OWLET_EXPIRY: token[CONF_OWLET_EXPIRY],
|
|
||||||
CONF_OWLET_REFRESH: token[CONF_OWLET_REFRESH],
|
|
||||||
},
|
},
|
||||||
options={CONF_SCAN_INTERVAL: POLLING_INTERVAL},
|
options={CONF_SCAN_INTERVAL: POLLING_INTERVAL},
|
||||||
)
|
)
|
||||||
@@ -142,12 +130,12 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
self.reauth_entry, data={**entry_data, **token}
|
self.reauth_entry, data={**entry_data, **token}
|
||||||
)
|
)
|
||||||
|
|
||||||
await self.hass.config_entries.async_reload(self.reauth_entry.entry_id)
|
await self.hass.config_entries.async_reload(self.reauth_entry.entry_id)
|
||||||
|
|
||||||
return self.async_abort(reason="reauth_successful")
|
return self.async_abort(reason="reauth_successful")
|
||||||
|
|
||||||
except OwletPasswordError:
|
except OwletPasswordError:
|
||||||
errors["base"] = "invalid_password"
|
errors[CONF_PASSWORD] = "invalid_password"
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
_LOGGER.exception("Error reauthenticating")
|
_LOGGER.exception("Error reauthenticating")
|
||||||
|
|
||||||
@@ -177,7 +165,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||||||
vol.Required(
|
vol.Required(
|
||||||
CONF_SCAN_INTERVAL,
|
CONF_SCAN_INTERVAL,
|
||||||
default=self.config_entry.options.get(CONF_SCAN_INTERVAL),
|
default=self.config_entry.options.get(CONF_SCAN_INTERVAL),
|
||||||
): vol.All(vol.Coerce(int), vol.Range(min=10)),
|
): vol.All(vol.Coerce(int), vol.Range(min=5)),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ DOMAIN = "owlet"
|
|||||||
CONF_OWLET_EXPIRY = "expiry"
|
CONF_OWLET_EXPIRY = "expiry"
|
||||||
CONF_OWLET_REFRESH = "refresh"
|
CONF_OWLET_REFRESH = "refresh"
|
||||||
|
|
||||||
SUPPORTED_VERSIONS = [3]
|
SUPPORTED_VERSIONS = [2, 3]
|
||||||
POLLING_INTERVAL = 5
|
POLLING_INTERVAL = 5
|
||||||
MANUFACTURER = "Owlet Baby Care"
|
MANUFACTURER = "Owlet Baby Care"
|
||||||
SLEEP_STATES = {0: "unknown", 1: "awake", 8: "light_sleep", 15: "deep_sleep"}
|
SLEEP_STATES = {0: "unknown", 1: "awake", 8: "light_sleep", 15: "deep_sleep"}
|
||||||
|
|||||||
@@ -15,10 +15,9 @@ from homeassistant.config_entries import ConfigEntry
|
|||||||
from homeassistant.const import CONF_EMAIL
|
from homeassistant.const import CONF_EMAIL
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from .const import DOMAIN, MANUFACTURER
|
from .const import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -26,7 +25,9 @@ _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, interval) -> None:
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, sock: Sock, interval, entry: ConfigEntry
|
||||||
|
) -> None:
|
||||||
"""Initialise a custom coordinator."""
|
"""Initialise a custom coordinator."""
|
||||||
super().__init__(
|
super().__init__(
|
||||||
hass,
|
hass,
|
||||||
@@ -34,17 +35,8 @@ class OwletCoordinator(DataUpdateCoordinator):
|
|||||||
name=DOMAIN,
|
name=DOMAIN,
|
||||||
update_interval=timedelta(seconds=interval),
|
update_interval=timedelta(seconds=interval),
|
||||||
)
|
)
|
||||||
assert self.config_entry is not None
|
|
||||||
self.config_entry: ConfigEntry
|
|
||||||
self.sock = sock
|
self.sock = sock
|
||||||
self.device_info = DeviceInfo(
|
self.config_entry: ConfigEntry = entry
|
||||||
identifiers={(DOMAIN, sock.serial)},
|
|
||||||
name="Owlet Baby Care Sock",
|
|
||||||
manufacturer=MANUFACTURER,
|
|
||||||
model=sock.model,
|
|
||||||
sw_version=sock.sw_version,
|
|
||||||
hw_version=sock.version,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _async_update_data(self) -> None:
|
async def _async_update_data(self) -> None:
|
||||||
"""Fetch the data from the device."""
|
"""Fetch the data from the device."""
|
||||||
|
|||||||
@@ -2,13 +2,17 @@
|
|||||||
|
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
|
|
||||||
from .coordinator import OwletCoordinator
|
from .coordinator import OwletCoordinator
|
||||||
|
from .const import DOMAIN, MANUFACTURER
|
||||||
|
|
||||||
|
|
||||||
class OwletBaseEntity(CoordinatorEntity[OwletCoordinator], Entity):
|
class OwletBaseEntity(CoordinatorEntity[OwletCoordinator], Entity):
|
||||||
"""Base class for Owlet Sock entities."""
|
"""Base class for Owlet Sock entities."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: OwletCoordinator,
|
coordinator: OwletCoordinator,
|
||||||
@@ -16,5 +20,15 @@ class OwletBaseEntity(CoordinatorEntity[OwletCoordinator], Entity):
|
|||||||
"""Initialize the base entity."""
|
"""Initialize the base entity."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self.sock = coordinator.sock
|
self.sock = coordinator.sock
|
||||||
self._attr_device_info = coordinator.device_info
|
|
||||||
self._attr_has_entity_name = True
|
@property
|
||||||
|
def device_info(self) -> DeviceInfo:
|
||||||
|
"""Return the device info of the device."""
|
||||||
|
return DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, self.sock.serial)},
|
||||||
|
name="Owlet Baby Care Sock",
|
||||||
|
manufacturer=MANUFACTURER,
|
||||||
|
model=self.sock.model,
|
||||||
|
sw_version=self.sock.sw_version,
|
||||||
|
hw_version=f"{self.sock.version}r{self.sock.revision}",
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
{
|
{
|
||||||
"domain": "owlet",
|
"domain": "owlet",
|
||||||
"name": "Owlet Smart Sock",
|
"name": "Owlet Smart Sock",
|
||||||
"codeowners": ["@ryanbdclark"],
|
"codeowners": [
|
||||||
|
"@ryanbdclark"
|
||||||
|
],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/owlet",
|
"documentation": "https://www.home-assistant.io/integrations/owlet",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"issue_tracker": "https://github.com/ryanbdclark/owlet/issues",
|
"issue_tracker": "https://github.com/ryanbdclark/owlet/issues",
|
||||||
"requirements": ["pyowletapi==2023.7.2"],
|
"requirements": [
|
||||||
"version":"2023.7.2"
|
"pyowletapi==2024.6.1"
|
||||||
|
],
|
||||||
|
"version": "2024.6.1"
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
"""Support for Owlet sensors."""
|
"""Support for Owlet sensors."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
@@ -25,76 +26,76 @@ from .coordinator import OwletCoordinator
|
|||||||
from .entity import OwletBaseEntity
|
from .entity import OwletBaseEntity
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass(kw_only=True)
|
||||||
class OwletSensorEntityDescriptionMixin:
|
class OwletSensorEntityDescription(SensorEntityDescription):
|
||||||
"""Owlet sensor description mix in."""
|
|
||||||
|
|
||||||
element: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class OwletSensorEntityDescription(
|
|
||||||
SensorEntityDescription, OwletSensorEntityDescriptionMixin
|
|
||||||
):
|
|
||||||
"""Represent the owlet sensor entity description."""
|
"""Represent the owlet sensor entity description."""
|
||||||
|
|
||||||
|
available_during_charging: bool
|
||||||
|
|
||||||
|
|
||||||
SENSORS: tuple[OwletSensorEntityDescription, ...] = (
|
SENSORS: tuple[OwletSensorEntityDescription, ...] = (
|
||||||
OwletSensorEntityDescription(
|
OwletSensorEntityDescription(
|
||||||
key="batterypercentage",
|
key="battery_percentage",
|
||||||
translation_key="batterypercent",
|
translation_key="batterypercent",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
device_class=SensorDeviceClass.BATTERY,
|
device_class=SensorDeviceClass.BATTERY,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
element="battery_percentage",
|
available_during_charging=True,
|
||||||
),
|
),
|
||||||
OwletSensorEntityDescription(
|
OwletSensorEntityDescription(
|
||||||
key="oxygensaturation",
|
key="oxygen_saturation",
|
||||||
translation_key="o2saturation",
|
translation_key="o2saturation",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
element="oxygen_saturation",
|
|
||||||
icon="mdi:leaf",
|
icon="mdi:leaf",
|
||||||
|
available_during_charging=False,
|
||||||
),
|
),
|
||||||
OwletSensorEntityDescription(
|
OwletSensorEntityDescription(
|
||||||
key="oxygensaturation10a",
|
key="heart_rate",
|
||||||
translation_key="o2saturation10a",
|
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
element="oxygen_10_av",
|
|
||||||
icon="mdi:leaf",
|
|
||||||
),
|
|
||||||
OwletSensorEntityDescription(
|
|
||||||
key="heartrate",
|
|
||||||
translation_key="heartrate",
|
translation_key="heartrate",
|
||||||
native_unit_of_measurement="bpm",
|
native_unit_of_measurement="bpm",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
element="heart_rate",
|
|
||||||
icon="mdi:heart-pulse",
|
icon="mdi:heart-pulse",
|
||||||
|
available_during_charging=False,
|
||||||
),
|
),
|
||||||
OwletSensorEntityDescription(
|
OwletSensorEntityDescription(
|
||||||
key="batteryminutes",
|
key="battery_minutes",
|
||||||
translation_key="batterymin",
|
translation_key="batterymin",
|
||||||
native_unit_of_measurement=UnitOfTime.MINUTES,
|
native_unit_of_measurement=UnitOfTime.MINUTES,
|
||||||
device_class=SensorDeviceClass.DURATION,
|
device_class=SensorDeviceClass.DURATION,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
available_during_charging=False,
|
||||||
element="battery_minutes",
|
|
||||||
),
|
),
|
||||||
OwletSensorEntityDescription(
|
OwletSensorEntityDescription(
|
||||||
key="signalstrength",
|
key="signal_strength",
|
||||||
translation_key="signalstrength",
|
translation_key="signalstrength",
|
||||||
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,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
element="signal_strength",
|
available_during_charging=True,
|
||||||
),
|
),
|
||||||
OwletSensorEntityDescription(
|
OwletSensorEntityDescription(
|
||||||
key="skintemp",
|
key="skin_temperature",
|
||||||
translation_key="skintemp",
|
translation_key="skintemp",
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
element="skin_temperature",
|
available_during_charging=False,
|
||||||
|
),
|
||||||
|
OwletSensorEntityDescription(
|
||||||
|
key="movement",
|
||||||
|
translation_key="movement",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
icon="mdi:cursor-move",
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
available_during_charging=False,
|
||||||
|
),
|
||||||
|
OwletSensorEntityDescription(
|
||||||
|
key="movement_bucket",
|
||||||
|
translation_key="movementbucket",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
icon="mdi:bucket-outline",
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
available_during_charging=False,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -110,15 +111,19 @@ async def async_setup_entry(
|
|||||||
hass.data[DOMAIN][config_entry.entry_id].values()
|
hass.data[DOMAIN][config_entry.entry_id].values()
|
||||||
)
|
)
|
||||||
|
|
||||||
async_add_entities(
|
sensors = []
|
||||||
OwletSensor(coordinator, sensor)
|
|
||||||
for coordinator in coordinators
|
|
||||||
for sensor in SENSORS
|
|
||||||
)
|
|
||||||
|
|
||||||
async_add_entities(
|
for coordinator in coordinators:
|
||||||
OwletSleepStateSensor(coordinator) for coordinator in coordinators
|
for sensor in SENSORS:
|
||||||
)
|
if sensor.key in coordinator.sock.properties:
|
||||||
|
sensors.append(OwletSensor(coordinator, sensor))
|
||||||
|
|
||||||
|
if OwletSleepSensor.entity_description.key in coordinator.sock.properties:
|
||||||
|
sensors.append(OwletSleepSensor(coordinator))
|
||||||
|
if OwletOxygenAverageSensor.entity_description.key in coordinator.sock.properties:
|
||||||
|
sensors.append(OwletOxygenAverageSensor(coordinator))
|
||||||
|
|
||||||
|
async_add_entities(sensors)
|
||||||
|
|
||||||
|
|
||||||
class OwletSensor(OwletBaseEntity, SensorEntity):
|
class OwletSensor(OwletBaseEntity, SensorEntity):
|
||||||
@@ -127,60 +132,75 @@ class OwletSensor(OwletBaseEntity, SensorEntity):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: OwletCoordinator,
|
coordinator: OwletCoordinator,
|
||||||
sensor_description: OwletSensorEntityDescription,
|
description: OwletSensorEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self.entity_description: OwletSensorEntityDescription = sensor_description
|
self.entity_description: OwletSensorEntityDescription = description
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id = f"{self.sock.serial}-{description.key}"
|
||||||
f"{self.sock.serial}-{self.entity_description.translation_key}"
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return if entity is available."""
|
||||||
|
return super().available and (
|
||||||
|
not self.sock.properties["charging"]
|
||||||
|
or self.entity_description.available_during_charging
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> StateType:
|
def native_value(self) -> StateType:
|
||||||
"""Return sensor value."""
|
"""Return sensor value."""
|
||||||
|
|
||||||
if (
|
return self.sock.properties[self.entity_description.key]
|
||||||
self.entity_description.element
|
|
||||||
in [
|
|
||||||
"heart_rate",
|
|
||||||
"battery_minutes",
|
|
||||||
"oxygen_saturation",
|
|
||||||
"skin_temperature",
|
|
||||||
"oxygen_10_av",
|
|
||||||
]
|
|
||||||
and self.sock.properties["charging"]
|
|
||||||
):
|
|
||||||
return None
|
|
||||||
|
|
||||||
properties = self.sock.properties
|
|
||||||
|
|
||||||
return properties[self.entity_description.element]
|
|
||||||
|
|
||||||
|
|
||||||
class OwletSleepStateSensor(OwletBaseEntity, SensorEntity):
|
class OwletSleepSensor(OwletSensor):
|
||||||
"""Representation of an Owlet sleep state sensor."""
|
"""Representation of an Owlet sleep sensor."""
|
||||||
|
|
||||||
|
_attr_options = list(SLEEP_STATES.values())
|
||||||
|
entity_description = OwletSensorEntityDescription(
|
||||||
|
key="sleep_state",
|
||||||
|
translation_key="sleepstate",
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
available_during_charging=False,
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: OwletCoordinator,
|
coordinator: OwletCoordinator,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator, self.entity_description)
|
||||||
self._attr_unique_id = f"{self.sock.serial}-sleepstate"
|
|
||||||
self._attr_icon = "mdi:sleep"
|
|
||||||
self._attr_device_class = SensorDeviceClass.ENUM
|
|
||||||
self._attr_translation_key = "sleepstate"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> str:
|
def native_value(self) -> StateType:
|
||||||
"""Return sensor value."""
|
"""Return sensor value."""
|
||||||
if self.sock.properties["charging"]:
|
|
||||||
return "unknown"
|
|
||||||
|
|
||||||
return SLEEP_STATES[self.sock.properties["sleep_state"]]
|
return SLEEP_STATES[self.sock.properties["sleep_state"]]
|
||||||
|
|
||||||
|
|
||||||
|
class OwletOxygenAverageSensor(OwletSensor):
|
||||||
|
"""Representation of an Owlet sleep sensor."""
|
||||||
|
|
||||||
|
entity_description = OwletSensorEntityDescription(
|
||||||
|
key="oxygen_10_av",
|
||||||
|
translation_key="o2saturation10a",
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
icon="mdi:leaf",
|
||||||
|
available_during_charging=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: OwletCoordinator,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the sensor."""
|
||||||
|
super().__init__(coordinator, self.entity_description)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def options(self) -> list[str]:
|
def available(self) -> bool:
|
||||||
"""Set options for sleep state."""
|
"""Return if entity is available."""
|
||||||
return list(SLEEP_STATES.values())
|
return super().available and (
|
||||||
|
not self.sock.properties["charging"]
|
||||||
|
or self.entity_description.available_during_charging
|
||||||
|
) and (self.sock.properties["oxygen_10_av"] >= 0 and self.sock.properties["oxygen_10_av"] <= 100)
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,16 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"title": "Enter login details",
|
|
||||||
"data": {
|
"data": {
|
||||||
"region": "Region",
|
"region": "Region",
|
||||||
"username": "Email",
|
"username": "Email",
|
||||||
"password": "Password"
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
"title": "Reauthentiaction required for Owlet",
|
"title": "Reauthentiaction required for Owlet",
|
||||||
"data": {
|
"data": {
|
||||||
"password": "Password"
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -39,68 +38,83 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
"binary_sensor":{
|
"binary_sensor": {
|
||||||
"charging": {
|
"charging": {
|
||||||
"name": "Charging"
|
"name": "Charging"
|
||||||
},
|
},
|
||||||
"high_hr_alrt":{
|
"high_hr_alrt": {
|
||||||
"name": "High Heart Rate Alert"
|
"name": "High heart rate alert"
|
||||||
},
|
},
|
||||||
"low_hr_alrt":{
|
"low_hr_alrt": {
|
||||||
"name":"Low Heart Rate Alert"
|
"name": "Low heart rate alert"
|
||||||
},
|
},
|
||||||
"high_ox_alrt":{
|
"high_ox_alrt": {
|
||||||
"name":"High Oxygen Alert"
|
"name": "High oxygen alert"
|
||||||
},
|
},
|
||||||
"low_ox_alrt":{
|
"low_ox_alrt": {
|
||||||
"name":"Low Oxygen Alert"
|
"name": "Low oxygen alert"
|
||||||
},
|
},
|
||||||
"low_batt_alrt":{
|
"crit_ox_alrt": {
|
||||||
"name": "Low Battery Alert"
|
"name": "Critical oxygen alert"
|
||||||
},
|
},
|
||||||
"lost_pwr_alrt":{
|
"low_batt_alrt": {
|
||||||
"name": "Lost Power Alert"
|
"name": "Low battery alert"
|
||||||
},
|
},
|
||||||
"sock_discon_alrt":{
|
"crit_batt_alrt": {
|
||||||
"name": "Sock Diconnected Alert"
|
"name": "Critical battery alert"
|
||||||
},
|
},
|
||||||
"sock_off":{
|
"lost_pwr_alrt": {
|
||||||
"name":"Sock Off"
|
"name": "Lost power alert"
|
||||||
},
|
},
|
||||||
"awake":{
|
"sock_discon_alrt": {
|
||||||
"name":"Awake"
|
"name": "Sock disconnected alert"
|
||||||
|
},
|
||||||
|
"sock_off": {
|
||||||
|
"name": "Sock off"
|
||||||
|
},
|
||||||
|
"awake": {
|
||||||
|
"name": "Awake"
|
||||||
|
},
|
||||||
|
"base_on": {
|
||||||
|
"name": "Base station on"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"batterypercent": {
|
"batterypercent": {
|
||||||
"name": "Battery Percentage"
|
"name": "Battery percentage"
|
||||||
},
|
|
||||||
"o2saturation": {
|
|
||||||
"name": "O2 Saturation"
|
|
||||||
},
|
|
||||||
"o2saturation10a": {
|
|
||||||
"name": "O2 Saturation 10 Minute Average"
|
|
||||||
},
|
|
||||||
"heartrate": {
|
|
||||||
"name": "Heart Rate"
|
|
||||||
},
|
|
||||||
"batterymin": {
|
|
||||||
"name": "Battery Remaining"
|
|
||||||
},
|
},
|
||||||
"signalstrength": {
|
"signalstrength": {
|
||||||
"name": "Signal Strength"
|
"name": "Signal strength"
|
||||||
|
},
|
||||||
|
"o2saturation": {
|
||||||
|
"name": "O2 saturation"
|
||||||
|
},
|
||||||
|
"o2saturation10a": {
|
||||||
|
"name": "O2 saturation 10 minute average"
|
||||||
|
},
|
||||||
|
"heartrate": {
|
||||||
|
"name": "Heart rate"
|
||||||
|
},
|
||||||
|
"batterymin": {
|
||||||
|
"name": "Battery remaining"
|
||||||
},
|
},
|
||||||
"skintemp": {
|
"skintemp": {
|
||||||
"name": "Skin Temperature"
|
"name": "Skin temperature"
|
||||||
},
|
},
|
||||||
"sleepstate": {
|
"sleepstate": {
|
||||||
"name": "Sleep State",
|
"name": "Sleep state",
|
||||||
"state": {
|
"state": {
|
||||||
"unknown": "Unknown",
|
"unknown": "Unknown",
|
||||||
"awake": "Awake",
|
"awake": "Awake",
|
||||||
"light_sleep": "Light Sleep",
|
"light_sleep": "Light sleep",
|
||||||
"deep_sleep": "Deep Sleep"
|
"deep_sleep": "Deep sleep"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"movement": {
|
||||||
|
"name": "Movement"
|
||||||
|
},
|
||||||
|
"movementbucket": {
|
||||||
|
"name": "Movement bucket"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,107 +1,121 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"title": "Enter login details",
|
"data": {
|
||||||
"data": {
|
"region": "Region",
|
||||||
"region": "Region",
|
"username": "Email",
|
||||||
"username": "Email",
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
"password": "Password"
|
}
|
||||||
|
},
|
||||||
|
"reauth_confirm": {
|
||||||
|
"title": "Reauthentiaction required for Owlet",
|
||||||
|
"data": {
|
||||||
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"invalid_email": "Entered email address is incorrect",
|
||||||
|
"invalid_password": "Entered password is incorrect",
|
||||||
|
"invalid_credentials": "Entered credentials are incorrect",
|
||||||
|
"unknown": "Unknown error occured"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Device already configured",
|
||||||
|
"reauth_successful": "Reauthentication successful"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"reauth_confirm": {
|
|
||||||
"title": "Reauthentiaction required for Owlet",
|
|
||||||
"data": {
|
|
||||||
"password": "Password"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"error": {
|
"options": {
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
"step": {
|
||||||
"invalid_email": "Entered email address is incorrect",
|
"init": {
|
||||||
"invalid_password": "Entered password is incorrect",
|
"title": "Configure options for Owlet",
|
||||||
"invalid_credentials": "Entered credentials are incorrect",
|
"data": {
|
||||||
"unknown": "Unknown error occured"
|
"pollinterval": "Polling interval in seconds, min 10"
|
||||||
},
|
}
|
||||||
"abort": {
|
}
|
||||||
"already_configured": "Device already configured",
|
|
||||||
"reauth_successful": "Reauthentication successful"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"options": {
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"title": "Configure options for Owlet",
|
|
||||||
"data": {
|
|
||||||
"pollinterval": "Polling interval in seconds, min 10"
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"entity": {
|
|
||||||
"binary_sensor":{
|
|
||||||
"charging": {
|
|
||||||
"name": "Charging"
|
|
||||||
},
|
|
||||||
"high_hr_alrt":{
|
|
||||||
"name": "High Heart Rate Alert"
|
|
||||||
},
|
|
||||||
"low_hr_alrt":{
|
|
||||||
"name":"Low Heart Rate Alert"
|
|
||||||
},
|
|
||||||
"high_ox_alrt":{
|
|
||||||
"name":"High Oxygen Alert"
|
|
||||||
},
|
|
||||||
"low_ox_alrt":{
|
|
||||||
"name":"Low Oxygen Alert"
|
|
||||||
},
|
|
||||||
"low_batt_alrt":{
|
|
||||||
"name": "Low Battery Alert"
|
|
||||||
},
|
|
||||||
"lost_pwr_alrt":{
|
|
||||||
"name": "Lost Power Alert"
|
|
||||||
},
|
|
||||||
"sock_discon_alrt":{
|
|
||||||
"name": "Sock Diconnected Alert"
|
|
||||||
},
|
|
||||||
"sock_off":{
|
|
||||||
"name":"Sock Off"
|
|
||||||
},
|
|
||||||
"awake":{
|
|
||||||
"name":"Awake"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"sensor": {
|
"entity": {
|
||||||
"batterypercent": {
|
"binary_sensor": {
|
||||||
"name": "Battery Percentage"
|
"charging": {
|
||||||
},
|
"name": "Charging"
|
||||||
"o2saturation": {
|
},
|
||||||
"name": "O2 Saturation"
|
"high_hr_alrt": {
|
||||||
},
|
"name": "High heart rate alert"
|
||||||
"o2saturation10a": {
|
},
|
||||||
"name": "O2 Saturation 10 Minute Average"
|
"low_hr_alrt": {
|
||||||
},
|
"name": "Low heart rate alert"
|
||||||
"heartrate": {
|
},
|
||||||
"name": "Heart Rate"
|
"high_ox_alrt": {
|
||||||
},
|
"name": "High oxygen alert"
|
||||||
"batterymin": {
|
},
|
||||||
"name": "Battery Remaining"
|
"low_ox_alrt": {
|
||||||
},
|
"name": "Low oxygen alert"
|
||||||
"signalstrength": {
|
},
|
||||||
"name": "Signal Strength"
|
"crit_ox_alrt": {
|
||||||
},
|
"name": "Critical oxygen alert"
|
||||||
"skintemp": {
|
},
|
||||||
"name": "Skin Temperature"
|
"low_batt_alrt": {
|
||||||
},
|
"name": "Low battery alert"
|
||||||
"sleepstate": {
|
},
|
||||||
"name": "Sleep State",
|
"crit_batt_alrt": {
|
||||||
"state": {
|
"name": "Critical battery alert"
|
||||||
"unknown": "Unknown",
|
},
|
||||||
"awake": "Awake",
|
"lost_pwr_alrt": {
|
||||||
"light_sleep": "Light Sleep",
|
"name": "Lost power alert"
|
||||||
"deep_sleep": "Deep Sleep"
|
},
|
||||||
|
"sock_discon_alrt": {
|
||||||
|
"name": "Sock disconnected alert"
|
||||||
|
},
|
||||||
|
"sock_off": {
|
||||||
|
"name": "Sock off"
|
||||||
|
},
|
||||||
|
"awake": {
|
||||||
|
"name": "Awake"
|
||||||
|
},
|
||||||
|
"base_on": {
|
||||||
|
"name": "Base station on"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sensor": {
|
||||||
|
"batterypercent": {
|
||||||
|
"name": "Battery percentage"
|
||||||
|
},
|
||||||
|
"signalstrength": {
|
||||||
|
"name": "Signal strength"
|
||||||
|
},
|
||||||
|
"o2saturation": {
|
||||||
|
"name": "O2 saturation"
|
||||||
|
},
|
||||||
|
"o2saturation10a": {
|
||||||
|
"name": "O2 saturation 10 minute average"
|
||||||
|
},
|
||||||
|
"heartrate": {
|
||||||
|
"name": "Heart rate"
|
||||||
|
},
|
||||||
|
"batterymin": {
|
||||||
|
"name": "Battery remaining"
|
||||||
|
},
|
||||||
|
"skintemp": {
|
||||||
|
"name": "Skin temperature"
|
||||||
|
},
|
||||||
|
"sleepstate": {
|
||||||
|
"name": "Sleep state",
|
||||||
|
"state": {
|
||||||
|
"unknown": "Unknown",
|
||||||
|
"awake": "Awake",
|
||||||
|
"light_sleep": "Light sleep",
|
||||||
|
"deep_sleep": "Deep sleep"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"movement": {
|
||||||
|
"name": "Movement"
|
||||||
|
},
|
||||||
|
"movementbucket": {
|
||||||
|
"name": "Movement bucket"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -2,17 +2,16 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"title": "Enter login details",
|
|
||||||
"data": {
|
"data": {
|
||||||
"region": "Region",
|
"region": "Region",
|
||||||
"username": "Email",
|
"username": "Email",
|
||||||
"password": "Password"
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
"title": "Reauthentiaction required for Owlet",
|
"title": "Reauthentiaction required for Owlet",
|
||||||
"data": {
|
"data": {
|
||||||
"password": "Password"
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -39,68 +38,83 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
"binary_sensor":{
|
"binary_sensor": {
|
||||||
"charging": {
|
"charging": {
|
||||||
"name": "Charging"
|
"name": "Charging"
|
||||||
},
|
},
|
||||||
"high_hr_alrt":{
|
"high_hr_alrt": {
|
||||||
"name": "High Heart Rate Alert"
|
"name": "High heart rate alert"
|
||||||
},
|
},
|
||||||
"low_hr_alrt":{
|
"low_hr_alrt": {
|
||||||
"name":"Low Heart Rate Alert"
|
"name": "Low heart rate alert"
|
||||||
},
|
},
|
||||||
"high_ox_alrt":{
|
"high_ox_alrt": {
|
||||||
"name":"High Oxygen Alert"
|
"name": "High oxygen alert"
|
||||||
},
|
},
|
||||||
"low_ox_alrt":{
|
"low_ox_alrt": {
|
||||||
"name":"Low Oxygen Alert"
|
"name": "Low oxygen alert"
|
||||||
},
|
},
|
||||||
"low_batt_alrt":{
|
"crit_ox_alrt": {
|
||||||
"name": "Low Battery Alert"
|
"name": "Critical oxygen alert"
|
||||||
},
|
},
|
||||||
"lost_pwr_alrt":{
|
"low_batt_alrt": {
|
||||||
"name": "Lost Power Alert"
|
"name": "Low battery alert"
|
||||||
},
|
},
|
||||||
"sock_discon_alrt":{
|
"crit_batt_alrt": {
|
||||||
"name": "Sock Diconnected Alert"
|
"name": "Critical battery alert"
|
||||||
},
|
},
|
||||||
"sock_off":{
|
"lost_pwr_alrt": {
|
||||||
"name":"Sock Off"
|
"name": "Lost power alert"
|
||||||
},
|
},
|
||||||
"awake":{
|
"sock_discon_alrt": {
|
||||||
"name":"Awake"
|
"name": "Sock disconnected alert"
|
||||||
|
},
|
||||||
|
"sock_off": {
|
||||||
|
"name": "Sock off"
|
||||||
|
},
|
||||||
|
"awake": {
|
||||||
|
"name": "Awake"
|
||||||
|
},
|
||||||
|
"base_on": {
|
||||||
|
"name": "Base station on"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"batterypercent": {
|
"batterypercent": {
|
||||||
"name": "Battery Percentage"
|
"name": "Battery percentage"
|
||||||
},
|
|
||||||
"o2saturation": {
|
|
||||||
"name": "O2 Saturation"
|
|
||||||
},
|
|
||||||
"o2saturation10a": {
|
|
||||||
"name": "O2 Saturation 10 Minute Average"
|
|
||||||
},
|
|
||||||
"heartrate": {
|
|
||||||
"name": "Heart Rate"
|
|
||||||
},
|
|
||||||
"batterymin": {
|
|
||||||
"name": "Battery Remaining"
|
|
||||||
},
|
},
|
||||||
"signalstrength": {
|
"signalstrength": {
|
||||||
"name": "Signal Strength"
|
"name": "Signal strength"
|
||||||
|
},
|
||||||
|
"o2saturation": {
|
||||||
|
"name": "O2 saturation"
|
||||||
|
},
|
||||||
|
"o2saturation10a": {
|
||||||
|
"name": "O2 saturation 10 minute average"
|
||||||
|
},
|
||||||
|
"heartrate": {
|
||||||
|
"name": "Heart rate"
|
||||||
|
},
|
||||||
|
"batterymin": {
|
||||||
|
"name": "Battery remaining"
|
||||||
},
|
},
|
||||||
"skintemp": {
|
"skintemp": {
|
||||||
"name": "Skin Temperature"
|
"name": "Skin temperature"
|
||||||
},
|
},
|
||||||
"sleepstate": {
|
"sleepstate": {
|
||||||
"name": "Sleep State",
|
"name": "Sleep state",
|
||||||
"state": {
|
"state": {
|
||||||
"unknown": "Unknown",
|
"unknown": "Unknown",
|
||||||
"awake": "Awake",
|
"awake": "Awake",
|
||||||
"light_sleep": "Light Sleep",
|
"light_sleep": "Light sleep",
|
||||||
"deep_sleep": "Deep Sleep"
|
"deep_sleep": "Deep sleep"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"movement": {
|
||||||
|
"name": "Movement"
|
||||||
|
},
|
||||||
|
"movementbucket": {
|
||||||
|
"name": "Movement bucket"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
info.md
4
info.md
@@ -8,9 +8,7 @@
|
|||||||
[![hacs][hacsbadge]][hacs]
|
[![hacs][hacsbadge]][hacs]
|
||||||
[![Project Maintenance][maintenance-shield]][user_profile]
|
[![Project Maintenance][maintenance-shield]][user_profile]
|
||||||
|
|
||||||
A custom component for the Owlet smart sock, currently this only supports the owlet smart sock 3.
|
A custom component for the Owlet smart sock
|
||||||
|
|
||||||
If you have a smart sock 2 and would like to contribute then please do so.
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|||||||
1211
tests/fixtures/update_properties_v2.json
vendored
Normal file
1211
tests/fixtures/update_properties_v2.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
188
tests/test_binary_sensor.py
Normal file
188
tests/test_binary_sensor.py
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
"""Test Owlet Sensor."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from . import async_init_integration
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensors_asleep(hass: HomeAssistant) -> None:
|
||||||
|
"""Test sensor values."""
|
||||||
|
await async_init_integration(
|
||||||
|
hass, properties_fixture="update_properties_asleep.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(hass.states.async_all("binary_sensor")) == 10
|
||||||
|
|
||||||
|
assert hass.states.get("binary_sensor.owlet_baby_care_sock_charging").state == "off"
|
||||||
|
assert (
|
||||||
|
hass.states.get(
|
||||||
|
"binary_sensor.owlet_baby_care_sock_high_heart_rate_alert"
|
||||||
|
).state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get("binary_sensor.owlet_baby_care_sock_low_heart_rate_alert").state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get("binary_sensor.owlet_baby_care_sock_high_oxygen_alert").state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get("binary_sensor.owlet_baby_care_sock_low_oxygen_alert").state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get("binary_sensor.owlet_baby_care_sock_low_battery_alert").state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get("binary_sensor.owlet_baby_care_sock_lost_power_alert").state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get(
|
||||||
|
"binary_sensor.owlet_baby_care_sock_sock_disconnected_alert"
|
||||||
|
).state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert hass.states.get("binary_sensor.owlet_baby_care_sock_sock_off").state == "off"
|
||||||
|
assert hass.states.get("binary_sensor.owlet_baby_care_sock_awake").state == "off"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensors_awake(hass: HomeAssistant) -> None:
|
||||||
|
"""Test sensor values."""
|
||||||
|
await async_init_integration(
|
||||||
|
hass, properties_fixture="update_properties_awake.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(hass.states.async_all("binary_sensor")) == 10
|
||||||
|
|
||||||
|
assert hass.states.get("binary_sensor.owlet_baby_care_sock_charging").state == "off"
|
||||||
|
assert (
|
||||||
|
hass.states.get(
|
||||||
|
"binary_sensor.owlet_baby_care_sock_high_heart_rate_alert"
|
||||||
|
).state
|
||||||
|
== "on"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get("binary_sensor.owlet_baby_care_sock_low_heart_rate_alert").state
|
||||||
|
== "on"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get("binary_sensor.owlet_baby_care_sock_high_oxygen_alert").state
|
||||||
|
== "on"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get("binary_sensor.owlet_baby_care_sock_low_oxygen_alert").state
|
||||||
|
== "on"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get("binary_sensor.owlet_baby_care_sock_low_battery_alert").state
|
||||||
|
== "on"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get("binary_sensor.owlet_baby_care_sock_lost_power_alert").state
|
||||||
|
== "on"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get(
|
||||||
|
"binary_sensor.owlet_baby_care_sock_sock_disconnected_alert"
|
||||||
|
).state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert hass.states.get("binary_sensor.owlet_baby_care_sock_sock_off").state == "off"
|
||||||
|
assert hass.states.get("binary_sensor.owlet_baby_care_sock_awake").state == "on"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensors_charging(hass: HomeAssistant) -> None:
|
||||||
|
"""Test sensor values."""
|
||||||
|
await async_init_integration(
|
||||||
|
hass, properties_fixture="update_properties_charging.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(hass.states.async_all("binary_sensor")) == 10
|
||||||
|
|
||||||
|
assert hass.states.get("binary_sensor.owlet_baby_care_sock_charging").state == "on"
|
||||||
|
assert (
|
||||||
|
hass.states.get(
|
||||||
|
"binary_sensor.owlet_baby_care_sock_high_heart_rate_alert"
|
||||||
|
).state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get("binary_sensor.owlet_baby_care_sock_low_heart_rate_alert").state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get("binary_sensor.owlet_baby_care_sock_high_oxygen_alert").state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get("binary_sensor.owlet_baby_care_sock_low_oxygen_alert").state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get("binary_sensor.owlet_baby_care_sock_low_battery_alert").state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get("binary_sensor.owlet_baby_care_sock_lost_power_alert").state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get(
|
||||||
|
"binary_sensor.owlet_baby_care_sock_sock_disconnected_alert"
|
||||||
|
).state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert hass.states.get("binary_sensor.owlet_baby_care_sock_sock_off").state == "off"
|
||||||
|
assert (
|
||||||
|
hass.states.get("binary_sensor.owlet_baby_care_sock_awake").state == "unknown"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensors_v2(hass: HomeAssistant) -> None:
|
||||||
|
"""Test sensor values."""
|
||||||
|
await async_init_integration(hass, properties_fixture="update_properties_v2.json")
|
||||||
|
assert len(hass.states.async_all("binary_sensor")) == 9
|
||||||
|
|
||||||
|
assert hass.states.get("binary_sensor.owlet_baby_care_sock_charging").state == "off"
|
||||||
|
assert (
|
||||||
|
hass.states.get(
|
||||||
|
"binary_sensor.owlet_baby_care_sock_high_heart_rate_alert"
|
||||||
|
).state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get("binary_sensor.owlet_baby_care_sock_low_heart_rate_alert").state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get("binary_sensor.owlet_baby_care_sock_low_oxygen_alert").state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get("binary_sensor.owlet_baby_care_sock_low_battery_alert").state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get(
|
||||||
|
"binary_sensor.owlet_baby_care_sock_critical_battery_alert"
|
||||||
|
).state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get(
|
||||||
|
"binary_sensor.owlet_baby_care_sock_critical_oxygen_alert"
|
||||||
|
).state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
hass.states.get(
|
||||||
|
"binary_sensor.owlet_baby_care_sock_sock_disconnected_alert"
|
||||||
|
).state
|
||||||
|
== "off"
|
||||||
|
)
|
||||||
|
assert hass.states.get("binary_sensor.owlet_baby_care_sock_sock_off").state == "off"
|
||||||
@@ -70,7 +70,7 @@ async def test_flow_wrong_password(hass: HomeAssistant) -> None:
|
|||||||
user_input=CONF_INPUT,
|
user_input=CONF_INPUT,
|
||||||
)
|
)
|
||||||
assert result["type"] == FlowResultType.FORM
|
assert result["type"] == FlowResultType.FORM
|
||||||
assert result["errors"] == {"base": "invalid_password"}
|
assert result["errors"] == {"password": "invalid_password"}
|
||||||
|
|
||||||
|
|
||||||
async def test_flow_wrong_email(hass: HomeAssistant) -> None:
|
async def test_flow_wrong_email(hass: HomeAssistant) -> None:
|
||||||
@@ -88,7 +88,7 @@ async def test_flow_wrong_email(hass: HomeAssistant) -> None:
|
|||||||
user_input=CONF_INPUT,
|
user_input=CONF_INPUT,
|
||||||
)
|
)
|
||||||
assert result["type"] == FlowResultType.FORM
|
assert result["type"] == FlowResultType.FORM
|
||||||
assert result["errors"] == {"base": "invalid_email"}
|
assert result["errors"] == {"username": "invalid_email"}
|
||||||
|
|
||||||
|
|
||||||
async def test_flow_credentials_error(hass: HomeAssistant) -> None:
|
async def test_flow_credentials_error(hass: HomeAssistant) -> None:
|
||||||
@@ -193,7 +193,7 @@ async def test_reauth_invalid_password(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
assert result["type"] == FlowResultType.FORM
|
assert result["type"] == FlowResultType.FORM
|
||||||
assert result["step_id"] == "reauth_confirm"
|
assert result["step_id"] == "reauth_confirm"
|
||||||
assert result["errors"] == {"base": "invalid_password"}
|
assert result["errors"] == {"password": "invalid_password"}
|
||||||
|
|
||||||
|
|
||||||
async def test_reauth_unknown_error(hass: HomeAssistant) -> None:
|
async def test_reauth_unknown_error(hass: HomeAssistant) -> None:
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ async def test_async_setup_entry(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
entities = er.async_entries_for_device(entity_registry, device_entry.id)
|
entities = er.async_entries_for_device(entity_registry, device_entry.id)
|
||||||
|
|
||||||
assert len(entities) == 8
|
assert len(entities) == 18
|
||||||
|
|
||||||
await entry.async_unload(hass)
|
await entry.async_unload(hass)
|
||||||
|
|
||||||
|
|||||||
@@ -107,3 +107,20 @@ async def test_sensors_charging(hass: HomeAssistant) -> None:
|
|||||||
== "unknown"
|
== "unknown"
|
||||||
)
|
)
|
||||||
assert hass.states.get("sensor.owlet_baby_care_sock_sleep_state").state == "unknown"
|
assert hass.states.get("sensor.owlet_baby_care_sock_sleep_state").state == "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensors_v2(hass: HomeAssistant) -> None:
|
||||||
|
"""Test sensor values."""
|
||||||
|
await async_init_integration(hass, properties_fixture="update_properties_v2.json")
|
||||||
|
assert len(hass.states.async_all("sensor")) == 4
|
||||||
|
|
||||||
|
assert (
|
||||||
|
hass.states.get("sensor.owlet_baby_care_sock_battery_percentage").state == "29"
|
||||||
|
)
|
||||||
|
assert hass.states.get("sensor.owlet_baby_care_sock_heart_rate").state == "145"
|
||||||
|
assert hass.states.get("sensor.owlet_baby_care_sock_o2_saturation").state == "99"
|
||||||
|
|
||||||
|
assert (
|
||||||
|
hass.states.get("sensor.owlet_baby_care_sock_signal_strength").state == "98.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user