diff --git a/custom_components/saas/__init__.py b/custom_components/saas/__init__.py new file mode 100644 index 0000000..e099cd6 --- /dev/null +++ b/custom_components/saas/__init__.py @@ -0,0 +1,36 @@ +"""The SAAS - Sleep As Android Stats integration.""" +import voluptuous as vol +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers import discovery +from .const import DOMAIN + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the SAAS - Sleep As Android Status component.""" + return True + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up SAAS - Sleep As Android Status from a config entry.""" + # Store the configuration data for each entry separately, using the entry's unique ID as a key + if DOMAIN not in hass.data: + hass.data[DOMAIN] = {} + hass.data[DOMAIN][entry.entry_id] = entry.data + + hass.async_create_task(hass.config_entries.async_forward_entry_setup(entry, "sensor")) + return True + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + # Remove the sensor platform + await hass.config_entries.async_forward_entry_unload(entry, "sensor") + + # Remove the entry from the domain data + hass.data[DOMAIN].pop(entry.entry_id) + + return True + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up SAAS - Sleep As Android Status from a config entry.""" + hass.data[DOMAIN] = entry.data + hass.async_create_task(hass.config_entries.async_forward_entry_setup(entry, "sensor")) + return True \ No newline at end of file diff --git a/custom_components/saas/config_flow.py b/custom_components/saas/config_flow.py new file mode 100644 index 0000000..144b007 --- /dev/null +++ b/custom_components/saas/config_flow.py @@ -0,0 +1,65 @@ +from .const import DOMAIN, CONF_NAME, CONF_TOPIC, CONF_QOS, STATE_MAPPING, CONF_AWAKE_DURATION, CONF_SLEEP_DURATION, CONF_AWAKE_STATES, CONF_SLEEP_STATES, DEFAULT_AWAKE_DURATION, DEFAULT_SLEEP_DURATION, DEFAULT_AWAKE_STATES, DEFAULT_SLEEP_STATES +from homeassistant import config_entries +from homeassistant.core import callback +from voluptuous import Schema, Required, In +from homeassistant.helpers import config_validation as cv + +class MyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + errors = {} + + if user_input is not None: + return self.async_create_entry(title=user_input[CONF_NAME], data=user_input) + + return self.async_show_form( + step_id="user", + data_schema=Schema( + { + Required(CONF_NAME): str, + Required(CONF_TOPIC): str, + Required(CONF_QOS, default=0): In([0, 1, 2]), + Required(CONF_AWAKE_DURATION, default=DEFAULT_AWAKE_DURATION): int, + Required(CONF_SLEEP_DURATION, default=DEFAULT_SLEEP_DURATION): int, + Required(CONF_AWAKE_STATES, default=DEFAULT_AWAKE_STATES): cv.multi_select(STATE_MAPPING), + Required(CONF_SLEEP_STATES, default=DEFAULT_SLEEP_STATES): cv.multi_select(STATE_MAPPING), + } + ), + errors=errors, + ) + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return OptionsFlowHandler(config_entry) + +class OptionsFlowHandler(config_entries.OptionsFlow): + """Handle options.""" + + def __init__(self, config_entry): + """Initialize options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Manage the options.""" + if user_input is not None: + return self.async_create_entry(title=user_input.get(CONF_NAME, ""), data=user_input) + + return self.async_show_form( + step_id="init", + data_schema=Schema( + { + Required(CONF_NAME, default=self.config_entry.options.get(CONF_NAME)): str, + Required(CONF_TOPIC, default=self.config_entry.options.get(CONF_TOPIC)): str, + Required(CONF_QOS, default=self.config_entry.options.get(CONF_QOS, 0)): In([0, 1, 2]), + Required(CONF_AWAKE_DURATION, default=self.config_entry.options.get(CONF_AWAKE_DURATION, DEFAULT_AWAKE_DURATION)): int, + Required(CONF_SLEEP_DURATION, default=self.config_entry.options.get(CONF_SLEEP_DURATION, DEFAULT_SLEEP_DURATION)): int, + Required(CONF_AWAKE_STATES, default=self.config_entry.options.get(CONF_AWAKE_STATES, DEFAULT_AWAKE_STATES)): cv.multi_select(STATE_MAPPING), + Required(CONF_SLEEP_STATES, default=self.config_entry.options.get(CONF_SLEEP_STATES, DEFAULT_SLEEP_STATES)): cv.multi_select(STATE_MAPPING), + } + ), + ) \ No newline at end of file diff --git a/custom_components/saas/const.py b/custom_components/saas/const.py new file mode 100644 index 0000000..9785e8c --- /dev/null +++ b/custom_components/saas/const.py @@ -0,0 +1,127 @@ +"""Constants for the SAAS - Sleep As Android Stats integration.""" + +DOMAIN = "saas" + +INTEGRATION_NAME = "SAAS - Sleep As Android Stats" +MODEL = "SAAS - Version 0.0.1" + +CONF_NAME = "name" # Name of the Integration +CONF_TOPIC = "topic_template" # MQTT Topic for Sleep As Android Events +CONF_QOS = "qos" # Quality of Service +CONF_AWAKE_DURATION = "awake_duration" # Awake Duration +CONF_SLEEP_DURATION = "sleep_duration" # Sleep Duration + +CONF_AWAKE_STATES = "awake_states" # Awake States +CONF_SLEEP_STATES = "sleep_states" # Sleep States + +DEFAULT_AWAKE_DURATION = 10 # Default Awake Duration +DEFAULT_SLEEP_DURATION = 10 # Default Sleep Duration +DEFAULT_AWAKE_STATES = ["awake", "sleep_tracking_stopped"] # Default Awake States +DEFAULT_SLEEP_STATES = ["not_awake", "rem", "light_sleep", "deep_sleep", "sleep_tracking_started"] # Default Sleep States + +SENSOR_TYPES = { + "state": {"name": "State", "device_class": None}, + "awake": {"name": "Awake", "device_class": "motion"}, +} + +AVAILABLE_STATES = [ + 'unknown', + 'alarm_alert_dismiss', + 'alarm_alert_start', + 'alarm_rescheduled', + 'alarm_skip_next', + 'alarm_snooze_canceled', + 'alarm_snooze_clicked', + 'antisnoring', + 'apnea_alarm', + 'awake', + 'before_alarm', + 'before_smart_period', + 'deep_sleep', + 'light_sleep', + 'lullaby_start', + 'lullaby_stop', + 'lullaby_volume_down', + 'not_awake', + 'rem', + 'show_skip_next_alarm', + 'sleep_tracking_paused', + 'sleep_tracking_resumed', + 'sleep_tracking_started', + 'sleep_tracking_stopped', + 'smart_period', + 'sound_event_baby', + 'sound_event_cough', + 'sound_event_laugh', + 'sound_event_snore', + 'sound_event_talk', + 'time_to_bed_alarm_alert' +] +STATE_MAPPING = { + "unknown": "Unknown", + "sleep_tracking_started": "Sleep Tracking Started", + "sleep_tracking_stopped": "Sleep Tracking Stopped", + "sleep_tracking_paused": "Sleep Tracking Paused", + "sleep_tracking_resumed": "Sleep Tracking Resumed", + "alarm_snooze_clicked": "Alarm Snoozed", + "alarm_snooze_canceled": "Snooze Canceled", + "time_to_bed_alarm_alert": "Time To Bed Alarm Alert", + "alarm_alert_start": "Alarm Alert Started", + "alarm_alert_dismiss": "Alarm Dismissed", + "alarm_skip_next": "Skip Next Alarm", + "show_skip_next_alarm": "Show Skip Next Alarm", + "rem": "REM", + "smart_period": "Smart Period", + "before_smart_period": "Before Smart Period", + "lullaby_start": "Lullaby Start", + "lullaby_stop": "Lullaby Stop", + "lullaby_volume_down": "Lullaby Volume Down", + "deep_sleep": "Deep Sleep", + "light_sleep": "Light Sleep", + "awake": "Awake", + "not_awake": "Not Awake", + "apnea_alarm": "Apnea Alarm", + "antisnoring": "Antisnoring", + "before_alarm": "Before Alarm", + "sound_event_snore": "Snore Detected", + "sound_event_talk": "Talk Detected", + "sound_event_cough": "Cough Detected", + "sound_event_baby": "Baby Cry Detected", + "sound_event_laugh": "Laugh Detected", + "alarm_rescheduled": "Alarm Rescheduled" +} + +SOUND_MAPPING = { + 'sound_event_snore': "Snore Detected", + 'sound_event_talk': "Talk Detected", + 'sound_event_cough': "Cough Detected", + 'sound_event_baby': "Baby Cry Detected", + 'sound_event_laugh': "Laugh Detected" +} + +DISTURBANCE_MAPPING = { + 'apnea_alarm': "Apnea Alarm", + 'antisnoring': "Antisnoring" +} + +ALARM_EVENT_MAPPING = { + 'before_alarm': "Before Alarm", + 'alarm_snooze_clicked': "Alarm Snoozed", + 'alarm_snooze_canceled': "Snooze Canceled", + 'time_to_bed_alarm_alert': "Time To Bed Alarm Alert", + 'alarm_alert_start': "Alarm Alert Started", + 'alarm_alert_dismiss': "Alarm Dismissed", + 'alarm_skip_next': "Skip Next Alarm", + 'show_skip_next_alarm': "Show Skip Next Alarm", + 'rem': "REM", + 'smart_period': "Smart Period", + 'before_smart_period': "Before Smart Period", + "alarm_rescheduled": "Alarm Rescheduled" +} + +SLEEP_TRACKING_MAPPING = { + 'sleep_tracking_started': "Sleep Tracking Started", + 'sleep_tracking_stopped': "Sleep Tracking Stopped", + 'sleep_tracking_paused': "Sleep Tracking Paused", + 'sleep_tracking_resumed': "Sleep Tracking Resumed" +} \ No newline at end of file diff --git a/custom_components/saas/icon.png b/custom_components/saas/icon.png new file mode 100644 index 0000000..640443c Binary files /dev/null and b/custom_components/saas/icon.png differ diff --git a/custom_components/saas/manifest.json b/custom_components/saas/manifest.json new file mode 100644 index 0000000..d0e07af --- /dev/null +++ b/custom_components/saas/manifest.json @@ -0,0 +1,13 @@ +{ + "domain": "saas", + "name": "SAAS - Sleep As Android Status", + "codeowners": ["@sudoxnym"], + "config_flow": true, + "dependencies": ["mqtt"], + "documentation": "https://www.github.com/sudoxnym/saas", + "iot_class": "local_push", + "issue_tracker": "", + "quality_scale": "silver", + "requirements": ["pyhaversion", "paho-mqtt"], + "version": "0.0.1" +} \ No newline at end of file diff --git a/custom_components/saas/sensor.py b/custom_components/saas/sensor.py new file mode 100644 index 0000000..2e2b169 --- /dev/null +++ b/custom_components/saas/sensor.py @@ -0,0 +1,486 @@ +import asyncio +import json +from datetime import timedelta, datetime +from collections import deque +from homeassistant.helpers.event import async_track_time_interval +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.components.mqtt import async_subscribe +from homeassistant.helpers.entity import Entity +from .const import DOMAIN, CONF_NAME, CONF_TOPIC, CONF_AWAKE_STATES, CONF_SLEEP_STATES, CONF_AWAKE_DURATION, CONF_SLEEP_DURATION, INTEGRATION_NAME, MODEL, STATE_MAPPING, SOUND_MAPPING, DISTURBANCE_MAPPING, ALARM_EVENT_MAPPING, SLEEP_TRACKING_MAPPING +import logging + +_LOGGER = logging.getLogger(__name__) + +class SAASSensor(Entity): + """Representation of a SAAS - Sleep As Android Stats sensor.""" + + def __init__(self, hass, name, mapping): + """Initialize the sensor.""" + self._state = None + self._name = name + self._hass = hass + self._mapping = mapping + + @property + def unique_id(self): + """Return a unique ID.""" + return f"saas_sensor_{self._name}" + + @property + def name(self): + """Return the name of the sensor.""" + return f"SAAS {self._name} State" + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def device_info(self): + """Return information about the device.""" + return { + "identifiers": {(DOMAIN, self._name)}, + "name": self._name, + "manufacturer": INTEGRATION_NAME, + "model": MODEL, + } + + async def async_added_to_hass(self): + """Run when entity about to be added.""" + await super().async_added_to_hass() + + async def message_received(msg): + """Handle new MQTT messages.""" + # Parse the incoming message + msg_json = json.loads(msg.payload) + + _LOGGER.info(f"Received MQTT message: {msg_json}") + + # Extract the EVENT field + event = msg_json.get('event') + + if event is None: + _LOGGER.warning(f"No 'event' key in the MQTT message: {msg_json}") + return + + # Use the mapping to convert the event to the corresponding state + new_state = self._mapping.get(event) + if new_state is not None: + self._state = new_state + self.async_schedule_update_ha_state() + #else: + #_LOGGER.warning(f"No mapping found for event '{event}'") + + # Subscribe to the topic from the user input + await async_subscribe(self._hass, self._hass.data[DOMAIN][CONF_TOPIC], message_received) + +class SAASAlarmEventSensor(Entity): + """Representation of a SAAS - Sleep As Android Stats sensor for Alarm Events.""" + + def __init__(self, hass, name, mapping): + """Initialize the sensor.""" + self._state = None + self._name = name + self._hass = hass + self._mapping = mapping + self._value1 = None + self._time = None + + @property + def unique_id(self): + """Return a unique ID.""" + return f"saas_alarm_event_sensor_{self._name}" + + @property + def name(self): + """Return the name of the sensor.""" + return f"SAAS {self._name} Alarm Event" + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def device_info(self): + """Return information about the device.""" + return { + "identifiers": {(DOMAIN, self._name)}, + "name": self._name, + "manufacturer": INTEGRATION_NAME, + "model": MODEL, + } + + async def async_added_to_hass(self): + """Run when entity about to be added.""" + await super().async_added_to_hass() + + async def message_received(msg): + """Handle new MQTT messages.""" + # Parse the incoming message + msg_json = json.loads(msg.payload) + + _LOGGER.info(f"Received MQTT message: {msg_json}") + + # Extract the EVENT field + event = msg_json.get('event') + + if event is None: + _LOGGER.warning(f"No 'event' key in the MQTT message: {msg_json}") + return + + # Use the mapping to convert the event to the corresponding state + new_state = self._mapping.get(event) + if new_state is not None: + self._state = new_state + self.async_schedule_update_ha_state() + + # Subscribe to the topic from the user input + await async_subscribe(self._hass, self._hass.data[DOMAIN][CONF_TOPIC], message_received) + + +class SAASSoundSensor(Entity): + """Representation of a SAAS - Sleep As Android Stats sensor for Sound Events.""" + def __init__(self, hass, name, mapping): + """Initialize the sensor.""" + self._state = None + self._name = name + self._hass = hass + self._mapping = mapping + + @property + def unique_id(self): + """Return a unique ID.""" + return f"saas_sound_sensor_{self._name}" + + @property + def name(self): + """Return the name of the sensor.""" + return f"SAAS {self._name} Sound" + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def device_info(self): + """Return information about the device.""" + return { + "identifiers": {(DOMAIN, self._name)}, + "name": self._name, + "manufacturer": INTEGRATION_NAME, + "model": MODEL, + } + + async def async_added_to_hass(self): + """Run when entity about to be added.""" + await super().async_added_to_hass() + + async def message_received(msg): + """Handle new MQTT messages.""" + # Parse the incoming message + msg_json = json.loads(msg.payload) + + _LOGGER.info(f"Received MQTT message: {msg_json}") + + # Extract the EVENT field + event = msg_json.get('event') + + if event is None: + #_LOGGER.warning(f"No 'event' key in the MQTT message: {msg_json}") + return + + # Use the mapping to convert the event to the corresponding state + new_state = self._mapping.get(event, "None") # Default to "None" if no mapping found + self._state = new_state + self.async_schedule_update_ha_state() + + # Subscribe to the topic from the user input + await async_subscribe(self._hass, self._hass.data[DOMAIN][CONF_TOPIC], message_received) + + async def message_received(msg): + """Handle new MQTT messages.""" + # Parse the incoming message + msg_json = json.loads(msg.payload) + + _LOGGER.info(f"Received MQTT message: {msg_json}") + + # Extract the EVENT field + event = msg_json.get('event') + + if event is None: + _LOGGER.warning(f"No 'event' key in the MQTT message: {msg_json}") + return + + # Use the mapping to convert the event to the corresponding state + new_state = self._mapping.get(event, "None") # Default to "None" if no mapping found + if new_state is not None: + self._state = new_state + self.async_schedule_update_ha_state() + #else: + #_LOGGER.warning(f"No mapping found for event '{event}'") + + +class SAASSleepTrackingSensor(Entity): + """Representation of a SAAS - Sleep As Android Stats sensor for Sleep Tracking.""" + def __init__(self, hass, name, mapping): + """Initialize the sensor.""" + self._state = None + self._name = name + self._hass = hass + self._mapping = mapping + + @property + def unique_id(self): + """Return a unique ID.""" + return f"saas_sleep_tracking_sensor_{self._name}" + + @property + def name(self): + """Return the name of the sensor.""" + return f"SAAS {self._name} Sleep Tracking" + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def device_info(self): + """Return information about the device.""" + return { + "identifiers": {(DOMAIN, self._name)}, + "name": self._name, + "manufacturer": INTEGRATION_NAME, + "model": MODEL, + } + + async def async_added_to_hass(self): + """Run when entity about to be added.""" + await super().async_added_to_hass() + + async def message_received(msg): + """Handle new MQTT messages.""" + # Parse the incoming message + msg_json = json.loads(msg.payload) + + _LOGGER.info(f"Received MQTT message: {msg_json}") + + # Extract the EVENT field + event = msg_json.get('event') + + if event is None: + #_LOGGER.warning(f"No 'event' key in the MQTT message: {msg_json}") + return + + # Use the mapping to convert the event to the corresponding state + new_state = self._mapping.get(event, "None") # Default to "None" if no mapping found + self._state = new_state + self.async_schedule_update_ha_state() + + # Subscribe to the topic from the user input + await async_subscribe(self._hass, self._hass.data[DOMAIN][CONF_TOPIC], message_received) + +class SAASDisturbanceSensor(Entity): + """Representation of a SAAS - Sleep As Android Stats sensor for Disturbance Events.""" + def __init__(self, hass, name, mapping): + """Initialize the sensor.""" + self._state = None + self._name = name + self._hass = hass + self._mapping = mapping + + @property + def unique_id(self): + """Return a unique ID.""" + return f"saas_disturbance_sensor_{self._name}" + + @property + def name(self): + """Return the name of the sensor.""" + return f"SAAS {self._name} Disturbance" + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def device_info(self): + """Return information about the device.""" + return { + "identifiers": {(DOMAIN, self._name)}, + "name": self._name, + "manufacturer": INTEGRATION_NAME, + "model": MODEL, + } + + async def async_added_to_hass(self): + """Run when entity about to be added.""" + await super().async_added_to_hass() + + async def message_received(msg): + """Handle new MQTT messages.""" + # Parse the incoming message + msg_json = json.loads(msg.payload) + + _LOGGER.info(f"Received MQTT message: {msg_json}") + + # Extract the EVENT field + event = msg_json.get('event') + + if event is None: + #_LOGGER.warning(f"No 'event' key in the MQTT message: {msg_json}") + return + + # Use the mapping to convert the event to the corresponding state + new_state = DISTURBANCE_MAPPING.get(event, "None") + self._state = new_state + self.async_schedule_update_ha_state() + + # Subscribe to the topic from the user input + await async_subscribe(self._hass, self._hass.data[DOMAIN][CONF_TOPIC], message_received) + +class SAASWakeStatusSensor(Entity): + """Representation of a SAAS - Sleep As Android Stats sensor for Wake Status.""" + + + def __init__(self, hass, name, awake_states, sleep_states, awake_duration, sleep_duration): + """Initialize the sensor.""" + self._state = None + self._name = name + self._hass = hass + self._awake_states = awake_states + self._sleep_states = sleep_states + self._awake_duration = timedelta(seconds=awake_duration) + self._sleep_duration = timedelta(seconds=sleep_duration) + self._last_message_time = datetime.now() + _LOGGER.info(f"Subscribing to topic: {self._hass.data[DOMAIN][CONF_TOPIC]}") + + @property + def unique_id(self): + """Return a unique ID.""" + return f"saas_wake_status_sensor_{self._name}" + + @property + def name(self): + """Return the name of the sensor.""" + return f"SAAS {self._name} Wake Status" + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def device_info(self): + """Return information about the device.""" + return { + "identifiers": {(DOMAIN, self._name)}, + "name": self._name, + "manufacturer": INTEGRATION_NAME, + "model": MODEL, + } + + async def message_received(self, msg): + """Handle new MQTT messages.""" + # Parse the incoming message + msg_json = json.loads(msg.payload) + + _LOGGER.info(f"Received MQTT message: {msg_json}") + + # Extract the EVENT field + event = msg_json.get('event') + + if event is None: + _LOGGER.warning(f"No 'event' key in the MQTT message: {msg_json}") + return + + # Update the last message time + self._last_message_time = datetime.now() + + # Check if the event matches the awake or asleep states + if event in self._awake_states: + self._state = 'Awake' + elif event in self._sleep_states: + self._state = 'Asleep' + + self.async_schedule_update_ha_state() + + _LOGGER.info(f"Message received. Event: {event}, State: {self._state}") + + async def async_added_to_hass(self): + """Run when entity about to be added.""" + await super().async_added_to_hass() + + # Subscribe to the topic from the user input + await async_subscribe(self._hass, self._hass.data[DOMAIN][CONF_TOPIC], self.message_received) + + # Connect to the time changed event + async_dispatcher_connect(self._hass, 'time_changed', self._time_changed) + + # Schedule time interval updates + async_track_time_interval(self._hass, self._time_changed, timedelta(seconds=10)) + + async def _time_changed(self, event_time): + """Handle the time changed event.""" + if self._state == 'Awake' and datetime.utcnow() - self._last_message_time > self._awake_duration: + self._state = 'Asleep' + elif self._state == 'Asleep' and datetime.utcnow() - self._last_message_time > self._sleep_duration: + self._state = 'Awake' + self.async_schedule_update_ha_state() + + _LOGGER.info(f"Time changed. State: {self._state}") + + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the SAAS sensor platform.""" + name = hass.data[DOMAIN].get(CONF_NAME, "Default Name") + topic = hass.data[DOMAIN].get(CONF_TOPIC) + awake_states = hass.data[DOMAIN].get(CONF_AWAKE_STATES) + sleep_states = hass.data[DOMAIN].get(CONF_SLEEP_STATES) + awake_duration = hass.data[DOMAIN].get(CONF_AWAKE_DURATION) + sleep_duration = hass.data[DOMAIN].get(CONF_SLEEP_DURATION) + + entities = [ + SAASSensor(hass, name, STATE_MAPPING), + SAASAlarmEventSensor(hass, name, ALARM_EVENT_MAPPING), + SAASSoundSensor(hass, name, SOUND_MAPPING), + SAASSleepTrackingSensor(hass, name, SLEEP_TRACKING_MAPPING), + SAASDisturbanceSensor(hass, name, DISTURBANCE_MAPPING), + SAASWakeStatusSensor(hass, name, awake_states, sleep_states, awake_duration, sleep_duration) + ] + + for entity in entities: + if hasattr(entity, "async_setup"): + await entity.async_setup() + + async_add_entities(entities) + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up the SAAS sensor platform from a config entry.""" + name = entry.data.get(CONF_NAME, "Default Name") + topic = entry.data.get(CONF_TOPIC) + awake_states = entry.data.get(CONF_AWAKE_STATES) + sleep_states = entry.data.get(CONF_SLEEP_STATES) + awake_duration = entry.data.get(CONF_AWAKE_DURATION) + sleep_duration = entry.data.get(CONF_SLEEP_DURATION) + hass.data[DOMAIN] = entry.data + + entities = [ + SAASSensor(hass, name, STATE_MAPPING), + SAASAlarmEventSensor(hass, name, ALARM_EVENT_MAPPING), + SAASSoundSensor(hass, name, SOUND_MAPPING), + SAASSleepTrackingSensor(hass, name, SLEEP_TRACKING_MAPPING), + SAASDisturbanceSensor(hass, name, DISTURBANCE_MAPPING), + SAASWakeStatusSensor(hass, name, awake_states, sleep_states, awake_duration, sleep_duration) + ] + + + for entity in entities: + if hasattr(entity, "async_setup"): + await entity.async_setup() + + async_add_entities(entities) \ No newline at end of file diff --git a/custom_components/saas/translations/en.json b/custom_components/saas/translations/en.json new file mode 100644 index 0000000..41e9a07 --- /dev/null +++ b/custom_components/saas/translations/en.json @@ -0,0 +1,37 @@ +{ + "title": "SAAS - Sleep As Android Status", + "config": { + "step": { + "user": { + "title": "Configuration for SAAS - Sleep As Android Status", + "description": "Configure the basic settings for the SAAS integration.", + "data": { + "name": "Name for Sensor", + "topic_template": "MQTT Topic for Sleep As Android Events", + "qos": "Quality of Service (QoS) for MQTT", + "awake_duration": "Awake Duration", + "sleep_duration": "Asleep Duration", + "awake_states": "Awake States*", + "sleep_states": "Asleep States*" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "SAAS Integration Settings", + "description": "Configure the basic settings for the SAAS integration.", + "data": { + "name": "Name for Sensor", + "topic_template": "MQTT Topic (from Sleep As Android)", + "qos": "MQTT Quality of Service (QoS)", + "awake_duration": "Awake Duration", + "sleep_duration": "Asleep Duration", + "awake_states": "Awake States*", + "sleep_states": "Asleep States*" + } + } + } + } +} \ No newline at end of file