diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..273d2fb --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +--- +# github: theneweinstein +ko_fi: theneweinstein \ No newline at end of file diff --git a/README.md b/README.md index 3283160..3d20db1 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,61 @@ -# Somneo custom component -Home Assistant custom component for Philips Someo. This integration let's you control the light of the Somneo and reads the following sensors: temperature, humidity, luminance and noise. Furthermore, it provides the alarms set on your Somneo instance as binary sensors and provides a sensor with the first upcoming alarm. +# Somneo custom integration +Home Assistant custom integration to control a Philips Somneo device. This integration let's you control: + - The light and nightlight of the Somneo + - All the 16 available alarms (toggle, time, days, powerwake) + - Media player of the Somneo (FM radio or aux. input) + - Snooze or ignore alarm (buttons) + +Furthermore, it provides the following sensors: + - Ambient sensors (temperature, humidity, luminance and noise) + - Alarm status (on, off, snooze, wake-up) + - Next alarm # Installation You can install this custom component via HACS as a custom repository (https://hacs.xyz/docs/faq/custom_repositories/). Alternatively you can clone or copy the files into the somneo folder in the custom_components folder of HomeAssistant. # Configuration -Go to: https://my.home-assistant.io/redirect/config_flow_start/?domain=somneo +The Somneo should be automatically detected via SSDP. If not, you can also manually configure the Somneo: https://my.home-assistant.io/redirect/config_flow_start/?domain=somneo. -# Alarm Configuration -### With slider-entity-row from HACS` -Add a "manual" card into lovelace UI and copy paste the following code. It will create a card for the first Somneo Alarm (alarm0). +# Alarm UI configuration +Add a "manual" card into lovelace UI and copy paste the following code. It will create a card for the first Somneo Alarm (alarm0). Other cards can be created for other alarms (alarm1, alarm2, etc.) ``` type: entities entities: - entity: switch.somneo_alarm0 name: On/Off - - type: custom:slider-entity-row - entity: number.somneo_alarm0_hours - hide_state: false - name: Hours - - type: custom:slider-entity-row - entity: number.somneo_alarm0_minutes - hide_state: false - name: Minutes + - entity: time.somneo_alarm0_time + name: Time - entity: select.somneo_alarm0_days name: Days + - entity: switch.somneo_alarm0_powerwake + name: PowerWake + - entity: number.somneo_alarm0_powerwake_delay + name: PowerWake delay title: Alarm work -show_header_toggle: false ``` -Example Lovelace Slider +Example Lovelace -### Without slider-entity-row from HACS +# Custom alarm days +The select entity for the days only supports a limited set of days, namely weekdays, weekends, everyday and tomorrow. In case you want to select a different day for the alarm, you can use the text entity. The text contains a comma-seperated list (without white-spaces) of abbreviations of the day of the week (i.e. `mon,tue,wed,thu,fri,sat,sun`) or `tomorrow`. ``` type: entities entities: - entity: switch.somneo_alarm0 name: On/Off - - entity: number.somneo_alarm0_hours - name: Hours - - entity: number.somneo_alarm0_minutes - name: Minutes - - entity: select.somneo_alarm0_days + - entity: time.somneo_alarm0_time + name: Time + - entity: text.somneo_alarm0_days name: Days + - entity: switch.somneo_alarm0_powerwake + name: PowerWake + - entity: number.somneo_alarm0_powerwake_delay + name: PowerWake delay title: Alarm work -show_header_toggle: false ``` -Example Lovelace +Example Lovelace with custom days + # Services This component includes two services to adjust the wake-up light and sound settings. To adjust the light settings of an alarm you can call the following function: @@ -85,4 +93,4 @@ target: service: somneo.remove_alarm target: entity_id: switch.somneo_alarm0 -``` +``` \ No newline at end of file diff --git a/custom_components/somneo/__init__.py b/custom_components/somneo/__init__.py index 0709ff3..e038a79 100644 --- a/custom_components/somneo/__init__.py +++ b/custom_components/somneo/__init__.py @@ -26,6 +26,7 @@ Platform.TIME, Platform.BUTTON, Platform.MEDIA_PLAYER, + Platform.TEXT ] SCAN_INTERVAL = timedelta(seconds=60) @@ -210,26 +211,38 @@ async def async_set_snooze_time(self, time): async def async_set_alarm_day(self, alarm, day): """Set the day of the alarm.""" async with self.state_lock: - if day == WORKDAYS: + if type(day) == list: await self.hass.async_add_executor_job( - self.somneo.set_alarm_workdays, alarm - ) + self.somneo.set_alarm_days, + alarm, + day + ) _LOGGER.debug("Optie is werkday") - elif day == WEEKEND: - await self.hass.async_add_executor_job( - self.somneo.set_alarm_weekend, alarm - ) - _LOGGER.debug("Optie is weekend") - elif day == TOMORROW: - await self.hass.async_add_executor_job( - self.somneo.set_alarm_tomorrow, alarm - ) - _LOGGER.debug("Optie is morgen") - elif day == EVERYDAY: - await self.hass.async_add_executor_job( - self.somneo.set_alarm_everyday, alarm - ) - _LOGGER.debug("Optie is elke dag") + else: + if day == WORKDAYS: + await self.hass.async_add_executor_job( + self.somneo.set_alarm_workdays, + alarm + ) + _LOGGER.debug("Optie is werkday") + elif day == WEEKEND: + await self.hass.async_add_executor_job( + self.somneo.set_alarm_weekend, + alarm + ) + _LOGGER.debug("Optie is weekend") + elif day == TOMORROW: + await self.hass.async_add_executor_job( + self.somneo.set_alarm_tomorrow, + alarm + ) + _LOGGER.debug("Optie is morgen") + elif day == EVERYDAY: + await self.hass.async_add_executor_job( + self.somneo.set_alarm_everyday, + alarm + ) + _LOGGER.debug("Optie is elke dag") await self.async_request_refresh() diff --git a/custom_components/somneo/const.py b/custom_components/somneo/const.py index 035caa4..8c362ee 100644 --- a/custom_components/somneo/const.py +++ b/custom_components/somneo/const.py @@ -25,7 +25,7 @@ WEEKEND: Final = "weekend" TOMORROW: Final = "tomorrow" EVERYDAY: Final = "daily" -UNKNOWN: Final = "unknown" +CUSTOM: Final = "custom" PW_DELTA: Final = "powerwake_delta" ALARMS_ICON: Final = "hass:alarm" diff --git a/custom_components/somneo/manifest.json b/custom_components/somneo/manifest.json index 1562900..003b9a2 100644 --- a/custom_components/somneo/manifest.json +++ b/custom_components/somneo/manifest.json @@ -15,13 +15,13 @@ "somneo" ], "requirements": [ - "pysomneo==3.0.1" + "pysomneo==3.1.3" ], "ssdp": [ { "nt": "urn:philips-com:device:DiProduct:1", - "modelName": "Wake-up Light" + "modelName": "Wake-up Light" } ], - "version": "5.0.4" -} + "version": "5.1.0" +} \ No newline at end of file diff --git a/custom_components/somneo/select.py b/custom_components/somneo/select.py index 2b55aa8..9ce8078 100644 --- a/custom_components/somneo/select.py +++ b/custom_components/somneo/select.py @@ -7,7 +7,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.components.select import SelectEntity -from .const import DOMAIN, WORKDAYS, WEEKEND, TOMORROW, EVERYDAY, UNKNOWN, WORKDAYS_ICON +from .const import DOMAIN, WORKDAYS, WEEKEND, TOMORROW, EVERYDAY, CUSTOM, WORKDAYS_ICON from .entity import SomneoEntity @@ -42,7 +42,7 @@ class SomneoDays(SomneoEntity, SelectEntity): _attr_icon = WORKDAYS_ICON _attr_assumed_state = False _attr_available = True - _attr_options = [WORKDAYS, WEEKEND, TOMORROW, EVERYDAY, UNKNOWN] + _attr_options = [WORKDAYS, WEEKEND, TOMORROW, EVERYDAY, CUSTOM] _attr_current_option = WORKDAYS def __init__(self, coordinator, unique_id, name, dev_info, alarm): diff --git a/custom_components/somneo/text.py b/custom_components/somneo/text.py new file mode 100644 index 0000000..116e85c --- /dev/null +++ b/custom_components/somneo/text.py @@ -0,0 +1,71 @@ +"""Text entities for Somneo.""" +import logging +from typing import Any +import voluptuous as vol + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_NAME +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.components.text import TextEntity +from homeassistant.helpers import config_validation as cv, entity_platform + +from .const import ( + DOMAIN, + WORKDAYS_ICON +) +from .entity import SomneoEntity + + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Add Somneo from config_entry.""" + + coordinator = hass.data[DOMAIN][config_entry.entry_id] + unique_id = config_entry.unique_id + assert unique_id is not None + name = config_entry.data[CONF_NAME] + device_info = config_entry.data["dev_info"] + + alarms = [] + for alarm in list(coordinator.data["alarms"]): + alarms.append( + SomneoAlarmDays(coordinator, unique_id, name, device_info, alarm) + ) + + async_add_entities(alarms, update_before_add=True) + + +class SomneoAlarmDays(SomneoEntity, TextEntity): + """Representation of a alarm switch.""" + + _attr_should_poll = True + _attr_assumed_state = False + _attr_available = True + _attr_icon = WORKDAYS_ICON + _attr_native_value = None + _attr_pattern = "^((tomorrow|mon|tue|wed|thu|fri|sat|sun)(,)?)+$" + + def __init__(self, coordinator, unique_id, name, device_info, alarm): + """Initialize the switches.""" + super().__init__(coordinator, unique_id, name, device_info, alarm) + + self._attr_translation_key = alarm + '_days_str' + self._alarm = alarm + + @callback + def _handle_coordinator_update(self) -> None: + days_list = self.coordinator.data["alarm_day_list"][self._alarm] + self._attr_native_value = ",".join([str(item) for item in days_list if item]) + + self.async_write_ha_state() + + async def async_set_value(self, value: str) -> None: + """Set the text value.""" + await self.coordinator.async_set_alarm_day(self._alarm, value.split(',')) \ No newline at end of file diff --git a/custom_components/somneo/translations/en.json b/custom_components/somneo/translations/en.json index ad7c5d0..f36a345 100644 --- a/custom_components/somneo/translations/en.json +++ b/custom_components/somneo/translations/en.json @@ -76,7 +76,8 @@ "workdays": "Workdays", "daily": "Daily", "weekend": "Weekend", - "tomorrow": "Tomorrow" + "tomorrow": "Tomorrow", + "custom": "Custom" } }, "alarm1_days": { @@ -85,7 +86,8 @@ "workdays": "Workdays", "daily": "Daily", "weekend": "Weekend", - "tomorrow": "Tomorrow" + "tomorrow": "Tomorrow", + "custom": "Custom" } }, "alarm2_days": { @@ -94,7 +96,8 @@ "workdays": "Workdays", "daily": "Daily", "weekend": "Weekend", - "tomorrow": "Tomorrow" + "tomorrow": "Tomorrow", + "custom": "Custom" } }, "alarm3_days": { @@ -103,7 +106,8 @@ "workdays": "Workdays", "daily": "Daily", "weekend": "Weekend", - "tomorrow": "Tomorrow" + "tomorrow": "Tomorrow", + "custom": "Custom" } }, "alarm4_days": { @@ -112,7 +116,8 @@ "workdays": "Workdays", "daily": "Daily", "weekend": "Weekend", - "tomorrow": "Tomorrow" + "tomorrow": "Tomorrow", + "custom": "Custom" } }, "alarm5_days": { @@ -121,7 +126,8 @@ "workdays": "Workdays", "daily": "Daily", "weekend": "Weekend", - "tomorrow": "Tomorrow" + "tomorrow": "Tomorrow", + "custom": "Custom" } }, "alarm6_days": { @@ -130,7 +136,8 @@ "workdays": "Workdays", "daily": "Daily", "weekend": "Weekend", - "tomorrow": "Tomorrow" + "tomorrow": "Tomorrow", + "custom": "Custom" } }, "alarm7_days": { @@ -139,7 +146,8 @@ "workdays": "Workdays", "daily": "Daily", "weekend": "Weekend", - "tomorrow": "Tomorrow" + "tomorrow": "Tomorrow", + "custom": "Custom" } }, "alarm8_days": { @@ -148,7 +156,8 @@ "workdays": "Workdays", "daily": "Daily", "weekend": "Weekend", - "tomorrow": "Tomorrow" + "tomorrow": "Tomorrow", + "custom": "Custom" } }, "alarm9_days": { @@ -157,7 +166,8 @@ "workdays": "Workdays", "daily": "Daily", "weekend": "Weekend", - "tomorrow": "Tomorrow" + "tomorrow": "Tomorrow", + "custom": "Custom" } }, "alarm10_days": { @@ -166,7 +176,8 @@ "workdays": "Workdays", "daily": "Daily", "weekend": "Weekend", - "tomorrow": "Tomorrow" + "tomorrow": "Tomorrow", + "custom": "Custom" } }, "alarm11_days": { @@ -175,7 +186,8 @@ "workdays": "Workdays", "daily": "Daily", "weekend": "Weekend", - "tomorrow": "Tomorrow" + "tomorrow": "Tomorrow", + "custom": "Custom" } }, "alarm12_days": { @@ -184,7 +196,8 @@ "workdays": "Workdays", "daily": "Daily", "weekend": "Weekend", - "tomorrow": "Tomorrow" + "tomorrow": "Tomorrow", + "custom": "Custom" } }, "alarm13_days": { @@ -193,7 +206,8 @@ "workdays": "Workdays", "daily": "Daily", "weekend": "Weekend", - "tomorrow": "Tomorrow" + "tomorrow": "Tomorrow", + "custom": "Custom" } }, "alarm14_days": { @@ -202,7 +216,8 @@ "workdays": "Workdays", "daily": "Daily", "weekend": "Weekend", - "tomorrow": "Tomorrow" + "tomorrow": "Tomorrow", + "custom": "Custom" } }, "alarm15_days": { @@ -211,7 +226,8 @@ "workdays": "Workdays", "daily": "Daily", "weekend": "Weekend", - "tomorrow": "Tomorrow" + "tomorrow": "Tomorrow", + "custom": "Custom" } } }, @@ -878,6 +894,56 @@ "name": "Snooze time" } }, + "text": { + "alarm0_days_str": { + "name": "Alarm0 days" + }, + "alarm1_days_str": { + "name": "Alarm1 days" + }, + "alarm2_days_str": { + "name": "Alarm2 days" + }, + "alarm3_days_str": { + "name": "Alarm3 days" + }, + "alarm4_days_str": { + "name": "Alarm4 days" + }, + "alarm5_days_str": { + "name": "Alarm5 days" + }, + "alarm6_days_str": { + "name": "Alarm6 days" + }, + "alarm7_days_str": { + "name": "Alarm7 days" + }, + "alarm8_days_str": { + "name": "Alarm8 days" + }, + "alarm9_days_str": { + "name": "Alarm9 days" + }, + "alarm10_days_str": { + "name": "Alarm10 days" + }, + "alarm11_days_str": { + "name": "Alarm11 days" + }, + "alarm12_days_str": { + "name": "Alarm12 days" + }, + "alarm13_days_str": { + "name": "Alarm13 days" + }, + "alarm14_days_str": { + "name": "Alarm14 days" + }, + "alarm15_days_str": { + "name": "Alarm15 days" + } + }, "time": { "alarm0_time": { "name": "Alarm0 time" @@ -929,4 +995,4 @@ } } } -} +} \ No newline at end of file diff --git a/custom_components/somneo/translations/nl.json b/custom_components/somneo/translations/nl.json index a585f64..9c3d2d3 100644 --- a/custom_components/somneo/translations/nl.json +++ b/custom_components/somneo/translations/nl.json @@ -76,7 +76,8 @@ "workdays": "Doordeweeks", "daily": "Dagelijks", "weekend": "Weekend", - "tomorrow": "Morgen" + "tomorrow": "Morgen", + "custom": "Aangepast" } }, "alarm1_days": { @@ -85,7 +86,8 @@ "workdays": "Doordeweeks", "daily": "Dagelijks", "weekend": "Weekend", - "tomorrow": "Morgen" + "tomorrow": "Morgen", + "custom": "Aangepast" } }, "alarm2_days": { @@ -94,7 +96,8 @@ "workdays": "Doordeweeks", "daily": "Dagelijks", "weekend": "Weekend", - "tomorrow": "Morgen" + "tomorrow": "Morgen", + "custom": "Aangepast" } }, "alarm3_days": { @@ -103,7 +106,8 @@ "workdays": "Doordeweeks", "daily": "Dagelijks", "weekend": "Weekend", - "tomorrow": "Morgen" + "tomorrow": "Morgen", + "custom": "Aangepast" } }, "alarm4_days": { @@ -112,7 +116,8 @@ "workdays": "Doordeweeks", "daily": "Dagelijks", "weekend": "Weekend", - "tomorrow": "Morgen" + "tomorrow": "Morgen", + "custom": "Aangepast" } }, "alarm5_days": { @@ -121,7 +126,8 @@ "workdays": "Doordeweeks", "daily": "Dagelijks", "weekend": "Weekend", - "tomorrow": "Morgen" + "tomorrow": "Morgen", + "custom": "Aangepast" } }, "alarm6_days": { @@ -130,7 +136,8 @@ "workdays": "Doordeweeks", "daily": "Dagelijks", "weekend": "Weekend", - "tomorrow": "Morgen" + "tomorrow": "Morgen", + "custom": "Aangepast" } }, "alarm7_days": { @@ -139,7 +146,8 @@ "workdays": "Doordeweeks", "daily": "Dagelijks", "weekend": "Weekend", - "tomorrow": "Morgen" + "tomorrow": "Morgen", + "custom": "Aangepast" } }, "alarm8_days": { @@ -148,7 +156,8 @@ "workdays": "Doordeweeks", "daily": "Dagelijks", "weekend": "Weekend", - "tomorrow": "Morgen" + "tomorrow": "Morgen", + "custom": "Aangepast" } }, "alarm9_days": { @@ -157,7 +166,8 @@ "workdays": "Doordeweeks", "daily": "Dagelijks", "weekend": "Weekend", - "tomorrow": "Morgen" + "tomorrow": "Morgen", + "custom": "Aangepast" } }, "alarm10_days": { @@ -166,7 +176,8 @@ "workdays": "Doordeweeks", "daily": "Dagelijks", "weekend": "Weekend", - "tomorrow": "Morgen" + "tomorrow": "Morgen", + "custom": "Aangepast" } }, "alarm11_days": { @@ -175,7 +186,8 @@ "workdays": "Doordeweeks", "daily": "Dagelijks", "weekend": "Weekend", - "tomorrow": "Morgen" + "tomorrow": "Morgen", + "custom": "Aangepast" } }, "alarm12_days": { @@ -184,7 +196,8 @@ "workdays": "Doordeweeks", "daily": "Dagelijks", "weekend": "Weekend", - "tomorrow": "Morgen" + "tomorrow": "Morgen", + "custom": "Aangepast" } }, "alarm13_days": { @@ -193,7 +206,8 @@ "workdays": "Doordeweeks", "daily": "Dagelijks", "weekend": "Weekend", - "tomorrow": "Morgen" + "tomorrow": "Morgen", + "custom": "Aangepast" } }, "alarm14_days": { @@ -202,7 +216,8 @@ "workdays": "Doordeweeks", "daily": "Dagelijks", "weekend": "Weekend", - "tomorrow": "Morgen" + "tomorrow": "Morgen", + "custom": "Aangepast" } }, "alarm15_days": { @@ -211,7 +226,8 @@ "workdays": "Doordeweeks", "daily": "Dagelijks", "weekend": "Weekend", - "tomorrow": "Morgen" + "tomorrow": "Morgen", + "custom": "Aangepast" } } }, @@ -878,6 +894,56 @@ "name": "Sluimertijd" } }, + "text": { + "alarm0_days_str": { + "name": "Alarm0 dagen" + }, + "alarm1_days_str": { + "name": "Alarm1 dagen" + }, + "alarm2_days_str": { + "name": "Alarm2 dagen" + }, + "alarm3_days_str": { + "name": "Alarm3 dagen" + }, + "alarm4_days_str": { + "name": "Alarm4 dagen" + }, + "alarm5_days_str": { + "name": "Alarm5 dagen" + }, + "alarm6_days_str": { + "name": "Alarm6 dagen" + }, + "alarm7_days_str": { + "name": "Alarm7 dagen" + }, + "alarm8_days_str": { + "name": "Alarm8 dagen" + }, + "alarm9_days_str": { + "name": "Alarm9 dagen" + }, + "alarm10_days_str": { + "name": "Alarm10 dagen" + }, + "alarm11_days_str": { + "name": "Alarm11 dagen" + }, + "alarm12_days_str": { + "name": "Alarm12 dagen" + }, + "alarm13_days_str": { + "name": "Alarm13 dagen" + }, + "alarm14_days_str": { + "name": "Alarm14 dagen" + }, + "alarm15_days_str": { + "name": "Alarm15 dagen" + } + }, "time": { "alarm0_time": { "name": "Alarm0 tijd" diff --git a/lovelace1.jpg b/lovelace1.jpg index 2cb7c0d..22a95b3 100644 Binary files a/lovelace1.jpg and b/lovelace1.jpg differ diff --git a/lovelace2.jpg b/lovelace2.jpg index 07ca92c..9fb7102 100644 Binary files a/lovelace2.jpg and b/lovelace2.jpg differ