8 Commits

Author SHA1 Message Date
ryanbdclark
faefd0b18b Update info.md 2023-11-23 15:42:11 +00:00
ryanbdclark
1192b833ca Update README.md 2023-11-23 15:41:37 +00:00
ryanbdclark
d440fed621 Update CHANGELOG.md 2023-11-23 15:41:03 +00:00
RyanClark123
50fe1a8765 Support for V2 sock added
### Feature
* Support added for V2 sock
* Added tests for binary sensors
### Fix
* Bumping pyowletapi to 2023.11.4 to allow V2 support
* Refactoring
* Corrected spelling of sock disconnected sensor
2023-11-23 15:38:35 +00:00
ryanbdclark
1cfff537d7 Update CHANGELOG.md 2023-11-16 11:45:00 +00:00
RyanClark123
3acf847352 Correcting error where properties may not exist
### Fix
* Bumping pyowletapi to 2023.11.1
* Sensors and binary sensors are now only created where the sock contains that property, this stops errors where different sock versions have different properties
2023-11-16 11:42:34 +00:00
ryanbdclark
c0bf404f6a Update CHANGELOG.md 2023-09-20 15:05:45 +01:00
RyanClark123
0a7f703100 Fix for new sock revision 5
### Fix
* Bumping pyowletapi to 2023.9.1 to allow for revisions
* 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
2023-09-20 15:03:10 +01:00
19 changed files with 1630 additions and 167 deletions

View File

@@ -1,7 +1,27 @@
# Changelog # Changelog
<!--next-version-placeholder--> <!--next-version-placeholder-->
## 2023.08.1 (2023-08-21) ##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 ### 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)) * 2 new sensors, movement and movement bucket disabled by default, thanks [`@seanford`](https://github.com/seanford) ([`575b213`](https://github.com/ryanbdclark/owlet/commit/575b213ddd732779cd7938e575fc87c8881a69b0))
@@ -17,11 +37,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
@@ -31,25 +51,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

View File

@@ -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".
@@ -47,4 +45,4 @@ This integration provides the following entities:
[releases]: https://github.com/ryanbdclark/owlet/releases [releases]: https://github.com/ryanbdclark/owlet/releases
[user_profile]: https://github.com/ryanbdclark [user_profile]: https://github.com/ryanbdclark
[add-integration]: https://my.home-assistant.io/redirect/config_flow_start?domain=owlet [add-integration]: https://my.home-assistant.io/redirect/config_flow_start?domain=owlet
[add-integration-badge]: https://my.home-assistant.io/badges/config_flow_start.svg [add-integration-badge]: https://my.home-assistant.io/badges/config_flow_start.svg

View File

@@ -42,18 +42,27 @@ SENSORS: tuple[OwletBinarySensorEntityDescription, ...] = (
key="high_oxygen_alert", key="high_oxygen_alert",
translation_key="high_ox_alrt", translation_key="high_ox_alrt",
device_class=BinarySensorDeviceClass.SOUND, device_class=BinarySensorDeviceClass.SOUND,
entity_registry_enabled_default=False,
), ),
OwletBinarySensorEntityDescription( OwletBinarySensorEntityDescription(
key="low_oxygen_alert", key="low_oxygen_alert",
translation_key="low_ox_alrt", translation_key="low_ox_alrt",
device_class=BinarySensorDeviceClass.SOUND, device_class=BinarySensorDeviceClass.SOUND,
), ),
OwletBinarySensorEntityDescription(
key="critical_oxygen_alert",
translation_key="crit_ox_alrt",
device_class=BinarySensorDeviceClass.SOUND,
),
OwletBinarySensorEntityDescription( OwletBinarySensorEntityDescription(
key="low_battery_alert", key="low_battery_alert",
translation_key="low_batt_alrt", translation_key="low_batt_alrt",
device_class=BinarySensorDeviceClass.SOUND, device_class=BinarySensorDeviceClass.SOUND,
), ),
OwletBinarySensorEntityDescription(
key="critical_battery_alert",
translation_key="crit_batt_alrt",
device_class=BinarySensorDeviceClass.SOUND,
),
OwletBinarySensorEntityDescription( OwletBinarySensorEntityDescription(
key="lost_power_alert", key="lost_power_alert",
translation_key="lost_pwr_alrt", translation_key="lost_pwr_alrt",
@@ -86,11 +95,14 @@ 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 print(coordinator.sock.properties)
for sensor in SENSORS for sensor in SENSORS:
) if sensor.key in coordinator.sock.properties:
sensors.append(OwletBinarySensor(coordinator, sensor))
async_add_entities(sensors)
class OwletBinarySensor(OwletBaseEntity, BinarySensorEntity): class OwletBinarySensor(OwletBaseEntity, BinarySensorEntity):
@@ -111,12 +123,9 @@ class OwletBinarySensor(OwletBaseEntity, BinarySensorEntity):
"""Return true if the binary sensor is on.""" """Return true if the binary sensor is on."""
state = self.sock.properties[self.entity_description.key] state = self.sock.properties[self.entity_description.key]
entity = self.entity_description.key if self.entity_description.key == "sleep_state":
if self.sock.properties["charging"]:
if self.sock.properties["charging"] and entity in ["sleep_state"]: return None
return None
if entity == "sleep_state":
if state in [8, 15]: if state in [8, 15]:
state = False state = False
else: else:

View File

@@ -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
@@ -86,10 +85,8 @@ class OwletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
title=user_input[CONF_USERNAME], title=user_input[CONF_USERNAME],
data={ data={
CONF_REGION: user_input[CONF_REGION], CONF_REGION: user_input[CONF_REGION],
CONF_USERNAME: user_input[CONF_PASSWORD], 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},
) )

