From cc1329e8708e404ba79b834cbfc2234a59b6ca61 Mon Sep 17 00:00:00 2001 From: sudoxnym <76703581+sudoxnym@users.noreply.github.com> Date: Thu, 9 May 2024 19:12:32 -0600 Subject: [PATCH] Add files via upload --- custom_components/saas/__init__.py | 46 ++ custom_components/saas/config_flow.py | 95 +++ custom_components/saas/const.py | 135 +++++ custom_components/saas/icon.png | Bin 0 -> 30563 bytes custom_components/saas/manifest.json | 13 + custom_components/saas/sensor.py | 583 +++++++++++++++++++ custom_components/saas/translations/ar.json | 75 +++ custom_components/saas/translations/de.json | 75 +++ custom_components/saas/translations/en.json | 75 +++ custom_components/saas/translations/es.json | 75 +++ custom_components/saas/translations/fr.json | 75 +++ custom_components/saas/translations/it.json | 75 +++ custom_components/saas/translations/ja.json | 75 +++ custom_components/saas/translations/ko.json | 75 +++ custom_components/saas/translations/nl.json | 75 +++ custom_components/saas/translations/pl.json | 75 +++ custom_components/saas/translations/pt.json | 75 +++ custom_components/saas/translations/pue.json | 75 +++ custom_components/saas/translations/ru.json | 75 +++ custom_components/saas/translations/sv.json | 75 +++ custom_components/saas/translations/zh.json | 75 +++ 21 files changed, 1997 insertions(+) create mode 100644 custom_components/saas/__init__.py create mode 100644 custom_components/saas/config_flow.py create mode 100644 custom_components/saas/const.py create mode 100644 custom_components/saas/icon.png create mode 100644 custom_components/saas/manifest.json create mode 100644 custom_components/saas/sensor.py create mode 100644 custom_components/saas/translations/ar.json create mode 100644 custom_components/saas/translations/de.json create mode 100644 custom_components/saas/translations/en.json create mode 100644 custom_components/saas/translations/es.json create mode 100644 custom_components/saas/translations/fr.json create mode 100644 custom_components/saas/translations/it.json create mode 100644 custom_components/saas/translations/ja.json create mode 100644 custom_components/saas/translations/ko.json create mode 100644 custom_components/saas/translations/nl.json create mode 100644 custom_components/saas/translations/pl.json create mode 100644 custom_components/saas/translations/pt.json create mode 100644 custom_components/saas/translations/pue.json create mode 100644 custom_components/saas/translations/ru.json create mode 100644 custom_components/saas/translations/sv.json create mode 100644 custom_components/saas/translations/zh.json diff --git a/custom_components/saas/__init__.py b/custom_components/saas/__init__.py new file mode 100644 index 0000000..518a888 --- /dev/null +++ b/custom_components/saas/__init__.py @@ -0,0 +1,46 @@ +import logging +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from .const import DOMAIN +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +_logger = logging.getLogger(__name__) + +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 a config entry.""" + hass.data.setdefault(DOMAIN, {}) + # Use hass.data[DOMAIN][entry.entry_id] instead of entry.options + if entry.entry_id in hass.data[DOMAIN]: + hass.data[DOMAIN][entry.entry_id] = hass.data[DOMAIN][entry.entry_id] + else: + hass.data[DOMAIN][entry.entry_id] = entry.data # Use entry.data instead of entry.options + + # Forward the setup to the sensor platform + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, "sensor") + ) + + # Define a coroutine function to reload the entry + async def reload_entry(): + await hass.config_entries.async_reload(entry.entry_id) + + # Listen for the reload signal and reload the integration when it is received + async_dispatcher_connect(hass, f"{DOMAIN}_reload_{entry.entry_id}", reload_entry) + + 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") + + # Ensure hass.data[DOMAIN] is a dictionary before popping + if isinstance(hass.data.get(DOMAIN, {}), dict): + if entry.entry_id in hass.data[DOMAIN]: + hass.data[DOMAIN].pop(entry.entry_id) + + 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..36b8441 --- /dev/null +++ b/custom_components/saas/config_flow.py @@ -0,0 +1,95 @@ +import logging +import traceback +import voluptuous as vol +from .const import DOMAIN, CONF_NAME, CONF_TOPIC, CONF_QOS, AVAILABLE_STATES, 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 +from homeassistant.helpers.dispatcher import async_dispatcher_send + +_logger = logging.getLogger(__name__) + +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(AVAILABLE_STATES), + Required(CONF_SLEEP_STATES, default=DEFAULT_SLEEP_STATES): cv.multi_select(AVAILABLE_STATES), + } + ), + 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.""" + _logger.debug("Entering async_step_init with user_input: %s", user_input) + + try: + # Fetch the initial configuration data + current_data = self.hass.data[DOMAIN].get(self.config_entry.entry_id, self.config_entry.options) + _logger.debug("Current data fetched: %s", current_data) + + if user_input is not None: + # Replace current_data with user_input + updated_data = user_input + _logger.debug("User input is not None, updated data: %s", updated_data) + + _logger.debug("Updating entry with updated data: %s", updated_data) + + if updated_data is not None: + self.hass.data[DOMAIN][self.config_entry.entry_id] = updated_data # Save updated data + self.config_entry.data = updated_data # Update the config entry data + self.hass.config_entries.async_update_entry(self.config_entry, data=updated_data) # Update the entry data + + # Send a signal to reload the integration + async_dispatcher_send(self.hass, f"{DOMAIN}_reload_{self.config_entry.entry_id}") + + return self.async_create_entry(title="", data=updated_data) + + _logger.debug("User input is None, showing form with current_data: %s", current_data) + return self.async_show_form(step_id="init", data_schema=self.get_data_schema(current_data)) + + except Exception as e: + _logger.error("Error in async_step_init: %s", str(e)) + return self.async_abort(reason=str(e)) + def get_data_schema(self, current_data): + return Schema( + { + Required(CONF_NAME, default=current_data.get(CONF_NAME, "")): str, + Required(CONF_TOPIC, default=current_data.get(CONF_TOPIC, "")): str, + Required(CONF_QOS, default=current_data.get(CONF_QOS, 0)): In([0, 1, 2]), + Required(CONF_AWAKE_DURATION, default=current_data.get(CONF_AWAKE_DURATION, DEFAULT_AWAKE_DURATION)): int, + Required(CONF_SLEEP_DURATION, default=current_data.get(CONF_SLEEP_DURATION, DEFAULT_SLEEP_DURATION)): int, + Required(CONF_AWAKE_STATES, default=current_data.get(CONF_AWAKE_STATES, DEFAULT_AWAKE_STATES)): cv.multi_select(AVAILABLE_STATES), + Required(CONF_SLEEP_STATES, default=current_data.get(CONF_SLEEP_STATES, DEFAULT_SLEEP_STATES)): cv.multi_select(AVAILABLE_STATES), + } + ) \ 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..07a930f --- /dev/null +++ b/custom_components/saas/const.py @@ -0,0 +1,135 @@ +"""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 for Bed' +] +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 for Bed", + "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" +} + +REVERSE_STATE_MAPPING = {v: k for k, v in STATE_MAPPING.items()} + +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" +} + +LULLABY_MAPPING = { + "lullaby_start": "Lullaby Start", + "lullaby_stop": "Lullaby Stop", + "lullaby_volume_down": "Lullaby Volume Down", +} + +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 for Bed", + '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 0000000000000000000000000000000000000000..640443cd900236100889b6cd52110b601bb06fed GIT binary patch literal 30563 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 literal 0 HcmV?d00001 diff --git a/custom_components/saas/manifest.json b/custom_components/saas/manifest.json new file mode 100644 index 0000000..2d4c322 --- /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": "https://www.github.com/sudoxnym/saas/issues", + "quality_scale": "silver", + "requirements": ["pyhaversion", "paho-mqtt"], + "version": "0.0.2" +} \ 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..06bd98b --- /dev/null +++ b/custom_components/saas/sensor.py @@ -0,0 +1,583 @@ +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 homeassistant.util import dt as dt_util +from homeassistant.components import mqtt +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, LULLABY_MAPPING, REVERSE_STATE_MAPPING +import logging +import time + +_LOGGER = logging.getLogger(__name__) +_LOGGER.setLevel(logging.DEBUG) + +class SAASSensor(Entity): + """Representation of a SAAS - Sleep As Android Stats sensor.""" + + def __init__(self, hass, name, mapping, entry_id): + """Initialize the sensor.""" + self._state = None + self._name = name + self._hass = hass + self._mapping = mapping + self.entry_id = entry_id + + @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][self.entry_id][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, entry_id): + """Initialize the sensor.""" + self._state = None + self._name = name + self._hass = hass + self._mapping = mapping + self._value1 = None + self._time = None + self.entry_id = entry_id + + @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][self.entry_id][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, entry_id): + """Initialize the sensor.""" + self._state = None + self._name = name + self._hass = hass + self._mapping = mapping + self.entry_id = entry_id + + @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][self.entry_id][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, entry_id): + """Initialize the sensor.""" + self._state = None + self._name = name + self._hass = hass + self._mapping = mapping + self.entry_id = entry_id + + @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][self.entry_id][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, entry_id): + """Initialize the sensor.""" + self._state = None + self._name = name + self._hass = hass + self._mapping = mapping + self.entry_id = entry_id + + @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][self.entry_id][CONF_TOPIC], message_received) + +class SAASLullabySensor(Entity): + """Representation of a SAAS - Sleep As Android Stats sensor for Lullaby.""" + def __init__(self, hass, name, mapping, entry_id): + """Initialize the sensor.""" + self._state = None + self._name = name + self._hass = hass + self._mapping = mapping + self.entry_id = entry_id + + @property + def unique_id(self): + """Return a unique ID.""" + return f"saas_lullaby_sensor_{self._name}" + + @property + def name(self): + """Return the name of the sensor.""" + return f"SAAS {self._name} Lullaby" + + @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][self.entry_id][CONF_TOPIC], message_received) +class SAASWakeStatusSensor(Entity): + def __init__(self, hass, name, awake_states, sleep_states, awake_duration, sleep_duration, mqtt_topic, entry_id): + self._state = None + self._name = name + self.awake_bucket = [] + self.sleep_bucket = [] + self.awake_duration = timedelta(seconds=awake_duration) + self.sleep_duration = timedelta(seconds=sleep_duration) + self.awake_states = awake_states + self.sleep_states = sleep_states + self.hass = hass + self.mqtt_topic = mqtt_topic + self.entry_id = entry_id + + @property + def unique_id(self): + """Return a unique ID.""" + return f"saas_wake_status_{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, + } + + def process_message(self, message): + try: + now = dt_util.utcnow() # Define 'now' before using it for logging + + # Extract the 'event' from the incoming message + event = message.get('event') + _LOGGER.debug(f"{dt_util.as_local(now).strftime('%H:%M:%S:%f')}: Extracted Event {event} from message.") + + # Map the event to a known state, or "Unknown" if the event is not recognized + mapped_value = STATE_MAPPING.get(event, "Unknown") + _LOGGER.debug(f"{dt_util.as_local(now).strftime('%H:%M:%S:%f')}: Mapped {event} to {mapped_value}.") + + # If the event could not be mapped to a known state, set the sensor state to "Unknown" + if mapped_value == "Unknown": + self._state = "Unknown" + _LOGGER.debug(f"{dt_util.as_local(now).strftime('%H:%M:%S:%f')}: Event {event} could not be mapped to a known state. Setting sensor state to 'Unknown'.") + + # If the mapped value is in the awake states, add it to the awake bucket and clear the sleep bucket + if mapped_value in self.awake_states: + self.awake_bucket.append((mapped_value, now)) + self.sleep_bucket = [] + _LOGGER.debug(f"{dt_util.as_local(now).strftime('%H:%M:%S:%f')}: Mapped value {mapped_value} is in awake states. Adding to awake bucket and clearing sleep bucket.") + + # If the mapped value is in the sleep states, add it to the sleep bucket and clear the awake bucket + elif mapped_value in self.sleep_states: + self.sleep_bucket.append((mapped_value, now)) + self.awake_bucket = [] + _LOGGER.debug(f"{dt_util.as_local(now).strftime('%H:%M:%S:%f')}: Mapped value {mapped_value} is in sleep states. Adding to sleep bucket and clearing awake bucket.") + + # Remove messages from the awake bucket that are older than the awake duration + self.awake_bucket = [(val, timestamp) for val, timestamp in self.awake_bucket if now - timestamp < self.awake_duration] + _LOGGER.debug(f"{dt_util.as_local(now).strftime('%H:%M:%S:%f')}: Removing messages from awake bucket that are older than the awake duration.") + + # Remove messages from the sleep bucket that are older than the sleep duration + self.sleep_bucket = [(val, timestamp) for val, timestamp in self.sleep_bucket if now - timestamp < self.sleep_duration] + _LOGGER.debug(f"{dt_util.as_local(now).strftime('%H:%M:%S:%f')}: Removing messages from sleep bucket that are older than the sleep duration.") + except Exception as e: + _LOGGER.error(f"Error processing message: {e}") + + async def async_update(self): + """Update the state.""" + now = dt_util.utcnow() + + # Remove messages from the awake bucket that are older than the awake duration + self.awake_bucket = [(val, timestamp) for val, timestamp in self.awake_bucket if now - timestamp < self.awake_duration] + + # Remove messages from the sleep bucket that are older than the sleep duration + self.sleep_bucket = [(val, timestamp) for val, timestamp in self.sleep_bucket if now - timestamp < self.sleep_duration] + + # If all messages in the awake bucket are within the awake duration, set the state to "Awake" + if self.awake_bucket and all(now - timestamp <= self.awake_duration for _, timestamp in self.awake_bucket): + self._state = "Awake" + + # If all messages in the sleep bucket are within the sleep duration, set the state to "Asleep" + elif self.sleep_bucket and all(now - timestamp <= self.sleep_duration for _, timestamp in self.sleep_bucket): + self._state = "Asleep" + + async def interval_callback(self, _): + """Wrapper function for async_track_time_interval.""" + # Call the async_update method to update the state + await self.async_update() + + async def async_added_to_hass(self): + """Run when entity about to be added.""" + await super().async_added_to_hass() + + # Schedule the interval callback to run every second + async_track_time_interval( + self.hass, + self.interval_callback, + timedelta(seconds=1) + ) + + # Subscribe to the MQTT topic to receive messages + await mqtt.async_subscribe( + self.hass, + self.mqtt_topic, + lambda message: self.process_message(json.loads(message.payload)) + ) +async def async_setup_entry(hass, entry, async_add_entities): + """Set up the SAAS sensor platform from a config entry.""" + name = hass.data[DOMAIN][entry.entry_id].get(CONF_NAME, "Default Name") + topic = hass.data[DOMAIN][entry.entry_id].get(CONF_TOPIC) + awake_states = hass.data[DOMAIN][entry.entry_id].get(CONF_AWAKE_STATES) + sleep_states = hass.data[DOMAIN][entry.entry_id].get(CONF_SLEEP_STATES) + awake_duration = hass.data[DOMAIN][entry.entry_id].get(CONF_AWAKE_DURATION) + sleep_duration = hass.data[DOMAIN][entry.entry_id].get(CONF_SLEEP_DURATION) + + entities = [ + SAASSensor(hass, name, STATE_MAPPING, entry_id), + SAASAlarmEventSensor(hass, name, ALARM_EVENT_MAPPING, entry_id), + SAASSoundSensor(hass, name, SOUND_MAPPING, entry_id), + SAASSleepTrackingSensor(hass, name, SLEEP_TRACKING_MAPPING, entry_id), + SAASDisturbanceSensor(hass, name, DISTURBANCE_MAPPING, entry_id), + SAASLullabySensor(hass, name, LULLABY_MAPPING, entry_id), + SAASWakeStatusSensor(hass, name, awake_states, sleep_states, awake_duration, sleep_duration, topic, entry_id) + ] + + 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) + entry_id = entry.entry_id + hass.data[DOMAIN][entry.entry_id] = entry.data + + entities = [ + SAASSensor(hass, name, STATE_MAPPING, entry_id), + SAASAlarmEventSensor(hass, name, ALARM_EVENT_MAPPING, entry_id), + SAASSoundSensor(hass, name, SOUND_MAPPING, entry_id), + SAASSleepTrackingSensor(hass, name, SLEEP_TRACKING_MAPPING, entry_id), + SAASDisturbanceSensor(hass, name, DISTURBANCE_MAPPING, entry_id), + SAASLullabySensor(hass, name, LULLABY_MAPPING, entry_id), + SAASWakeStatusSensor(hass, name, awake_states, sleep_states, awake_duration, sleep_duration, topic, entry_id) + ] + + 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/ar.json b/custom_components/saas/translations/ar.json new file mode 100644 index 0000000..1e3a4a8 --- /dev/null +++ b/custom_components/saas/translations/ar.json @@ -0,0 +1,75 @@ +{ + "title": "SAAS - حالة Sleep As Android", + "config": { + "step": { + "user": { + "title": "التكوين لـ SAAS - حالة Sleep As Android", + "description": "تكوين الإعدادات الأساسية لتكامل SAAS.", + "data": { + "name": "الاسم للمستشعر", + "topic_template": "موضوع MQTT لأحداث Sleep As Android", + "qos": "جودة الخدمة (QoS) لـ MQTT", + "awake_duration": "مدة اليقظة", + "sleep_duration": "مدة النوم", + "awake_states": "حالات اليقظة*", + "sleep_states": "حالات النوم*" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "إعدادات تكامل SAAS", + "description": "تكوين الإعدادات الأساسية لتكامل SAAS.", + "data": { + "name": "الاسم للمستشعر", + "topic_template": "موضوع MQTT (من Sleep As Android)", + "qos": "جودة الخدمة (QoS) لـ MQTT", + "awake_duration": "مدة اليقظة", + "sleep_duration": "مدة النوم", + "awake_states": "حالات اليقظة*", + "sleep_states": "حالات النوم*" + } + } + } + }, + "entity": { + "sensor": { + "state": { + "Unknown": "غير معروف", + "Sleep Tracking Started": "بدأ تتبع النوم", + "Sleep Tracking Stopped": "توقف تتبع النوم", + "Sleep Tracking Paused": "تم إيقاف تتبع النوم مؤقتًا", + "Sleep Tracking Resumed": "استأنف تتبع النوم", + "Alarm Snoozed": "تأجيل الإنذار", + "Snooze Canceled": "تم إلغاء التأجيل", + "Time for Bed": "وقت النوم", + "Alarm Alert Started": "بدأ تنبيه الإنذار", + "Alarm Dismissed": "تم تجاهل الإنذار", + "Skip Next Alarm": "تخطي الإنذار التالي", + "Show Skip Next Alarm": "إظهار تخطي الإنذار التالي", + "REM": "REM", + "Smart Period": "الفترة الذكية", + "Before Smart Period": "قبل الفترة الذكية", + "Lullaby Start": "بداية التهليل", + "Lullaby Stop": "توقف التهليل", + "Lullaby Volume Down": "خفض صوت التهليل", + "Deep Sleep": "نوم عميق", + "Light Sleep": "نوم خفيف", + "Awake": "مستيقظ", + "Not Awake": "غير مستيقظ", + "Apnea Alarm": "إنذار الأنابيب", + "Antisnoring": "مضاد للشخير", + "Before Alarm": "قبل الإنذار", + "Snore Detected": "تم اكتشاف الشخير", + "Talk Detected": "تم اكتشاف الكلام", + "Cough Detected": "تم اكتشاف السعال", + "Baby Cry Detected": "تم اكتشاف بكاء الطفل", + "Laugh Detected": "تم اكتشاف الضحك", + "Alarm Rescheduled": "تم إعادة جدولة الإنذار", + "None": "لا شيء" + } + } + } +} \ No newline at end of file diff --git a/custom_components/saas/translations/de.json b/custom_components/saas/translations/de.json new file mode 100644 index 0000000..dea54ce --- /dev/null +++ b/custom_components/saas/translations/de.json @@ -0,0 +1,75 @@ +{ + "title": "SAAS - Sleep As Android Status", + "config": { + "step": { + "user": { + "title": "Konfiguration für SAAS - Sleep As Android Status", + "description": "Konfigurieren Sie die grundlegenden Einstellungen für die SAAS-Integration.", + "data": { + "name": "Name für Sensor", + "topic_template": "MQTT-Thema für Sleep As Android Ereignisse", + "qos": "Quality of Service (QoS) für MQTT", + "awake_duration": "Wachdauer", + "sleep_duration": "Schlafdauer", + "awake_states": "Wachzustände*", + "sleep_states": "Schlafzustände*" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "SAAS-Integrationseinstellungen", + "description": "Konfigurieren Sie die grundlegenden Einstellungen für die SAAS-Integration.", + "data": { + "name": "Name für Sensor", + "topic_template": "MQTT-Thema (von Sleep As Android)", + "qos": "MQTT Quality of Service (QoS)", + "awake_duration": "Wachdauer", + "sleep_duration": "Schlafdauer", + "awake_states": "Wachzustände*", + "sleep_states": "Schlafzustände*" + } + } + } + }, + "entity": { + "sensor": { + "state": { + "Unknown": "Unbekannt", + "Sleep Tracking Started": "Schlafüberwachung Gestartet", + "Sleep Tracking Stopped": "Schlafüberwachung Gestoppt", + "Sleep Tracking Paused": "Schlafüberwachung Pausiert", + "Sleep Tracking Resumed": "Schlafüberwachung Fortgesetzt", + "Alarm Snoozed": "Alarm Snoozed", + "Snooze Canceled": "Snooze Abgebrochen", + "Time for Bed": "Zeit fürs Bett", + "Alarm Alert Started": "Alarmalarm Gestartet", + "Alarm Dismissed": "Alarm Abgewiesen", + "Skip Next Alarm": "Nächsten Alarm Überspringen", + "Show Skip Next Alarm": "Zeige 'Nächsten Alarm Überspringen'", + "REM": "REM", + "Smart Period": "Intelligenter Zeitraum", + "Before Smart Period": "Vor Intelligenter Zeitraum", + "Lullaby Start": "Wiegenlied Start", + "Lullaby Stop": "Wiegenlied Stop", + "Lullaby Volume Down": "Wiegenlied Lautstärke Runter", + "Deep Sleep": "Tiefschlaf", + "Light Sleep": "Leichter Schlaf", + "Awake": "Wach", + "Not Awake": "Nicht Wach", + "Apnea Alarm": "Apnoe Alarm", + "Antisnoring": "Antischnarchen", + "Before Alarm": "Vor Alarm", + "Snore Detected": "Schnarchen Erkannt", + "Talk Detected": "Gespräch Erkannt", + "Cough Detected": "Husten Erkannt", + "Baby Cry Detected": "Babyweinen Erkannt", + "Laugh Detected": "Lachen Erkannt", + "Alarm Rescheduled": "Alarm Neu Geplant", + "None": "Keine" + } + } + } +} \ 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..630e021 --- /dev/null +++ b/custom_components/saas/translations/en.json @@ -0,0 +1,75 @@ +{ + "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*" + } + } + } + }, + "entity": { + "sensor": { + "state": { + "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 Snoozed": "Alarm Snoozed", + "Snooze Canceled": "Snooze Canceled", + "Time for Bed": "Time for Bed", + "Alarm Alert Started": "Alarm Alert Started", + "Alarm Dismissed": "Alarm Dismissed", + "Skip Next Alarm": "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", + "Snore Detected": "Snore Detected", + "Talk Detected": "Talk Detected", + "Cough Detected": "Cough Detected", + "Baby Cry Detected": "Baby Cry Detected", + "Laugh Detected": "Laugh Detected", + "Alarm Rescheduled": "Alarm Rescheduled", + "None": "None" + } + } + } +} \ No newline at end of file diff --git a/custom_components/saas/translations/es.json b/custom_components/saas/translations/es.json new file mode 100644 index 0000000..c600e23 --- /dev/null +++ b/custom_components/saas/translations/es.json @@ -0,0 +1,75 @@ +{ + "title": "SAAS - Estado de Sleep As Android", + "config": { + "step": { + "user": { + "title": "Configuración para SAAS - Estado de Sleep As Android", + "description": "Configura los ajustes básicos para la integración de SAAS.", + "data": { + "name": "Nombre para el Sensor", + "topic_template": "Tema MQTT para Eventos de Sleep As Android", + "qos": "Calidad de Servicio (QoS) para MQTT", + "awake_duration": "Duración Despierto", + "sleep_duration": "Duración Durmiendo", + "awake_states": "Estados Despierto*", + "sleep_states": "Estados Durmiendo*" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "Configuración de Integración SAAS", + "description": "Configura los ajustes básicos para la integración de SAAS.", + "data": { + "name": "Nombre para el Sensor", + "topic_template": "Tema MQTT (de Sleep As Android)", + "qos": "Calidad de Servicio (QoS) de MQTT", + "awake_duration": "Duración Despierto", + "sleep_duration": "Duración Durmiendo", + "awake_states": "Estados Despierto*", + "sleep_states": "Estados Durmiendo*" + } + } + } + }, + "entity": { + "sensor": { + "state": { + "Unknown": "Desconocido", + "Sleep Tracking Started": "Seguimiento del Sueño Iniciado", + "Sleep Tracking Stopped": "Seguimiento del Sueño Detenido", + "Sleep Tracking Paused": "Seguimiento del Sueño Pausado", + "Sleep Tracking Resumed": "Seguimiento del Sueño Reanudado", + "Alarm Snoozed": "Alarma en Snooze", + "Snooze Canceled": "Snooze Cancelado", + "Time for Bed": "Hora de Dormir", + "Alarm Alert Started": "Alerta de Alarma Iniciada", + "Alarm Dismissed": "Alarma Desactivada", + "Skip Next Alarm": "Saltar Próxima Alarma", + "Show Skip Next Alarm": "Mostrar Saltar Próxima Alarma", + "REM": "REM", + "Smart Period": "Periodo Inteligente", + "Before Smart Period": "Antes del Periodo Inteligente", + "Lullaby Start": "Inicio de Canción de Cuna", + "Lullaby Stop": "Fin de Canción de Cuna", + "Lullaby Volume Down": "Bajar Volumen de Canción de Cuna", + "Deep Sleep": "Sueño Profundo", + "Light Sleep": "Sueño Ligero", + "Awake": "Despierto", + "Not Awake": "No Despierto", + "Apnea Alarm": "Alarma de Apnea", + "Antisnoring": "Antirronquidos", + "Before Alarm": "Antes de la Alarma", + "Snore Detected": "Ronquido Detectado", + "Talk Detected": "Habla Detectada", + "Cough Detected": "Tos Detectada", + "Baby Cry Detected": "Llanto de Bebé Detectado", + "Laugh Detected": "Risa Detectada", + "Alarm Rescheduled": "Alarma Re-programada", + "None": "Ninguno" + } + } + } +} \ No newline at end of file diff --git a/custom_components/saas/translations/fr.json b/custom_components/saas/translations/fr.json new file mode 100644 index 0000000..30becb7 --- /dev/null +++ b/custom_components/saas/translations/fr.json @@ -0,0 +1,75 @@ +{ + "title": "SAAS - Statut de Sleep As Android", + "config": { + "step": { + "user": { + "title": "Configuration pour SAAS - Statut de Sleep As Android", + "description": "Configurez les paramètres de base pour l'intégration de SAAS.", + "data": { + "name": "Nom pour le Capteur", + "topic_template": "Sujet MQTT pour les Événements de Sleep As Android", + "qos": "Qualité de Service (QoS) pour MQTT", + "awake_duration": "Durée Éveillé", + "sleep_duration": "Durée Endormi", + "awake_states": "États Éveillé*", + "sleep_states": "États Endormi*" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "Paramètres d'Intégration SAAS", + "description": "Configurez les paramètres de base pour l'intégration de SAAS.", + "data": { + "name": "Nom pour le Capteur", + "topic_template": "Sujet MQTT (de Sleep As Android)", + "qos": "Qualité de Service (QoS) de MQTT", + "awake_duration": "Durée Éveillé", + "sleep_duration": "Durée Endormi", + "awake_states": "États Éveillé*", + "sleep_states": "États Endormi*" + } + } + } + }, + "entity": { + "sensor": { + "state": { + "Unknown": "Inconnu", + "Sleep Tracking Started": "Suivi du Sommeil Commencé", + "Sleep Tracking Stopped": "Suivi du Sommeil Arrêté", + "Sleep Tracking Paused": "Suivi du Sommeil en Pause", + "Sleep Tracking Resumed": "Suivi du Sommeil Repris", + "Alarm Snoozed": "Alarme en Snooze", + "Snooze Canceled": "Snooze Annulé", + "Time for Bed": "Heure du Coucher", + "Alarm Alert Started": "Alerte d'Alarme Commencée", + "Alarm Dismissed": "Alarme Dismissed", + "Skip Next Alarm": "Passer la Prochaine Alarme", + "Show Skip Next Alarm": "Montrer Passer la Prochaine Alarme", + "REM": "REM", + "Smart Period": "Période Intelligente", + "Before Smart Period": "Avant la Période Intelligente", + "Lullaby Start": "Début de Berceuse", + "Lullaby Stop": "Fin de Berceuse", + "Lullaby Volume Down": "Diminuer le Volume de la Berceuse", + "Deep Sleep": "Sommeil Profond", + "Light Sleep": "Sommeil Léger", + "Awake": "Éveillé", + "Not Awake": "Pas Éveillé", + "Apnea Alarm": "Alarme d'Apnée", + "Antisnoring": "Anti-ronflement", + "Before Alarm": "Avant l'Alarme", + "Snore Detected": "Ronflement Détecté", + "Talk Detected": "Parole Détectée", + "Cough Detected": "Toux Détectée", + "Baby Cry Detected": "Pleurs de Bébé Détectés", + "Laugh Detected": "Rire Détecté", + "Alarm Rescheduled": "Alarme Re-programmée", + "None": "Aucun" + } + } + } +} \ No newline at end of file diff --git a/custom_components/saas/translations/it.json b/custom_components/saas/translations/it.json new file mode 100644 index 0000000..c0e05d0 --- /dev/null +++ b/custom_components/saas/translations/it.json @@ -0,0 +1,75 @@ +{ + "title": "SAAS - Stato di Sleep As Android", + "config": { + "step": { + "user": { + "title": "Configurazione per SAAS - Stato di Sleep As Android", + "description": "Configura le impostazioni di base per l'integrazione SAAS.", + "data": { + "name": "Nome per Sensore", + "topic_template": "Topic MQTT per Eventi Sleep As Android", + "qos": "Quality of Service (QoS) per MQTT", + "awake_duration": "Durata Sveglio", + "sleep_duration": "Durata Addormentato", + "awake_states": "Stati di Veglia*", + "sleep_states": "Stati di Sonno*" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "Impostazioni di Integrazione SAAS", + "description": "Configura le impostazioni di base per l'integrazione SAAS.", + "data": { + "name": "Nome per Sensore", + "topic_template": "Topic MQTT (da Sleep As Android)", + "qos": "Quality of Service (QoS) MQTT", + "awake_duration": "Durata Sveglio", + "sleep_duration": "Durata Addormentato", + "awake_states": "Stati di Veglia*", + "sleep_states": "Stati di Sonno*" + } + } + } + }, + "entity": { + "sensor": { + "state": { + "Unknown": "Sconosciuto", + "Sleep Tracking Started": "Inizio Monitoraggio del Sonno", + "Sleep Tracking Stopped": "Fine Monitoraggio del Sonno", + "Sleep Tracking Paused": "Monitoraggio del Sonno in Pausa", + "Sleep Tracking Resumed": "Ripresa Monitoraggio del Sonno", + "Alarm Snoozed": "Allarme Posticipato", + "Snooze Canceled": "Posticipo Annullato", + "Time for Bed": "Ora di Andare a Letto", + "Alarm Alert Started": "Inizio Allarme", + "Alarm Dismissed": "Allarme Disattivato", + "Skip Next Alarm": "Salta il Prossimo Allarme", + "Show Skip Next Alarm": "Mostra 'Salta il Prossimo Allarme'", + "REM": "REM", + "Smart Period": "Periodo Smart", + "Before Smart Period": "Prima del Periodo Smart", + "Lullaby Start": "Inizio Ninna Nanna", + "Lullaby Stop": "Fine Ninna Nanna", + "Lullaby Volume Down": "Diminuzione Volume Ninna Nanna", + "Deep Sleep": "Sonno Profondo", + "Light Sleep": "Sonno Leggero", + "Awake": "Sveglio", + "Not Awake": "Non Sveglio", + "Apnea Alarm": "Allarme Apnea", + "Antisnoring": "Antirussamento", + "Before Alarm": "Prima dell'Allarme", + "Snore Detected": "Russamento Rilevato", + "Talk Detected": "Conversazione Rilevata", + "Cough Detected": "Tosse Rilevata", + "Baby Cry Detected": "Pianto del Bambino Rilevato", + "Laugh Detected": "Risata Rilevata", + "Alarm Rescheduled": "Allarme Riprogrammato", + "None": "Nessuno" + } + } + } +} \ No newline at end of file diff --git a/custom_components/saas/translations/ja.json b/custom_components/saas/translations/ja.json new file mode 100644 index 0000000..52dcaa6 --- /dev/null +++ b/custom_components/saas/translations/ja.json @@ -0,0 +1,75 @@ +{ + "title": "SAAS - 睡眠如 Android 狀態", + "config": { + "step": { + "user": { + "title": "SAAS - 睡眠如 Android 狀態配置", + "description": "配置 SAAS 整合的基本設置。", + "data": { + "name": "感應器名稱", + "topic_template": "睡眠如 Android 事件的 MQTT 主題", + "qos": "MQTT 的服務質量 (QoS)", + "awake_duration": "清醒持續時間", + "sleep_duration": "睡眠持續時間", + "awake_states": "清醒狀態*", + "sleep_states": "睡眠狀態*" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "SAAS 整合設置", + "description": "配置 SAAS 整合的基本設置。", + "data": { + "name": "感應器名稱", + "topic_template": "MQTT 主題(來自睡眠如 Android)", + "qos": "MQTT 服務質量 (QoS)", + "awake_duration": "清醒持續時間", + "sleep_duration": "睡眠持續時間", + "awake_states": "清醒狀態*", + "sleep_states": "睡眠狀態*" + } + } + } + }, + "entity": { + "sensor": { + "state": { + "Unknown": "未知", + "Sleep Tracking Started": "開始追蹤睡眠", + "Sleep Tracking Stopped": "停止追蹤睡眠", + "Sleep Tracking Paused": "暫停追蹤睡眠", + "Sleep Tracking Resumed": "恢復追蹤睡眠", + "Alarm Snoozed": "鬧鐘貪睡", + "Snooze Canceled": "取消貪睡", + "Time for Bed": "該睡覺了", + "Alarm Alert Started": "鬧鐘警報開始", + "Alarm Dismissed": "鬧鐘解除", + "Skip Next Alarm": "跳過下一個鬧鐘", + "Show Skip Next Alarm": "顯示跳過下一個鬧鐘", + "REM": "REM", + "Smart Period": "智能期間", + "Before Smart Period": "在智能期間之前", + "Lullaby Start": "搖籃曲開始", + "Lullaby Stop": "搖籃曲停止", + "Lullaby Volume Down": "搖籃曲音量下降", + "Deep Sleep": "深度睡眠", + "Light Sleep": "淺度睡眠", + "Awake": "清醒", + "Not Awake": "未清醒", + "Apnea Alarm": "呼吸暫停警報", + "Antisnoring": "防打鼾", + "Before Alarm": "在鬧鐘之前", + "Snore Detected": "檢測到打鼾", + "Talk Detected": "檢測到說話", + "Cough Detected": "檢測到咳嗽", + "Baby Cry Detected": "檢測到嬰兒哭聲", + "Laugh Detected": "檢測到笑聲", + "Alarm Rescheduled": "鬧鐘重新安排", + "None": "無" + } + } + } +} \ No newline at end of file diff --git a/custom_components/saas/translations/ko.json b/custom_components/saas/translations/ko.json new file mode 100644 index 0000000..9d71829 --- /dev/null +++ b/custom_components/saas/translations/ko.json @@ -0,0 +1,75 @@ +{ + "title": "SAAS - Sleep As Android 상태", + "config": { + "step": { + "user": { + "title": "SAAS - Sleep As Android 상태 설정", + "description": "SAAS 통합의 기본 설정을 구성합니다.", + "data": { + "name": "센서 이름", + "topic_template": "Sleep As Android 이벤트의 MQTT 주제", + "qos": "MQTT의 서비스 품질(QoS)", + "awake_duration": "깨어 있는 시간", + "sleep_duration": "수면 시간", + "awake_states": "깨어 있는 상태*", + "sleep_states": "수면 상태*" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "SAAS 통합 설정", + "description": "SAAS 통합의 기본 설정을 구성합니다.", + "data": { + "name": "센서 이름", + "topic_template": "Sleep As Android에서의 MQTT 주제", + "qos": "MQTT의 서비스 품질(QoS)", + "awake_duration": "깨어 있는 시간", + "sleep_duration": "수면 시간", + "awake_states": "깨어 있는 상태*", + "sleep_states": "수면 상태*" + } + } + } + }, + "entity": { + "sensor": { + "state": { + "Unknown": "알 수 없음", + "Sleep Tracking Started": "수면 추적 시작", + "Sleep Tracking Stopped": "수면 추적 중지", + "Sleep Tracking Paused": "수면 추적 일시 중지", + "Sleep Tracking Resumed": "수면 추적 재개", + "Alarm Snoozed": "알람 스누즈", + "Snooze Canceled": "스누즈 취소", + "Time for Bed": "침대 시간", + "Alarm Alert Started": "알람 경고 시작", + "Alarm Dismissed": "알람 해제", + "Skip Next Alarm": "다음 알람 건너뛰기", + "Show Skip Next Alarm": "다음 알람 건너뛰기 표시", + "REM": "REM", + "Smart Period": "스마트 기간", + "Before Smart Period": "스마트 기간 이전", + "Lullaby Start": "자장가 시작", + "Lullaby Stop": "자장가 중지", + "Lullaby Volume Down": "자장가 볼륨 다운", + "Deep Sleep": "깊은 잠", + "Light Sleep": "얕은 잠", + "Awake": "깨어 있음", + "Not Awake": "깨어 있지 않음", + "Apnea Alarm": "무호흡 알람", + "Antisnoring": "코골이 방지", + "Before Alarm": "알람 이전", + "Snore Detected": "코골이 감지됨", + "Talk Detected": "대화 감지됨", + "Cough Detected": "기침 감지됨", + "Baby Cry Detected": "아기 울음소리 감지됨", + "Laugh Detected": "웃음소리 감지됨", + "Alarm Rescheduled": "알람 재스케줄", + "None": "없음" + } + } + } + } \ No newline at end of file diff --git a/custom_components/saas/translations/nl.json b/custom_components/saas/translations/nl.json new file mode 100644 index 0000000..2164d9d --- /dev/null +++ b/custom_components/saas/translations/nl.json @@ -0,0 +1,75 @@ +{ + "title": "Configuratie voor SAAS - Sleep As Android Status", + "config": { + "step": { + "user": { + "title": "Configuratie voor SAAS - Sleep As Android Status", + "description": "Configureer de basisinstellingen voor de SAAS-integratie.", + "data": { + "name": "Naam voor Sensor", + "topic_template": "MQTT-onderwerp voor Sleep As Android Gebeurtenissen", + "qos": "Quality of Service (QoS) voor MQTT", + "awake_duration": "Wakker Duur", + "sleep_duration": "Slaapduur", + "awake_states": "Wakker Staten*", + "sleep_states": "Slaap Staten*" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "SAAS-Integratie-Instellingen", + "description": "Configureer de basisinstellingen voor de SAAS-integratie.", + "data": { + "name": "Naam voor Sensor", + "topic_template": "MQTT-Onderwerp (van Sleep As Android)", + "qos": "MQTT Quality of Service (QoS)", + "awake_duration": "Wakker Duur", + "sleep_duration": "Slaapduur", + "awake_states": "Wakker Staten*", + "sleep_states": "Slaap Staten*" + } + } + } + }, + "entity": { + "sensor": { + "state": { + "Unknown": "Onbekend", + "Sleep Tracking Started": "Slaaptracking Gestart", + "Sleep Tracking Stopped": "Slaaptracking Gestopt", + "Sleep Tracking Paused": "Slaaptracking Gepauzeerd", + "Sleep Tracking Resumed": "Slaaptracking Hervat", + "Alarm Snoozed": "Alarm Gesnoozed", + "Snooze Canceled": "Snooze Geannuleerd", + "Time for Bed": "Tijd om te Gaan Slapen", + "Alarm Alert Started": "Alarm Alert Gestart", + "Alarm Dismissed": "Alarm Afgeslagen", + "Skip Next Alarm": "Volgende Alarm Overslaan", + "Show Skip Next Alarm": "Toon 'Volgende Alarm Overslaan'", + "REM": "REM", + "Smart Period": "Slimme Periode", + "Before Smart Period": "Voor Slimme Periode", + "Lullaby Start": "Slaapliedje Start", + "Lullaby Stop": "Slaapliedje Stop", + "Lullaby Volume Down": "Slaapliedje Volume Omlaag", + "Deep Sleep": "Diepe Slaap", + "Light Sleep": "Lichte Slaap", + "Awake": "Wakker", + "Not Awake": "Niet Wakker", + "Apnea Alarm": "Apneu Alarm", + "Antisnoring": "Antisnurken", + "Before Alarm": "Voor Alarm", + "Snore Detected": "Snurken Gedetecteerd", + "Talk Detected": "Gesprek Gedetecteerd", + "Cough Detected": "Hoesten Gedetecteerd", + "Baby Cry Detected": "Baby Huilen Gedetecteerd", + "Laugh Detected": "Lachen Gedetecteerd", + "Alarm Rescheduled": "Alarm Herpland", + "None": "Geen" + } + } + } +} \ No newline at end of file diff --git a/custom_components/saas/translations/pl.json b/custom_components/saas/translations/pl.json new file mode 100644 index 0000000..7cf84d9 --- /dev/null +++ b/custom_components/saas/translations/pl.json @@ -0,0 +1,75 @@ +{ + "title": "SAAS - Status Sleep As Android", + "config": { + "step": { + "user": { + "title": "Konfiguracja dla SAAS - Status Sleep As Android", + "description": "Konfiguracja podstawowych ustawień dla integracji SAAS.", + "data": { + "name": "Nazwa dla czujnika", + "topic_template": "Temat MQTT dla zdarzeń Sleep As Android", + "qos": "Jakość usługi (QoS) dla MQTT", + "awake_duration": "Czas czuwania", + "sleep_duration": "Czas snu", + "awake_states": "Stany czuwania*", + "sleep_states": "Stany snu*" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "Ustawienia integracji SAAS", + "description": "Konfiguracja podstawowych ustawień dla integracji SAAS.", + "data": { + "name": "Nazwa dla czujnika", + "topic_template": "Temat MQTT (od Sleep As Android)", + "qos": "Jakość usługi (QoS) MQTT", + "awake_duration": "Czas czuwania", + "sleep_duration": "Czas snu", + "awake_states": "Stany czuwania*", + "sleep_states": "Stany snu*" + } + } + } + }, + "entity": { + "sensor": { + "state": { + "Unknown": "Nieznane", + "Sleep Tracking Started": "Rozpoczęto śledzenie snu", + "Sleep Tracking Stopped": "Zatrzymano śledzenie snu", + "Sleep Tracking Paused": "Wstrzymano śledzenie snu", + "Sleep Tracking Resumed": "Wznowiono śledzenie snu", + "Alarm Snoozed": "Alarm w drzemce", + "Snooze Canceled": "Drzemka anulowana", + "Time for Bed": "Czas na sen", + "Alarm Alert Started": "Rozpoczęto alarm", + "Alarm Dismissed": "Alarm wyłączony", + "Skip Next Alarm": "Pomiń następny alarm", + "Show Skip Next Alarm": "Pokaż pomiń następny alarm", + "REM": "REM", + "Smart Period": "Inteligentny okres", + "Before Smart Period": "Przed inteligentnym okresem", + "Lullaby Start": "Rozpoczęcie kołysanki", + "Lullaby Stop": "Zakończenie kołysanki", + "Lullaby Volume Down": "Zmniejsz głośność kołysanki", + "Deep Sleep": "Głęboki sen", + "Light Sleep": "Lekki sen", + "Awake": "Czuwanie", + "Not Awake": "Nie czuwa", + "Apnea Alarm": "Alarm apnei", + "Antisnoring": "Przeciw chrapaniu", + "Before Alarm": "Przed alarmem", + "Snore Detected": "Wykryto chrapanie", + "Talk Detected": "Wykryto mówienie", + "Cough Detected": "Wykryto kaszel", + "Baby Cry Detected": "Wykryto płacz dziecka", + "Laugh Detected": "Wykryto śmiech", + "Alarm Rescheduled": "Alarm został przesunięty", + "None": "Brak" + } + } + } +} \ No newline at end of file diff --git a/custom_components/saas/translations/pt.json b/custom_components/saas/translations/pt.json new file mode 100644 index 0000000..592e132 --- /dev/null +++ b/custom_components/saas/translations/pt.json @@ -0,0 +1,75 @@ +{ + "title": "SAAS - Status do Sleep As Android", + "config": { + "step": { + "user": { + "title": "Configuração para SAAS - Status do Sleep As Android", + "description": "Configure as configurações básicas para a integração SAAS.", + "data": { + "name": "Nome para Sensor", + "topic_template": "Tópico MQTT para Eventos do Sleep As Android", + "qos": "Qualidade de Serviço (QoS) para MQTT", + "awake_duration": "Duração Acordado", + "sleep_duration": "Duração Adormecido", + "awake_states": "Estados Acordado*", + "sleep_states": "Estados Adormecido*" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "Configurações de Integração SAAS", + "description": "Configure as configurações básicas para a integração SAAS.", + "data": { + "name": "Nome para Sensor", + "topic_template": "Tópico MQTT (do Sleep As Android)", + "qos": "Qualidade de Serviço (QoS) MQTT", + "awake_duration": "Duração Acordado", + "sleep_duration": "Duração Adormecido", + "awake_states": "Estados Acordado*", + "sleep_states": "Estados Adormecido*" + } + } + } + }, + "entity": { + "sensor": { + "state": { + "Unknown": "Desconhecido", + "Sleep Tracking Started": "Monitoramento do Sono Iniciado", + "Sleep Tracking Stopped": "Monitoramento do Sono Parado", + "Sleep Tracking Paused": "Monitoramento do Sono Pausado", + "Sleep Tracking Resumed": "Monitoramento do Sono Retomado", + "Alarm Snoozed": "Alarme Adiado", + "Snooze Canceled": "Adiamento Cancelado", + "Time for Bed": "Hora de Dormir", + "Alarm Alert Started": "Alerta de Alarme Iniciado", + "Alarm Dismissed": "Alarme Desligado", + "Skip Next Alarm": "Pular Próximo Alarme", + "Show Skip Next Alarm": "Mostrar 'Pular Próximo Alarme'", + "REM": "REM", + "Smart Period": "Período Inteligente", + "Before Smart Period": "Antes do Período Inteligente", + "Lullaby Start": "Início de Canção de Ninar", + "Lullaby Stop": "Fim de Canção de Ninar", + "Lullaby Volume Down": "Diminuir Volume da Canção de Ninar", + "Deep Sleep": "Sono Profundo", + "Light Sleep": "Sono Leve", + "Awake": "Acordado", + "Not Awake": "Não Acordado", + "Apnea Alarm": "Alarme de Apneia", + "Antisnoring": "Antirronco", + "Before Alarm": "Antes do Alarme", + "Snore Detected": "Ronco Detectado", + "Talk Detected": "Conversa Detectada", + "Cough Detected": "Tosse Detectada", + "Baby Cry Detected": "Choro de Bebê Detectado", + "Laugh Detected": "Risada Detectada", + "Alarm Rescheduled": "Alarme Remarcado", + "None": "Nenhum" + } + } + } + } \ No newline at end of file diff --git a/custom_components/saas/translations/pue.json b/custom_components/saas/translations/pue.json new file mode 100644 index 0000000..b4fac33 --- /dev/null +++ b/custom_components/saas/translations/pue.json @@ -0,0 +1,75 @@ +{ + "title": "SAAS - 睡眠如 Android 狀態", + "config": { + "step": { + "user": { + "title": "SAAS - 睡眠如 Android 狀態配置", + "description": "配置 SAAS 整合的基本設置。", + "data": { + "name": "感應器名稱", + "topic_template": "睡眠如 Android 事件的 MQTT 主題", + "qos": "MQTT 的服務質量 (QoS)", + "awake_duration": "清醒持續時間", + "sleep_duration": "睡眠持續時間", + "awake_states": "清醒狀態*", + "sleep_states": "睡眠狀態*" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "SAAS 整合設置", + "description": "配置 SAAS 整合的基本設置。", + "data": { + "name": "感應器名稱", + "topic_template": "MQTT 主題(來自睡眠如 Android)", + "qos": "MQTT 服務質量 (QoS)", + "awake_duration": "清醒持續時間", + "sleep_duration": "睡眠持續時間", + "awake_states": "清醒狀態*", + "sleep_states": "睡眠狀態*" + } + } + } + }, + "entity": { + "sensor": { + "state": { + "Unknown": "未知", + "Sleep Tracking Started": "開始追蹤睡眠", + "Sleep Tracking Stopped": "停止追蹤睡眠", + "Sleep Tracking Paused": "暫停追蹤睡眠", + "Sleep Tracking Resumed": "恢復追蹤睡眠", + "Alarm Snoozed": "鬧鐘貪睡", + "Snooze Canceled": "取消貪睡", + "Time for Bed": "該睡覺了", + "Alarm Alert Started": "鬧鐘警報開始", + "Alarm Dismissed": "鬧鐘解除", + "Skip Next Alarm": "跳過下一個鬧鐘", + "Show Skip Next Alarm": "顯示跳過下一個鬧鐘", + "REM": "REM", + "Smart Period": "智能期間", + "Before Smart Period": "在智能期間之前", + "Lullaby Start": "搖籃曲開始", + "Lullaby Stop": "搖籃曲停止", + "Lullaby Volume Down": "搖籃曲音量下降", + "Deep Sleep": "深度睡眠", + "Light Sleep": "淺度睡眠", + "Awake": "清醒", + "Not Awake": "未清醒", + "Apnea Alarm": "呼吸暫停警報", + "Antisnoring": "防打鼾", + "Before Alarm": "在鬧鐘之前", + "Snore Detected": "檢測到打鼾", + "Talk Detected": "檢測到說話", + "Cough Detected": "檢測到咳嗽", + "Baby Cry Detected": "檢測到嬰兒哭聲", + "Laugh Detected": "檢測到笑聲", + "Alarm Rescheduled": "鬧鐘重新安排", + "None": "無" + } + } + } + } \ No newline at end of file diff --git a/custom_components/saas/translations/ru.json b/custom_components/saas/translations/ru.json new file mode 100644 index 0000000..fbbed4a --- /dev/null +++ b/custom_components/saas/translations/ru.json @@ -0,0 +1,75 @@ +{ + "title": "SAAS - Статус Sleep As Android", + "config": { + "step": { + "user": { + "title": "Конфигурация для SAAS - Статус Sleep As Android", + "description": "Настройка основных параметров для интеграции SAAS.", + "data": { + "name": "Имя для датчика", + "topic_template": "Тема MQTT для событий Sleep As Android", + "qos": "Качество обслуживания (QoS) для MQTT", + "awake_duration": "Продолжительность бодрствования", + "sleep_duration": "Продолжительность сна", + "awake_states": "Состояния бодрствования*", + "sleep_states": "Состояния сна*" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "Настройки интеграции SAAS", + "description": "Настройка основных параметров для интеграции SAAS.", + "data": { + "name": "Имя для датчика", + "topic_template": "Тема MQTT (от Sleep As Android)", + "qos": "Качество обслуживания (QoS) MQTT", + "awake_duration": "Продолжительность бодрствования", + "sleep_duration": "Продолжительность сна", + "awake_states": "Состояния бодрствования*", + "sleep_states": "Состояния сна*" + } + } + } + }, + "entity": { + "sensor": { + "state": { + "Unknown": "Неизвестно", + "Sleep Tracking Started": "Начато отслеживание сна", + "Sleep Tracking Stopped": "Отслеживание сна остановлено", + "Sleep Tracking Paused": "Отслеживание сна приостановлено", + "Sleep Tracking Resumed": "Отслеживание сна возобновлено", + "Alarm Snoozed": "Будильник в режиме паузы", + "Snooze Canceled": "Пауза отменена", + "Time for Bed": "Время спать", + "Alarm Alert Started": "Начало сигнала будильника", + "Alarm Dismissed": "Будильник отключен", + "Skip Next Alarm": "Пропустить следующий будильник", + "Show Skip Next Alarm": "Показать пропуск следующего будильника", + "REM": "REM", + "Smart Period": "Умный период", + "Before Smart Period": "До умного периода", + "Lullaby Start": "Начало колыбельной", + "Lullaby Stop": "Конец колыбельной", + "Lullaby Volume Down": "Уменьшить громкость колыбельной", + "Deep Sleep": "Глубокий сон", + "Light Sleep": "Легкий сон", + "Awake": "Бодрствование", + "Not Awake": "Не бодрствует", + "Apnea Alarm": "Сигнал апноэ", + "Antisnoring": "Против храпа", + "Before Alarm": "До будильника", + "Snore Detected": "Обнаружен храп", + "Talk Detected": "Обнаружена речь", + "Cough Detected": "Обнаружен кашель", + "Baby Cry Detected": "Обнаружен плач ребенка", + "Laugh Detected": "Обнаружен смех", + "Alarm Rescheduled": "Будильник перенесен", + "None": "Нет" + } + } + } +} \ No newline at end of file diff --git a/custom_components/saas/translations/sv.json b/custom_components/saas/translations/sv.json new file mode 100644 index 0000000..5c6ed52 --- /dev/null +++ b/custom_components/saas/translations/sv.json @@ -0,0 +1,75 @@ +{ + "title": "SAAS - Sleep As Android Status", + "config": { + "step": { + "user": { + "title": "Konfiguration för SAAS - Sleep As Android Status", + "description": "Konfigurera de grundläggande inställningarna för SAAS-integrationen.", + "data": { + "name": "Namn för Sensor", + "topic_template": "MQTT Ämne för Sleep As Android Händelser", + "qos": "Kvalitet på Tjänst (QoS) för MQTT", + "awake_duration": "Vaken Varaktighet", + "sleep_duration": "Sovande Varaktighet", + "awake_states": "Vakna Tillstånd*", + "sleep_states": "Sovande Tillstånd*" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "SAAS Integrationsinställningar", + "description": "Konfigurera de grundläggande inställningarna för SAAS-integrationen.", + "data": { + "name": "Namn för Sensor", + "topic_template": "MQTT Ämne (från Sleep As Android)", + "qos": "MQTT Kvalitet på Tjänst (QoS)", + "awake_duration": "Vaken Varaktighet", + "sleep_duration": "Sovande Varaktighet", + "awake_states": "Vakna Tillstånd*", + "sleep_states": "Sovande Tillstånd*" + } + } + } + }, + "entity": { + "sensor": { + "state": { + "Unknown": "Okänd", + "Sleep Tracking Started": "Sömnmätning Startad", + "Sleep Tracking Stopped": "Sömnmätning Stoppad", + "Sleep Tracking Paused": "Sömnmätning Pausad", + "Sleep Tracking Resumed": "Sömnmätning Återupptagen", + "Alarm Snoozed": "Alarm Snoozed", + "Snooze Canceled": "Snooze Avbruten", + "Time for Bed": "Dags att Gå och Lägga Sig", + "Alarm Alert Started": "Alarmvarning Startad", + "Alarm Dismissed": "Alarm Avvisat", + "Skip Next Alarm": "Hoppa över Nästa Alarm", + "Show Skip Next Alarm": "Visa 'Hoppa över Nästa Alarm'", + "REM": "REM", + "Smart Period": "Smart Period", + "Before Smart Period": "Före Smart Period", + "Lullaby Start": "Vaggvisa Start", + "Lullaby Stop": "Vaggvisa Stopp", + "Lullaby Volume Down": "Vaggvisa Volym Ner", + "Deep Sleep": "Djup Sömn", + "Light Sleep": "Lätt Sömn", + "Awake": "Vaken", + "Not Awake": "Inte Vaken", + "Apnea Alarm": "Apné Alarm", + "Antisnoring": "Antisnarkning", + "Before Alarm": "Före Alarm", + "Snore Detected": "Snarkning Upptäckt", + "Talk Detected": "Prat Upptäckt", + "Cough Detected": "Hosta Upptäckt", + "Baby Cry Detected": "Bebisgråt Upptäckt", + "Laugh Detected": "Skratt Upptäckt", + "Alarm Rescheduled": "Alarm Omplanerat", + "None": "Ingen" + } + } + } +} \ No newline at end of file diff --git a/custom_components/saas/translations/zh.json b/custom_components/saas/translations/zh.json new file mode 100644 index 0000000..3ca0f23 --- /dev/null +++ b/custom_components/saas/translations/zh.json @@ -0,0 +1,75 @@ +{ + "title": "SAAS - Sleep As Android 状态", + "config": { + "step": { + "user": { + "title": "SAAS - Sleep As Android 状态配置", + "description": "配置 SAAS 集成的基本设置。", + "data": { + "name": "传感器名称", + "topic_template": "Sleep As Android 事件的 MQTT 主题", + "qos": "MQTT 的服务质量 (QoS)", + "awake_duration": "清醒持续时间", + "sleep_duration": "睡眠持续时间", + "awake_states": "清醒状态*", + "sleep_states": "睡眠状态*" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "SAAS 集成设置", + "description": "配置 SAAS 集成的基本设置。", + "data": { + "name": "传感器名称", + "topic_template": "Sleep As Android 的 MQTT 主题", + "qos": "MQTT 的服务质量 (QoS)", + "awake_duration": "清醒持续时间", + "sleep_duration": "睡眠持续时间", + "awake_states": "清醒状态*", + "sleep_states": "睡眠状态*" + } + } + } + }, + "entity": { + "sensor": { + "state": { + "Unknown": "未知", + "Sleep Tracking Started": "开始睡眠跟踪", + "Sleep Tracking Stopped": "停止睡眠跟踪", + "Sleep Tracking Paused": "暂停睡眠跟踪", + "Sleep Tracking Resumed": "恢复睡眠跟踪", + "Alarm Snoozed": "闹钟贪睡", + "Snooze Canceled": "取消贪睡", + "Time for Bed": "该睡觉了", + "Alarm Alert Started": "闹钟警报开始", + "Alarm Dismissed": "闹钟解除", + "Skip Next Alarm": "跳过下一个闹钟", + "Show Skip Next Alarm": "显示跳过下一个闹钟", + "REM": "REM", + "Smart Period": "智能周期", + "Before Smart Period": "在智能周期之前", + "Lullaby Start": "开始摇篮曲", + "Lullaby Stop": "停止摇篮曲", + "Lullaby Volume Down": "降低摇篮曲音量", + "Deep Sleep": "深度睡眠", + "Light Sleep": "浅睡", + "Awake": "清醒", + "Not Awake": "未清醒", + "Apnea Alarm": "呼吸暂停警报", + "Antisnoring": "防打鼾", + "Before Alarm": "在闹钟之前", + "Snore Detected": "检测到打鼾", + "Talk Detected": "检测到说话", + "Cough Detected": "检测到咳嗽", + "Baby Cry Detected": "检测到婴儿哭声", + "Laugh Detected": "检测到笑声", + "Alarm Rescheduled": "闹钟重新安排", + "None": "无" + } + } + } + } \ No newline at end of file