forked from basnijholt/home-assistant-config
-
Notifications
You must be signed in to change notification settings - Fork 0
/
wake_up_with_spotify.py
executable file
·136 lines (118 loc) · 4.25 KB
/
wake_up_with_spotify.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
"""Turns on speakers and plays Spotify while slowly ramping the volume.
To cancel the ramp, simply change the volume manually.
# Example `apps.yaml` config:
```
wake_up_with_spotify:
module: wake_up_with_spotify
class: WakeUpWithSpotify
speaker: media_player.kef_ls50
spotify_source: "KEF LS50"
playlist: "spotify:playlist:6rPTm9dYftKcFAfwyRqmDZ"
total_time: 900
final_volume: 0.3
input_boolean: input_boolean.wake_up_with_spotify
```
# Example `configuration.yaml`:
```
input_boolean:
wake_up_with_spotify:
name: Start wake up with Spotify
initial: off
icon: mdi:music
```
"""
import hassapi as hass
DEFAULT_SPEAKER = "media_player.kef_ls50"
DEFAULT_SPOTIFY_SOURCE = "KEF KEF LS50"
DEFAULT_PLAYLIST = "6rPTm9dYftKcFAfwyRqmDZ"
DEFAULT_TOTAL_TIME = 300
DEFAULT_FINAL_VOLUME = 0.3
DEFAULT_INPUT_BOOLEAN = "input_boolean.wake_up_with_spotify"
MIN_VOLUME_STEP = 0.01
MIN_TIME_STEP = 4
DEFAULTS = {
"speaker": DEFAULT_SPEAKER,
"spotify_source": DEFAULT_SPOTIFY_SOURCE,
"playlist": DEFAULT_PLAYLIST,
"total_time": DEFAULT_TOTAL_TIME,
"final_volume": DEFAULT_FINAL_VOLUME,
"input_boolean": DEFAULT_INPUT_BOOLEAN,
}
def linspace(a, b, n=100):
if n < 2:
return b
diff = (float(b) - a) / (n - 1)
return [diff * i + a for i in range(n)]
class WakeUpWithSpotify(hass.Hass):
def initialize(self):
self.volume = None
self.input_boolean = self.args.get("input_boolean", DEFAULT_INPUT_BOOLEAN)
self.listen_state(self.start_cb, self.input_boolean, new="on")
self.todos = []
def maybe_defaults(self, kwargs):
for key in set(DEFAULTS) | set(self.args):
if key in kwargs:
continue
elif key in self.args:
kwargs[key] = self.args[key]
else:
kwargs[key] = DEFAULTS[key]
@property
def done_signal(self):
return f"{self.input_boolean}.done"
def start_cb(self, entity, attribute, old, new, kwargs):
self.set_state(self.input_boolean, state="off")
self.start()
def start(self, **kwargs):
self.volume = 0
self.maybe_defaults(kwargs)
app = self.get_app("start_spotify")
self.listen_event(
self.start_volume_ramp_cb, app.done_signal, timeout=30, oneshot=True
)
app.start(volume=self.volume, **kwargs)
def start_volume_ramp_cb(self, event=None, data=None, kwargs=None):
return self.start_volume_ramp(**data)
def start_volume_ramp(self, **kwargs):
steps_volume = round(kwargs["final_volume"] / MIN_VOLUME_STEP)
steps_time = round(kwargs["total_time"] / MIN_TIME_STEP)
steps = min(steps_volume, steps_time) + 1
times = linspace(0, kwargs["total_time"], steps)
volumes = linspace(0, kwargs["final_volume"], steps)
self.log(f"volumes: {volumes}, times: {times}")
for t, vol in zip(times, volumes):
service_kwargs = {
"entity_id": kwargs["speaker"],
"volume_level": round(vol, 2),
}
is_done = t == kwargs["total_time"]
todo = self.run_in(
self.set_state_cb,
t,
service_kwargs=service_kwargs,
is_done=is_done,
**kwargs,
)
self.todos.append(todo)
def maybe_cancel(self, speaker):
current_volume = self.get_state(speaker, attribute="volume_level")
manually_changed = abs(current_volume - self.volume) > 2 * MIN_VOLUME_STEP
if manually_changed:
self.log(
f"Canceling sequence, "
f"current_volume: {current_volume}, "
f"volume: {self.volume}"
)
while self.todos:
self.cancel_timer(self.todos.pop())
return True
def set_state_cb(self, kwargs):
if self.maybe_cancel(kwargs["speaker"]):
return
service_kwargs = kwargs.pop("service_kwargs")
self.log(f"Setting volume: {service_kwargs}")
self.call_service("media_player/volume_set", **service_kwargs)
self.volume = service_kwargs["volume_level"]
if kwargs.pop("is_done"):
self.fire_event(self.done_signal, **kwargs)
self.log(self.done_signal)