View File

@@ -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"}

View File

@@ -36,7 +36,7 @@ class OwletCoordinator(DataUpdateCoordinator):
update_interval=timedelta(seconds=interval), update_interval=timedelta(seconds=interval),
) )
self.sock = sock self.sock = sock
self.config_entry = entry self.config_entry: ConfigEntry = entry
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."""

View File

@@ -2,7 +2,7 @@
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.entity import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from .coordinator import OwletCoordinator from .coordinator import OwletCoordinator
from .const import DOMAIN, MANUFACTURER from .const import DOMAIN, MANUFACTURER
@@ -23,12 +23,12 @@ class OwletBaseEntity(CoordinatorEntity[OwletCoordinator], Entity):
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
"""Return the device info of the device""" """Return the device info of the device."""
return DeviceInfo( return DeviceInfo(
identifiers={(DOMAIN, self.sock.serial)}, identifiers={(DOMAIN, self.sock.serial)},
name="Owlet Baby Care Sock", name="Owlet Baby Care Sock",
manufacturer=MANUFACTURER, manufacturer=MANUFACTURER,
model=self.sock.model, model=self.sock.model,
sw_version=self.sock.sw_version, sw_version=self.sock.sw_version,
hw_version=self.sock.version, hw_version=f"{self.sock.version}r{self.sock.revision}",
) )

View File

@@ -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.8.1" "pyowletapi==2023.11.4"
} ],
"version": "2023.11.2"
}

View File

@@ -45,13 +45,6 @@ SENSORS: tuple[OwletSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
icon="mdi:leaf", icon="mdi:leaf",
), ),
OwletSensorEntityDescription(
key="oxygen_10_av",
translation_key="o2saturation10a",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:leaf",
),
OwletSensorEntityDescription( OwletSensorEntityDescription(
key="heart_rate", key="heart_rate",
translation_key="heartrate", translation_key="heartrate",
@@ -92,6 +85,13 @@ SENSORS: tuple[OwletSensorEntityDescription, ...] = (
icon="mdi:cursor-move", icon="mdi:cursor-move",
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
OwletSensorEntityDescription(
key="oxygen_10_av",
translation_key="o2saturation10a",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:leaf",
),
OwletSensorEntityDescription( OwletSensorEntityDescription(
key="movement_bucket", key="movement_bucket",
translation_key="movementbucket", translation_key="movementbucket",
@@ -113,11 +113,14 @@ 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 coordinator in coordinators:
for sensor in SENSORS for sensor in SENSORS:
) if sensor.key in coordinator.sock.properties:
sensors.append(OwletSensor(coordinator, sensor))
async_add_entities(sensors)
class OwletSensor(OwletBaseEntity, SensorEntity): class OwletSensor(OwletBaseEntity, SensorEntity):
@@ -157,7 +160,7 @@ class OwletSensor(OwletBaseEntity, SensorEntity):
return self.sock.properties[self.entity_description.key] return self.sock.properties[self.entity_description.key]
@property @property
def options(self) -> list[str]: def options(self) -> list[str] | None:
"""Set options for sleep state.""" """Set options for sleep state."""
if self.entity_description.key != "sleep_state": if self.entity_description.key != "sleep_state":
return None return None

View File

@@ -54,14 +54,20 @@
"low_ox_alrt": { "low_ox_alrt": {
"name": "Low oxygen alert" "name": "Low oxygen alert"
}, },
"crit_ox_alrt": {
"name": "Critical oxygen alert"
},
"low_batt_alrt": { "low_batt_alrt": {
"name": "Low battery alert" "name": "Low battery alert"
}, },
"crit_batt_alrt": {
"name": "Critical battery alert"
},
"lost_pwr_alrt": { "lost_pwr_alrt": {
"name": "Lost power alert" "name": "Lost power alert"
}, },
"sock_discon_alrt": { "sock_discon_alrt": {
"name": "Sock diconnected alert" "name": "Sock disconnected alert"
}, },
"sock_off": { "sock_off": {
"name": "Sock off" "name": "Sock off"

View File

@@ -1,112 +1,118 @@
{ {
"config": { "config": {
"step": { "step": {
"user": { "user": {
"data": { "data": {
"region": "Region", "region": "Region",
"username": "Email", "username": "Email",
"password": "Password" "password": "Password"
}
},
"reauth_confirm": {
"title": "Reauthentiaction required for Owlet",
"data": {
"password": "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"
"signalstrength": { },
"name": "Signal strength" "high_hr_alrt": {
}, "name": "High heart rate alert"
"o2saturation": { },
"name": "O2 saturation" "low_hr_alrt": {
}, "name": "Low heart rate alert"
"o2saturation10a": { },
"name": "O2 saturation 10 minute average" "high_ox_alrt": {
}, "name": "High oxygen alert"
"heartrate": { },
"name": "Heart rate" "low_ox_alrt": {
}, "name": "Low oxygen alert"
"batterymin": { },
"name": "Battery remaining" "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"
}
},
"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"
}
} }
},
"movement": {
"name": "Movement"
},
"movementbucket": {
"name": "Movement bucket"
}
} }
}
} }

View File

@@ -54,14 +54,20 @@
"low_ox_alrt": { "low_ox_alrt": {
"name": "Low oxygen alert" "name": "Low oxygen alert"
}, },
"crit_ox_alrt": {
"name": "Critical oxygen alert"
},
"low_batt_alrt": { "low_batt_alrt": {
"name": "Low battery alert" "name": "Low battery alert"
}, },
"crit_batt_alrt": {
"name": "Critical battery alert"
},
"lost_pwr_alrt": { "lost_pwr_alrt": {
"name": "Lost power alert" "name": "Lost power alert"
}, },
"sock_discon_alrt": { "sock_discon_alrt": {
"name": "Sock diconnected alert" "name": "Sock disconnected alert"
}, },
"sock_off": { "sock_off": {
"name": "Sock off" "name": "Sock off"

View File

@@ -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
@@ -34,4 +32,4 @@ If you have a smart sock 2 and would like to contribute then please do so.
[releases]: https://github.com/ryanbdclark/owlet/releases [releases]: https://github.com/ryanbdclark/owlet/releases
[user_profile]: https://github.com/ryanbdclark [user_profile]: https://github.com/ryanbdclark
[add-integration]: https://my.home-assistant.io/redirect/config_flow_start?domain=owlet [add-integration]: https://my.home-assistant.io/redirect/config_flow_start?domain=owlet
[add-integration-badge]: https://my.home-assistant.io/badges/config_flow_start.svg [add-integration-badge]: https://my.home-assistant.io/badges/config_flow_start.svg

View File

@@ -1052,4 +1052,4 @@
"expiry": 200, "expiry": 200,
"refresh": "new_refresh_token" "refresh": "new_refresh_token"
} }
} }

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
View 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"

View File

@@ -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:

View File

@@ -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)

View File

@@ -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"
)