Skip to content

Commit 8a00ee1

Browse files
authored
Merge pull request #120 from tanvach/feature/alarm-api-migration
Migrate alarm system to new API with dynamic entities, dismiss & snooze buttons
2 parents d16a2c9 + 18b2e2b commit 8a00ee1

7 files changed

Lines changed: 441 additions & 285 deletions

File tree

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,7 @@ cython_debug/
159159
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
160160
.idea/
161161
# ignoring md files so that context files can be created here
162-
*.md
162+
*.md
163+
164+
# Dev/exploration scripts (never push)
165+
.dev/

custom_components/eight_sleep/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343

4444
PLATFORMS = [
4545
Platform.BINARY_SENSOR,
46+
Platform.BUTTON,
4647
Platform.CLIMATE,
4748
Platform.MEDIA_PLAYER,
4849
Platform.NUMBER,
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"""Eight Sleep button entities for alarm actions."""
2+
3+
from __future__ import annotations
4+
5+
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
6+
from homeassistant.config_entries import ConfigEntry
7+
from homeassistant.core import HomeAssistant
8+
from homeassistant.helpers.entity_platform import AddEntitiesCallback
9+
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
10+
11+
from . import EightSleepBaseEntity, EightSleepConfigEntryData
12+
from .const import DOMAIN
13+
from .pyEight.eight import EightSleep
14+
from .pyEight.user import EightUser
15+
16+
17+
BUTTON_DESCRIPTIONS = [
18+
ButtonEntityDescription(
19+
key="alarm_dismiss",
20+
name="Dismiss Alarm",
21+
icon="mdi:alarm-check",
22+
),
23+
ButtonEntityDescription(
24+
key="alarm_snooze",
25+
name="Snooze Alarm",
26+
icon="mdi:alarm-snooze",
27+
),
28+
]
29+
30+
31+
async def async_setup_entry(
32+
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
33+
) -> None:
34+
config_entry_data: EightSleepConfigEntryData = hass.data[DOMAIN][entry.entry_id]
35+
eight = config_entry_data.api
36+
37+
entities: list[ButtonEntity] = []
38+
39+
for user in eight.users.values():
40+
for description in BUTTON_DESCRIPTIONS:
41+
entities.append(
42+
EightAlarmButton(
43+
entry,
44+
config_entry_data.user_coordinator,
45+
eight,
46+
user,
47+
description,
48+
)
49+
)
50+
51+
async_add_entities(entities)
52+
53+
54+
class EightAlarmButton(EightSleepBaseEntity, ButtonEntity):
55+
"""Button to dismiss or snooze the currently active alarm."""
56+
57+
def __init__(
58+
self,
59+
entry: ConfigEntry,
60+
coordinator: DataUpdateCoordinator,
61+
eight: EightSleep,
62+
user: EightUser,
63+
entity_description: ButtonEntityDescription,
64+
) -> None:
65+
super().__init__(entry, coordinator, eight, user, entity_description.key)
66+
self.entity_description = entity_description
67+
68+
async def async_press(self) -> None:
69+
if self._user_obj is None:
70+
return
71+
72+
if self.entity_description.key == "alarm_dismiss":
73+
await self._user_obj.alarm_dismiss()
74+
elif self.entity_description.key == "alarm_snooze":
75+
await self._user_obj.alarm_snooze(self._user_obj.snooze_minutes)
76+
77+
await self.coordinator.async_request_refresh()

custom_components/eight_sleep/number.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from homeassistant.core import HomeAssistant
44
from homeassistant.config_entries import ConfigEntry
55
from homeassistant.helpers.entity_platform import AddEntitiesCallback
6+
from homeassistant.helpers.restore_state import RestoreEntity
67
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
78

89
from custom_components.eight_sleep import EightSleepBaseEntity, EightSleepConfigEntryData
@@ -30,6 +31,16 @@
3031
icon="mdi:head",
3132
)
3233

34+
SNOOZE_MINUTES_DESCRIPTION = NumberEntityDescription(
35+
key="alarm_snooze_minutes",
36+
native_unit_of_measurement="min",
37+
native_max_value=30,
38+
native_min_value=5,
39+
native_step=1,
40+
name="Alarm Snooze Minutes",
41+
icon="mdi:alarm-snooze",
42+
)
43+
3344

3445
async def async_setup_entry(
3546
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
@@ -40,6 +51,21 @@ async def async_setup_entry(
4051

4152
entities: list[NumberEntity] = []
4253

54+
# Add snooze minutes number entity for each user
55+
for user in eight.users.values():
56+
entities.append(
57+
EightNumberEntity(
58+
entry,
59+
config_entry_data.user_coordinator,
60+
eight,
61+
user,
62+
SNOOZE_MINUTES_DESCRIPTION,
63+
lambda u=user: u.snooze_minutes,
64+
lambda value, u=user: setattr(u, 'snooze_minutes', int(value)),
65+
base_entity=False,
66+
)
67+
)
68+
4369
user = eight.base_user
4470
if user:
4571
def set_leg_angle(value):
@@ -71,7 +97,7 @@ def set_torso_angle(value):
7197
async_add_entities(entities)
7298

7399

74-
class EightNumberEntity(EightSleepBaseEntity, NumberEntity):
100+
class EightNumberEntity(EightSleepBaseEntity, NumberEntity, RestoreEntity):
75101

76102
def __init__(
77103
self,
@@ -81,13 +107,24 @@ def __init__(
81107
user: EightUser | None,
82108
entity_description: NumberEntityDescription,
83109
value_getter: Callable[[], float | None],
84-
set_value_callback: Callable[[float], None]
110+
set_value_callback: Callable[[float], None],
111+
base_entity: bool = True,
85112
):
86-
super().__init__(entry, coordinator, eight, user, entity_description.key, base_entity=True)
113+
super().__init__(entry, coordinator, eight, user, entity_description.key, base_entity=base_entity)
87114
self.entity_description = entity_description
88115
self._value_getter = value_getter
89116
self._set_value_callback = set_value_callback
90117

118+
async def async_added_to_hass(self) -> None:
119+
"""Restore previous value on startup."""
120+
await super().async_added_to_hass()
121+
last_state = await self.async_get_last_state()
122+
if last_state and last_state.state not in (None, "unknown", "unavailable"):
123+
try:
124+
self._set_value_callback(float(last_state.state))
125+
except (ValueError, TypeError):
126+
pass
127+
91128
@property
92129
def native_value(self) -> float | None:
93130
return self._value_getter()

0 commit comments

Comments
 (0)