Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0bf404f6a | ||
|
|
0a7f703100 | ||
|
|
cab737cae4 | ||
|
|
092321cbae | ||
|
|
835786b89b | ||
|
|
2cd46c18f8 | ||
|
|
575b213ddd | ||
|
|
3afa43c82c | ||
|
|
5c8411fab7 | ||
|
|
c45959b123 | ||
|
|
4e30d4652f | ||
|
|
02f8679ed1 | ||
|
|
534ad8a351 |
14
.github/workflows/cron.yml
vendored
14
.github/workflows/cron.yml
vendored
@@ -7,15 +7,19 @@ on:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
hassfest:
|
||||
name: "Hassfest validation"
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- uses: "actions/checkout@v3"
|
||||
|
||||
- name: HACS validation
|
||||
- uses: "home-assistant/actions/hassfest@master"
|
||||
hacs:
|
||||
name: "HACS Action"
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- name: "HACS Action"
|
||||
uses: "hacs/action@main"
|
||||
with:
|
||||
category: "integration"
|
||||
|
||||
- name: Hassfest validation
|
||||
uses: "home-assistant/actions/hassfest@master"
|
||||
|
||||
|
||||
36
CHANGELOG.md
36
CHANGELOG.md
@@ -1,11 +1,33 @@
|
||||
# Changelog
|
||||
|
||||
<!--next-version-placeholder-->
|
||||
## 2023.05.7 (2023-05-30)
|
||||
## 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)
|
||||
### Fix
|
||||
* Bumping pyowletapi version to 2023.7.2 ([`c45959b`](https://github.com/ryanbdclark/owlet/commit/c45959b123a6e5f77747475f11d3d3ab67859756))
|
||||
|
||||
## 2023.7.1 (2023-07-03)
|
||||
### Fix
|
||||
* Bumping pyowletapi to 2023.7.1 ([`c693fef`](https://github.com/ryanbdclark/owlet/commit/c693fefbf3dba8f35802b87d064401dadbb211b5))
|
||||
|
||||
## 2023.5.7 (2023-05-30)
|
||||
### 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))
|
||||
|
||||
## 2023.05.6 (2023-05-30)
|
||||
## 2023.5.6 (2023-05-30)
|
||||
### 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
|
||||
* Sensor names now moved to strings file to allow for translations
|
||||
@@ -15,25 +37,25 @@
|
||||
### Feature
|
||||
* Tests added
|
||||
|
||||
## 2023.05.5 (2023-05-19)
|
||||
## 2023.5.5 (2023-05-19)
|
||||
#### 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))
|
||||
|
||||
## 2023.05.4 (2023-05-17)
|
||||
## 2023.5.4 (2023-05-17)
|
||||
#### Fix
|
||||
* Bumping to pyowletapi 2023.5.25
|
||||
|
||||
## 2023.05.3 (2023-05-17)
|
||||
## 2023.5.3 (2023-05-17)
|
||||
#### Fix
|
||||
* Bumping to pyowletapi 2023.5.24
|
||||
* Reauthing now no longer re adds users' password to config entry
|
||||
|
||||
## 2023.05.2 (2023-05-16)
|
||||
## 2023.5.2 (2023-05-16)
|
||||
#### 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))
|
||||
* 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
|
||||
* Changed versioning to date based
|
||||
### Fix
|
||||
|
||||
@@ -79,7 +79,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
scan_interval = entry.options.get(CONF_SCAN_INTERVAL)
|
||||
coordinators = {
|
||||
serial: OwletCoordinator(hass, sock, scan_interval)
|
||||
serial: OwletCoordinator(hass, sock, scan_interval, entry)
|
||||
for (serial, sock) in socks.items()
|
||||
}
|
||||
|
||||
|
||||
@@ -18,16 +18,7 @@ from .entity import OwletBaseEntity
|
||||
|
||||
|
||||
@dataclass
|
||||
class OwletBinarySensorEntityMixin:
|
||||
"""Owlet binary sensor element mixin"""
|
||||
|
||||
element: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class OwletBinarySensorEntityDescription(
|
||||
BinarySensorEntityDescription, OwletBinarySensorEntityMixin
|
||||
):
|
||||
class OwletBinarySensorEntityDescription(BinarySensorEntityDescription):
|
||||
"""Represent the owlet binary sensor entity description."""
|
||||
|
||||
|
||||
@@ -36,60 +27,50 @@ SENSORS: tuple[OwletBinarySensorEntityDescription, ...] = (
|
||||
key="charging",
|
||||
translation_key="charging",
|
||||
device_class=BinarySensorDeviceClass.BATTERY_CHARGING,
|
||||
element="charging",
|
||||
),
|
||||
OwletBinarySensorEntityDescription(
|
||||
key="highhr",
|
||||
key="high_heart_rate_alert",
|
||||
translation_key="high_hr_alrt",
|
||||
device_class=BinarySensorDeviceClass.SOUND,
|
||||
element="high_heart_rate_alert",
|
||||
),
|
||||
OwletBinarySensorEntityDescription(
|
||||
key="lowhr",
|
||||
key="low_heart_rate_alert",
|
||||
translation_key="low_hr_alrt",
|
||||
device_class=BinarySensorDeviceClass.SOUND,
|
||||
element="low_heart_rate_alert",
|
||||
),
|
||||
OwletBinarySensorEntityDescription(
|
||||
key="higho2",
|
||||
key="high_oxygen_alert",
|
||||
translation_key="high_ox_alrt",
|
||||
device_class=BinarySensorDeviceClass.SOUND,
|
||||
element="high_oxygen_alert",
|
||||
),
|
||||
OwletBinarySensorEntityDescription(
|
||||
key="lowo2",
|
||||
key="low_oxygen_alert",
|
||||
translation_key="low_ox_alrt",
|
||||
device_class=BinarySensorDeviceClass.SOUND,
|
||||
element="low_oxygen_alert",
|
||||
),
|
||||
OwletBinarySensorEntityDescription(
|
||||
key="lowbattery",
|
||||
key="low_battery_alert",
|
||||
translation_key="low_batt_alrt",
|
||||
device_class=BinarySensorDeviceClass.SOUND,
|
||||
element="low_battery_alert",
|
||||
),
|
||||
OwletBinarySensorEntityDescription(
|
||||
key="lostpower",
|
||||
key="lost_power_alert",
|
||||
translation_key="lost_pwr_alrt",
|
||||
device_class=BinarySensorDeviceClass.SOUND,
|
||||
element="lost_power_alert",
|
||||
),
|
||||
OwletBinarySensorEntityDescription(
|
||||
key="sockdisconnected",
|
||||
key="sock_disconnected",
|
||||
translation_key="sock_discon_alrt",
|
||||
device_class=BinarySensorDeviceClass.SOUND,
|
||||
element="sock_disconnected",
|
||||
),
|
||||
OwletBinarySensorEntityDescription(
|
||||
key="sock_off",
|
||||
translation_key="sock_off",
|
||||
device_class=BinarySensorDeviceClass.POWER,
|
||||
element="sock_off",
|
||||
),
|
||||
OwletBinarySensorEntityDescription(
|
||||
key="awake",
|
||||
key="sleep_state",
|
||||
translation_key="awake",
|
||||
element="sleep_state",
|
||||
icon="mdi:sleep",
|
||||
),
|
||||
)
|
||||
@@ -117,19 +98,19 @@ class OwletBinarySensor(OwletBaseEntity, BinarySensorEntity):
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: OwletCoordinator,
|
||||
sensor_description: OwletBinarySensorEntityDescription,
|
||||
description: OwletBinarySensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the binary sensor."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = sensor_description
|
||||
self._attr_unique_id = f"{self.sock.serial}-{self.entity_description.translation_key}"
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{self.sock.serial}-{description.key}"
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if the binary sensor is on."""
|
||||
state = self.sock.properties[self.entity_description.element]
|
||||
state = self.sock.properties[self.entity_description.key]
|
||||
|
||||
if self.entity_description.element == "sleep_state":
|
||||
if self.entity_description.key == "sleep_state":
|
||||
if self.sock.properties["charging"]:
|
||||
return None
|
||||
if state in [8, 15]:
|
||||
|
||||
@@ -34,9 +34,9 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required("region"): vol.In(["europe", "world"]),
|
||||
vol.Required("username"): str,
|
||||
vol.Required("password"): str,
|
||||
vol.Required(CONF_REGION): vol.In(["europe", "world"]),
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -45,15 +45,10 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Owlet Smart Sock."""
|
||||
|
||||
VERSION = 1
|
||||
reauth_entry: ConfigEntry | None = None
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""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(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
@@ -61,18 +56,14 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle the initial step."""
|
||||
errors: dict[str, str] = {}
|
||||
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(
|
||||
region=self._region,
|
||||
user=self._username,
|
||||
password=self._password,
|
||||
region=user_input[CONF_REGION],
|
||||
user=user_input[CONF_USERNAME],
|
||||
password=user_input[CONF_PASSWORD],
|
||||
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()
|
||||
|
||||
try:
|
||||
@@ -82,9 +73,9 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
except OwletDevicesError:
|
||||
errors["base"] = "no_devices"
|
||||
except OwletEmailError:
|
||||
errors["base"] = "invalid_email"
|
||||
errors[CONF_USERNAME] = "invalid_email"
|
||||
except OwletPasswordError:
|
||||
errors["base"] = "invalid_password"
|
||||
errors[CONF_PASSWORD] = "invalid_password"
|
||||
except OwletCredentialsError:
|
||||
errors["base"] = "invalid_credentials"
|
||||
except Exception: # pylint: disable=broad-except
|
||||
@@ -92,10 +83,10 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
return self.async_create_entry(
|
||||
title=self._username,
|
||||
title=user_input[CONF_USERNAME],
|
||||
data={
|
||||
CONF_REGION: self._region,
|
||||
CONF_USERNAME: self._username,
|
||||
CONF_REGION: user_input[CONF_REGION],
|
||||
CONF_USERNAME: user_input[CONF_PASSWORD],
|
||||
CONF_API_TOKEN: token[CONF_API_TOKEN],
|
||||
CONF_OWLET_EXPIRY: token[CONF_OWLET_EXPIRY],
|
||||
CONF_OWLET_REFRESH: token[CONF_OWLET_REFRESH],
|
||||
@@ -147,7 +138,7 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
|
||||
except OwletPasswordError:
|
||||
errors["base"] = "invalid_password"
|
||||
errors[CONF_PASSWORD] = "invalid_password"
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Error reauthenticating")
|
||||
|
||||
|
||||
@@ -15,10 +15,9 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_EMAIL
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN, MANUFACTURER
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -26,7 +25,9 @@ _LOGGER = logging.getLogger(__name__)
|
||||
class OwletCoordinator(DataUpdateCoordinator):
|
||||
"""Coordinator is responsible for querying the device at a specified route."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, sock: Sock, interval) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, sock: Sock, interval, entry: ConfigEntry
|
||||
) -> None:
|
||||
"""Initialise a custom coordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
@@ -34,17 +35,8 @@ class OwletCoordinator(DataUpdateCoordinator):
|
||||
name=DOMAIN,
|
||||
update_interval=timedelta(seconds=interval),
|
||||
)
|
||||
assert self.config_entry is not None
|
||||
self.config_entry: ConfigEntry
|
||||
self.sock = sock
|
||||
self.device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, sock.serial)},
|
||||
name="Owlet Baby Care Sock",
|
||||
manufacturer=MANUFACTURER,
|
||||
model=sock.model,
|
||||
sw_version=sock.sw_version,
|
||||
hw_version=sock.version,
|
||||
)
|
||||
self.config_entry = entry
|
||||
|
||||
async def _async_update_data(self) -> None:
|
||||
"""Fetch the data from the device."""
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
|
||||
from .coordinator import OwletCoordinator
|
||||
from .const import DOMAIN, MANUFACTURER
|
||||
|
||||
|
||||
class OwletBaseEntity(CoordinatorEntity[OwletCoordinator], Entity):
|
||||
"""Base class for Owlet Sock entities."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: OwletCoordinator,
|
||||
@@ -16,5 +20,15 @@ class OwletBaseEntity(CoordinatorEntity[OwletCoordinator], Entity):
|
||||
"""Initialize the base entity."""
|
||||
super().__init__(coordinator)
|
||||
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",
|
||||
"name": "Owlet Smart Sock",
|
||||
"codeowners": ["@ryanbdclark"],
|
||||
"codeowners": [
|
||||
"@ryanbdclark"
|
||||
],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/owlet",
|
||||
"iot_class": "cloud_polling",
|
||||
"issue_tracker": "https://github.com/ryanbdclark/owlet/issues",
|
||||
"requirements": ["pyowletapi==2023.07.01"],
|
||||
"version":"2023.07.01"
|
||||
"requirements": [
|
||||
"pyowletapi==2023.9.1"
|
||||
],
|
||||
"version": "2023.9.1"
|
||||
}
|
||||
@@ -26,75 +26,81 @@ from .entity import OwletBaseEntity
|
||||
|
||||
|
||||
@dataclass
|
||||
class OwletSensorEntityDescriptionMixin:
|
||||
"""Owlet sensor description mix in."""
|
||||
|
||||
element: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class OwletSensorEntityDescription(
|
||||
SensorEntityDescription, OwletSensorEntityDescriptionMixin
|
||||
):
|
||||
class OwletSensorEntityDescription(SensorEntityDescription):
|
||||
"""Represent the owlet sensor entity description."""
|
||||
|
||||
|
||||
SENSORS: tuple[OwletSensorEntityDescription, ...] = (
|
||||
SENSORS_ALL: tuple[OwletSensorEntityDescription, ...] = (
|
||||
OwletSensorEntityDescription(
|
||||
key="batterypercentage",
|
||||
key="battery_percentage",
|
||||
translation_key="batterypercent",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
element="battery_percentage",
|
||||
),
|
||||
OwletSensorEntityDescription(
|
||||
key="oxygensaturation",
|
||||
key="oxygen_saturation",
|
||||
translation_key="o2saturation",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
element="oxygen_saturation",
|
||||
icon="mdi:leaf",
|
||||
),
|
||||
OwletSensorEntityDescription(
|
||||
key="oxygensaturation10a",
|
||||
translation_key="o2saturation10a",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
element="oxygen_10_av",
|
||||
icon="mdi:leaf",
|
||||
),
|
||||
OwletSensorEntityDescription(
|
||||
key="heartrate",
|
||||
key="heart_rate",
|
||||
translation_key="heartrate",
|
||||
native_unit_of_measurement="bpm",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
element="heart_rate",
|
||||
icon="mdi:heart-pulse",
|
||||
),
|
||||
OwletSensorEntityDescription(
|
||||
key="batteryminutes",
|
||||
key="battery_minutes",
|
||||
translation_key="batterymin",
|
||||
native_unit_of_measurement=UnitOfTime.MINUTES,
|
||||
device_class=SensorDeviceClass.DURATION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
element="battery_minutes",
|
||||
),
|
||||
OwletSensorEntityDescription(
|
||||
key="signalstrength",
|
||||
key="signal_strength",
|
||||
translation_key="signalstrength",
|
||||
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
element="signal_strength",
|
||||
),
|
||||
OwletSensorEntityDescription(
|
||||
key="skintemp",
|
||||
key="skin_temperature",
|
||||
translation_key="skintemp",
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
element="skin_temperature",
|
||||
),
|
||||
OwletSensorEntityDescription(
|
||||
key="sleep_state",
|
||||
translation_key="sleepstate",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
),
|
||||
OwletSensorEntityDescription(
|
||||
key="movement",
|
||||
translation_key="movement",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
icon="mdi:cursor-move",
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
)
|
||||
|
||||
SENSORS_OLD: tuple[OwletSensorEntityDescription, ...] = (
|
||||
OwletSensorEntityDescription(
|
||||
key="oxygen_10_av",
|
||||
translation_key="o2saturation10a",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
icon="mdi:leaf",
|
||||
),
|
||||
OwletSensorEntityDescription(
|
||||
key="movement_bucket",
|
||||
translation_key="movementbucket",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
icon="mdi:bucket-outline",
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -110,15 +116,17 @@ async def async_setup_entry(
|
||||
hass.data[DOMAIN][config_entry.entry_id].values()
|
||||
)
|
||||
|
||||
async_add_entities(
|
||||
OwletSensor(coordinator, sensor)
|
||||
for coordinator in coordinators
|
||||
for sensor in SENSORS
|
||||
)
|
||||
sensors = []
|
||||
|
||||
async_add_entities(
|
||||
OwletSleepStateSensor(coordinator) for coordinator in coordinators
|
||||
)
|
||||
sensor_list = SENSORS_ALL
|
||||
for coordinator in coordinators:
|
||||
if coordinator.sock.revision < 5:
|
||||
sensor_list += SENSORS_OLD
|
||||
|
||||
for sensor in sensor_list:
|
||||
sensors.append(OwletSensor(coordinator, sensor))
|
||||
|
||||
async_add_entities(sensors)
|
||||
|
||||
|
||||
class OwletSensor(OwletBaseEntity, SensorEntity):
|
||||
@@ -127,60 +135,39 @@ class OwletSensor(OwletBaseEntity, SensorEntity):
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: OwletCoordinator,
|
||||
sensor_description: OwletSensorEntityDescription,
|
||||
description: OwletSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description: OwletSensorEntityDescription = sensor_description
|
||||
self._attr_unique_id = (
|
||||
f"{self.sock.serial}-{self.entity_description.translation_key}"
|
||||
)
|
||||
self.entity_description: OwletSensorEntityDescription = description
|
||||
self._attr_unique_id = f"{self.sock.serial}-{description.key}"
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return sensor value."""
|
||||
|
||||
if (
|
||||
self.entity_description.element
|
||||
self.entity_description.key
|
||||
in [
|
||||
"heart_rate",
|
||||
"battery_minutes",
|
||||
"oxygen_saturation",
|
||||
"skin_temperature",
|
||||
"oxygen_10_av",
|
||||
"sleep_state",
|
||||
]
|
||||
and self.sock.properties["charging"]
|
||||
):
|
||||
return None
|
||||
|
||||
properties = self.sock.properties
|
||||
|
||||
return properties[self.entity_description.element]
|
||||
|
||||
|
||||
class OwletSleepStateSensor(OwletBaseEntity, SensorEntity):
|
||||
"""Representation of an Owlet sleep state sensor."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: OwletCoordinator,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator)
|
||||
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
|
||||
def native_value(self) -> str:
|
||||
"""Return sensor value."""
|
||||
if self.sock.properties["charging"]:
|
||||
return "unknown"
|
||||
|
||||
if self.entity_description.key == "sleep_state":
|
||||
return SLEEP_STATES[self.sock.properties["sleep_state"]]
|
||||
|
||||
return self.sock.properties[self.entity_description.key]
|
||||
|
||||
@property
|
||||
def options(self) -> list[str]:
|
||||
"""Set options for sleep state."""
|
||||
if self.entity_description.key != "sleep_state":
|
||||
return None
|
||||
return list(SLEEP_STATES.values())
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Enter login details",
|
||||
"data": {
|
||||
"region": "Region",
|
||||
"username": "Email",
|
||||
@@ -44,28 +43,28 @@
|
||||
"name": "Charging"
|
||||
},
|
||||
"high_hr_alrt": {
|
||||
"name": "High Heart Rate Alert"
|
||||
"name": "High heart rate alert"
|
||||
},
|
||||
"low_hr_alrt": {
|
||||
"name":"Low Heart Rate Alert"
|
||||
"name": "Low heart rate alert"
|
||||
},
|
||||
"high_ox_alrt": {
|
||||
"name":"High Oxygen Alert"
|
||||
"name": "High oxygen alert"
|
||||
},
|
||||
"low_ox_alrt": {
|
||||
"name":"Low Oxygen Alert"
|
||||
"name": "Low oxygen alert"
|
||||
},
|
||||
"low_batt_alrt": {
|
||||
"name": "Low Battery Alert"
|
||||
"name": "Low battery alert"
|
||||
},
|
||||
"lost_pwr_alrt": {
|
||||
"name": "Lost Power Alert"
|
||||
"name": "Lost power alert"
|
||||
},
|
||||
"sock_discon_alrt": {
|
||||
"name": "Sock Diconnected Alert"
|
||||
"name": "Sock diconnected alert"
|
||||
},
|
||||
"sock_off": {
|
||||
"name":"Sock Off"
|
||||
"name": "Sock off"
|
||||
},
|
||||
"awake": {
|
||||
"name": "Awake"
|
||||
@@ -73,34 +72,40 @@
|
||||
},
|
||||
"sensor": {
|
||||
"batterypercent": {
|
||||
"name": "Battery Percentage"
|
||||
},
|
||||
"o2saturation": {
|
||||
"name": "O2 Saturation"
|
||||
},
|
||||
"o2saturation10a": {
|
||||
"name": "O2 Saturation 10 Minute Average"
|
||||
},
|
||||
"heartrate": {
|
||||
"name": "Heart Rate"
|
||||
},
|
||||
"batterymin": {
|
||||
"name": "Battery Remaining"
|
||||
"name": "Battery percentage"
|
||||
},
|
||||
"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": {
|
||||
"name": "Skin Temperature"
|
||||
"name": "Skin temperature"
|
||||
},
|
||||
"sleepstate": {
|
||||
"name": "Sleep State",
|
||||
"name": "Sleep state",
|
||||
"state": {
|
||||
"unknown": "Unknown",
|
||||
"awake": "Awake",
|
||||
"light_sleep": "Light Sleep",
|
||||
"deep_sleep": "Deep Sleep"
|
||||
}
|
||||
"light_sleep": "Light sleep",
|
||||
"deep_sleep": "Deep sleep"
|
||||
}
|
||||
},
|
||||
"movement": {
|
||||
"name": "Movement"
|
||||
},
|
||||
"movementbucket": {
|
||||
"name": "Movement bucket"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Enter login details",
|
||||
"data": {
|
||||
"region": "Region",
|
||||
"username": "Email",
|
||||
@@ -44,28 +43,28 @@
|
||||
"name": "Charging"
|
||||
},
|
||||
"high_hr_alrt": {
|
||||
"name": "High Heart Rate Alert"
|
||||
"name": "High heart rate alert"
|
||||
},
|
||||
"low_hr_alrt": {
|
||||
"name":"Low Heart Rate Alert"
|
||||
"name": "Low heart rate alert"
|
||||
},
|
||||
"high_ox_alrt": {
|
||||
"name":"High Oxygen Alert"
|
||||
"name": "High oxygen alert"
|
||||
},
|
||||
"low_ox_alrt": {
|
||||
"name":"Low Oxygen Alert"
|
||||
"name": "Low oxygen alert"
|
||||
},
|
||||
"low_batt_alrt": {
|
||||
"name": "Low Battery Alert"
|
||||
"name": "Low battery alert"
|
||||
},
|
||||
"lost_pwr_alrt": {
|
||||
"name": "Lost Power Alert"
|
||||
"name": "Lost power alert"
|
||||
},
|
||||
"sock_discon_alrt": {
|
||||
"name": "Sock Diconnected Alert"
|
||||
"name": "Sock diconnected alert"
|
||||
},
|
||||
"sock_off": {
|
||||
"name":"Sock Off"
|
||||
"name": "Sock off"
|
||||
},
|
||||
"awake": {
|
||||
"name": "Awake"
|
||||
@@ -73,34 +72,40 @@
|
||||
},
|
||||
"sensor": {
|
||||
"batterypercent": {
|
||||
"name": "Battery Percentage"
|
||||
},
|
||||
"o2saturation": {
|
||||
"name": "O2 Saturation"
|
||||
},
|
||||
"o2saturation10a": {
|
||||
"name": "O2 Saturation 10 Minute Average"
|
||||
},
|
||||
"heartrate": {
|
||||
"name": "Heart Rate"
|
||||
},
|
||||
"batterymin": {
|
||||
"name": "Battery Remaining"
|
||||
"name": "Battery percentage"
|
||||
},
|
||||
"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": {
|
||||
"name": "Skin Temperature"
|
||||
"name": "Skin temperature"
|
||||
},
|
||||
"sleepstate": {
|
||||
"name": "Sleep State",
|
||||
"name": "Sleep state",
|
||||
"state": {
|
||||
"unknown": "Unknown",
|
||||
"awake": "Awake",
|
||||
"light_sleep": "Light Sleep",
|
||||
"deep_sleep": "Deep Sleep"
|
||||
}
|
||||
"light_sleep": "Light sleep",
|
||||
"deep_sleep": "Deep sleep"
|
||||
}
|
||||
},
|
||||
"movement": {
|
||||
"name": "Movement"
|
||||
},
|
||||
"movementbucket": {
|
||||
"name": "Movement bucket"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Enter login details",
|
||||
"data": {
|
||||
"region": "Region",
|
||||
"username": "Email",
|
||||
@@ -44,28 +43,28 @@
|
||||
"name": "Charging"
|
||||
},
|
||||
"high_hr_alrt": {
|
||||
"name": "High Heart Rate Alert"
|
||||
"name": "High heart rate alert"
|
||||
},
|
||||
"low_hr_alrt": {
|
||||
"name":"Low Heart Rate Alert"
|
||||
"name": "Low heart rate alert"
|
||||
},
|
||||
"high_ox_alrt": {
|
||||
"name":"High Oxygen Alert"
|
||||
"name": "High oxygen alert"
|
||||
},
|
||||
"low_ox_alrt": {
|
||||
"name":"Low Oxygen Alert"
|
||||
"name": "Low oxygen alert"
|
||||
},
|
||||
"low_batt_alrt": {
|
||||
"name": "Low Battery Alert"
|
||||
"name": "Low battery alert"
|
||||
},
|
||||
"lost_pwr_alrt": {
|
||||
"name": "Lost Power Alert"
|
||||
"name": "Lost power alert"
|
||||
},
|
||||
"sock_discon_alrt": {
|
||||
"name": "Sock Diconnected Alert"
|
||||
"name": "Sock diconnected alert"
|
||||
},
|
||||
"sock_off": {
|
||||
"name":"Sock Off"
|
||||
"name": "Sock off"
|
||||
},
|
||||
"awake": {
|
||||
"name": "Awake"
|
||||
@@ -73,34 +72,40 @@
|
||||
},
|
||||
"sensor": {
|
||||
"batterypercent": {
|
||||
"name": "Battery Percentage"
|
||||
},
|
||||
"o2saturation": {
|
||||
"name": "O2 Saturation"
|
||||
},
|
||||
"o2saturation10a": {
|
||||
"name": "O2 Saturation 10 Minute Average"
|
||||
},
|
||||
"heartrate": {
|
||||
"name": "Heart Rate"
|
||||
},
|
||||
"batterymin": {
|
||||
"name": "Battery Remaining"
|
||||
"name": "Battery percentage"
|
||||
},
|
||||
"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": {
|
||||
"name": "Skin Temperature"
|
||||
"name": "Skin temperature"
|
||||
},
|
||||
"sleepstate": {
|
||||
"name": "Sleep State",
|
||||
"name": "Sleep state",
|
||||
"state": {
|
||||
"unknown": "Unknown",
|
||||
"awake": "Awake",
|
||||
"light_sleep": "Light Sleep",
|
||||
"deep_sleep": "Deep Sleep"
|
||||
}
|
||||
"light_sleep": "Light sleep",
|
||||
"deep_sleep": "Deep sleep"
|
||||
}
|
||||
},
|
||||
"movement": {
|
||||
"name": "Movement"
|
||||
},
|
||||
"movementbucket": {
|
||||
"name": "Movement bucket"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user