From 2cd46c18f8e93fd6bbe62c644da0124f9b23ed44 Mon Sep 17 00:00:00 2001 From: RyanClark123 Date: Mon, 21 Aug 2023 15:06:28 +0100 Subject: [PATCH] Refactoring ###Fix * Refactoring done based on home assistant style guidelines and suggestions submitted on the core pull request version of this integration * Added new sensors to strings json and disabled by default --- custom_components/owlet/__init__.py | 2 +- custom_components/owlet/binary_sensor.py | 55 +++++------- custom_components/owlet/config_flow.py | 37 ++++---- custom_components/owlet/coordinator.py | 18 ++-- custom_components/owlet/entity.py | 18 +++- custom_components/owlet/sensor.py | 88 ++++++-------------- custom_components/owlet/strings.json | 83 +++++++++--------- custom_components/owlet/translations/en.json | 83 +++++++++--------- custom_components/owlet/translations/uk.json | 83 +++++++++--------- 9 files changed, 214 insertions(+), 253 deletions(-) diff --git a/custom_components/owlet/__init__.py b/custom_components/owlet/__init__.py index 3089c47..333cb86 100644 --- a/custom_components/owlet/__init__.py +++ b/custom_components/owlet/__init__.py @@ -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() } diff --git a/custom_components/owlet/binary_sensor.py b/custom_components/owlet/binary_sensor.py index 5a69af4..5377520 100644 --- a/custom_components/owlet/binary_sensor.py +++ b/custom_components/owlet/binary_sensor.py @@ -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,51 @@ 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", + entity_registry_enabled_default=False, ), 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,21 +99,24 @@ 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.sock.properties["charging"]: - return None + entity = self.entity_description.key + + if self.sock.properties["charging"] and entity in ["sleep_state"]: + return None + + if entity == "sleep_state": if state in [8, 15]: state = False else: diff --git a/custom_components/owlet/config_flow.py b/custom_components/owlet/config_flow.py index e4c7f5a..ab9d288 100644 --- a/custom_components/owlet/config_flow.py +++ b/custom_components/owlet/config_flow.py @@ -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") diff --git a/custom_components/owlet/coordinator.py b/custom_components/owlet/coordinator.py index a9f6dd4..4016be8 100644 --- a/custom_components/owlet/coordinator.py +++ b/custom_components/owlet/coordinator.py @@ -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.""" diff --git a/custom_components/owlet/entity.py b/custom_components/owlet/entity.py index f5633a6..2c3356c 100644 --- a/custom_components/owlet/entity.py +++ b/custom_components/owlet/entity.py @@ -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=self.sock.version, + ) diff --git a/custom_components/owlet/sensor.py b/custom_components/owlet/sensor.py index 6f87091..6e2cf8a 100644 --- a/custom_components/owlet/sensor.py +++ b/custom_components/owlet/sensor.py @@ -26,89 +26,78 @@ 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, ...] = ( 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", + key="oxygen_10_av", 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, - element="movement", icon="mdi:cursor-move", + entity_registry_enabled_default=False, ), OwletSensorEntityDescription( - key="movementbucket", + key="movement_bucket", translation_key="movementbucket", state_class=SensorStateClass.MEASUREMENT, - element="movement_bucket", icon="mdi:bucket-outline", + entity_registry_enabled_default=False, ), ) @@ -130,10 +119,6 @@ async def async_setup_entry( for sensor in SENSORS ) - async_add_entities( - OwletSleepStateSensor(coordinator) for coordinator in coordinators - ) - class OwletSensor(OwletBaseEntity, SensorEntity): """Representation of an Owlet sensor.""" @@ -141,60 +126,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 + if self.entity_description.key == "sleep_state": + return SLEEP_STATES[self.sock.properties["sleep_state"]] - 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" - - 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()) diff --git a/custom_components/owlet/strings.json b/custom_components/owlet/strings.json index 00843cf..d75702f 100644 --- a/custom_components/owlet/strings.json +++ b/custom_components/owlet/strings.json @@ -2,7 +2,6 @@ "config": { "step": { "user": { - "title": "Enter login details", "data": { "region": "Region", "username": "Email", @@ -39,69 +38,75 @@ } }, "entity": { - "binary_sensor":{ + "binary_sensor": { "charging": { "name": "Charging" }, - "high_hr_alrt":{ - "name": "High Heart Rate Alert" + "high_hr_alrt": { + "name": "High heart rate alert" }, - "low_hr_alrt":{ - "name":"Low Heart Rate Alert" + "low_hr_alrt": { + "name": "Low heart rate alert" }, - "high_ox_alrt":{ - "name":"High Oxygen Alert" + "high_ox_alrt": { + "name": "High oxygen alert" }, - "low_ox_alrt":{ - "name":"Low Oxygen Alert" + "low_ox_alrt": { + "name": "Low oxygen alert" }, - "low_batt_alrt":{ - "name": "Low Battery Alert" + "low_batt_alrt": { + "name": "Low battery alert" }, - "lost_pwr_alrt":{ - "name": "Lost Power Alert" + "lost_pwr_alrt": { + "name": "Lost power alert" }, - "sock_discon_alrt":{ - "name": "Sock Diconnected Alert" + "sock_discon_alrt": { + "name": "Sock diconnected alert" }, - "sock_off":{ - "name":"Sock Off" + "sock_off": { + "name": "Sock off" }, - "awake":{ - "name":"Awake" + "awake": { + "name": "Awake" } }, "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" } } } -} +} \ No newline at end of file diff --git a/custom_components/owlet/translations/en.json b/custom_components/owlet/translations/en.json index 00843cf..d75702f 100644 --- a/custom_components/owlet/translations/en.json +++ b/custom_components/owlet/translations/en.json @@ -2,7 +2,6 @@ "config": { "step": { "user": { - "title": "Enter login details", "data": { "region": "Region", "username": "Email", @@ -39,69 +38,75 @@ } }, "entity": { - "binary_sensor":{ + "binary_sensor": { "charging": { "name": "Charging" }, - "high_hr_alrt":{ - "name": "High Heart Rate Alert" + "high_hr_alrt": { + "name": "High heart rate alert" }, - "low_hr_alrt":{ - "name":"Low Heart Rate Alert" + "low_hr_alrt": { + "name": "Low heart rate alert" }, - "high_ox_alrt":{ - "name":"High Oxygen Alert" + "high_ox_alrt": { + "name": "High oxygen alert" }, - "low_ox_alrt":{ - "name":"Low Oxygen Alert" + "low_ox_alrt": { + "name": "Low oxygen alert" }, - "low_batt_alrt":{ - "name": "Low Battery Alert" + "low_batt_alrt": { + "name": "Low battery alert" }, - "lost_pwr_alrt":{ - "name": "Lost Power Alert" + "lost_pwr_alrt": { + "name": "Lost power alert" }, - "sock_discon_alrt":{ - "name": "Sock Diconnected Alert" + "sock_discon_alrt": { + "name": "Sock diconnected alert" }, - "sock_off":{ - "name":"Sock Off" + "sock_off": { + "name": "Sock off" }, - "awake":{ - "name":"Awake" + "awake": { + "name": "Awake" } }, "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" } } } -} +} \ No newline at end of file diff --git a/custom_components/owlet/translations/uk.json b/custom_components/owlet/translations/uk.json index 00843cf..d75702f 100644 --- a/custom_components/owlet/translations/uk.json +++ b/custom_components/owlet/translations/uk.json @@ -2,7 +2,6 @@ "config": { "step": { "user": { - "title": "Enter login details", "data": { "region": "Region", "username": "Email", @@ -39,69 +38,75 @@ } }, "entity": { - "binary_sensor":{ + "binary_sensor": { "charging": { "name": "Charging" }, - "high_hr_alrt":{ - "name": "High Heart Rate Alert" + "high_hr_alrt": { + "name": "High heart rate alert" }, - "low_hr_alrt":{ - "name":"Low Heart Rate Alert" + "low_hr_alrt": { + "name": "Low heart rate alert" }, - "high_ox_alrt":{ - "name":"High Oxygen Alert" + "high_ox_alrt": { + "name": "High oxygen alert" }, - "low_ox_alrt":{ - "name":"Low Oxygen Alert" + "low_ox_alrt": { + "name": "Low oxygen alert" }, - "low_batt_alrt":{ - "name": "Low Battery Alert" + "low_batt_alrt": { + "name": "Low battery alert" }, - "lost_pwr_alrt":{ - "name": "Lost Power Alert" + "lost_pwr_alrt": { + "name": "Lost power alert" }, - "sock_discon_alrt":{ - "name": "Sock Diconnected Alert" + "sock_discon_alrt": { + "name": "Sock diconnected alert" }, - "sock_off":{ - "name":"Sock Off" + "sock_off": { + "name": "Sock off" }, - "awake":{ - "name":"Awake" + "awake": { + "name": "Awake" } }, "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" } } } -} +} \ No newline at end of file