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
```
-
+
-### 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
```
-
+
+
# 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