So, I am attempting to build an automation/blueprint for an alarm clock of sorts. Nothing complicated really, begin to fade in lights a user adjustable offset before, then when it is time to wake up begin playing music at a low volume and slowly increase it.
Lights are super simple since light.turn_on has the transition property, set and forget. Unfortunately there is nothing like that for a media player’s volume_level, so it has to be dealt with manually through a loop. So far so good, but the most important function of an alarm clock is missing: Snooze! Also the ability to deactivate the alarm (turn off lights/music).
To me the straightforward way to handle snooze would also be with a loop. The problem is I cannot quite figure out how to implement this due to variable scope. If I detect that snooze/stop is pressed inside the volume loop I can break out of it with a wait.completed condition. But once on the outside, all information about what happened inside is lost. I can no longer tell whether to also break out of the outer loop (alarm was stopped), go back to the beginning of the loop (alarm was snoozed), or keep waiting for either event (loop finished naturally).
Not quite ready to share my full code yet, and not sure it will help in the state it is currently in. But here is some simplified pseudo-code for the parts as described above:
repeat:
sequence:
turn on light transition
play music
repeat:
sequence:
increase volume level
wait_for_trigger:
- triggers: snooze & stop
timeout: next volume increase
until:
wait.completed (or manual takeover of playback)
wait_for_trigger:
- triggers: snooze & stop
timeout: alarm duration
until:
wait.completed or alarm duration passed
turn off lights
pause music
Is there any way around this, short of using a helper? Because frankly it seems downright ridiculous to require setting up external storage to pass information within the same run of an automation. Maybe there is something obvious I am overlooking, or a completely different way to structure this automation…
Hmm. Not exactly sure how you mean, use just the one loop rather than two nested loops? Then how do I keep track of where in the process I am? As in when a new iteration of the loop begins, how do I know what happened previously, without resorting to storing things in a helper…
Another thing I didn’t mention above which may or may not complicate things is that I also need to keep track of the number of snoozes. One thing I’ve always found anoying with your typical alarm clock is the fixed snooze time, so I want the snooze to become shorter for each time it is invoked.
I’ll need to see the entire automation to provide more incite. It’s very rare that anyone would need global variables if you set up the loop ahead of time.
I’ll have to do some more cleanup before posting what I have, but I’ll get on it then…
I don’t see how I can account for every possible variable ahead of time, considering the user can stop or snooze at any time, and snooze any number of times, and what happens on a snooze will differ every time. Automations would be sooo much easier to write with persistent variables, and other things you’d expect out of any programming languages such as functions or even just a goto statement.
Also, as far as I can tell the only variable that may persist long enough to be accessible by a repeat: until: is wait. Is there any other way to break out of a loop? Using a condition: only jumps to the next iteration, while stop: stops the whole automation…
Sorry, I just don’t see the need for 2 loops, or the need to break anything out at the end. If you could just post your entities and your snooze, stop triggers, I could show you what I mean.
EDIT: And your initial trigger.
EDIT: I see why you want 2 loops and the “need for a global variable”
Anyways, here’s how you can do it without any loops and 2 input_datetimes
alias: Alarm
mode: parallel
trigger:
- platform: time
at:
- input_datetime.alarm
- input_datetime.snooze
id: alarm
- platform: event
event_type: alarm_volume
id: volume
- platform: event
event_type: alarm_snooze
id: snooze
- platform: event
event_type: alarm_stop
id: stop
action:
- choose:
# SNOOZE
- conditions:
- condition: trigger
id: snooze
sequence:
- <ACTION TO STOP MUSIC>
- action: input_datetime.set_datetime
target:
entity_id: input_datetime.alarm
data:
time: "{{ (today_at(states('input_datetime.alarm')) + timedelta(minutes=9)).time() }}"
# ALARM
- conditions:
- condition: trigger
id: alarm
sequence:
- <INCREASE volume>
- delay: "00:01:00"
# Potentially add a condition here with an input boolean.
# Or even easier, check to see if the media is still playing, if it is, increase volume.
- event: alarm_volume
# STOP
- conditions:
- condition: trigger
id: stop
sequence:
- <ACTION TO STOP MUSIC>
- action: input_datetime.set_datetime
target:
entity_id: input_datetime.snooze
data:
time: "{{ states('input_datetime.alarm') }}"
EDIT: You may need an input boolean in there to stop a volume increase from occuring when you stop.
Also keep in mind this will always continue to alarm until you stop.
EDIT EDIT: This method will also survive restarts for the most part. If you use a 3rd input_datetime, you could make the volume event survive restarts too.
While I agree with this, I also dislike using helpers because they can’t be created from the automation itself. I also don’t like how helpers can be modified by the UI if someone places them on a dashboard. If we had helpers that were actionable from actions only, that would be a different story.
Helpers are obviously useful for things you want to put on the dashboard, or data that may be used by many different automations. But for persistent values that will never be used by a different automation they are an annoyance. That they are seemingly necessary merely for passing information within the same run of an automation due to the ridiculously restrictive variable scope is downright madness.
Until this is dealt with Home Assistant will never be a mainstream home automation platform. When a “normal user” downloads a blueprint for an automation, that automation should be able to be entirely self-contained for everything except the entities it pulls data from. You should never ever have to ask a “normal user” to unnecessarily set up something they do not understand, i.e. helpers.
That is certainly an interesting way to do it. I am admittedly very bad at asynchronous programming, too much of a linear thinker I guess. Still requires adding additional datetime helpers, and has no way of keeping track of the number of snoozes without adding even further helpers. Something I feel absolutely should be avoidable, see explanation above…
Here’s an example for the use case with asynchronous programming… Don’t include the snooze button even in your loop. Your snooze button should have a simple unrelated automation that does nothing other than increment an input_number (only if the alarm is going off) - and in your code/loops just look at that input_number. Also, when the alarm is turned off, then just reset the snooze input_number to 0. Easy peasy. When debugging your code, look at the dashboard to see the input_number snooze count - and then when done debugging, just set it as hidden. I use input helpers allover the place for such things. Thoughts?
(FYI/PS I also have a large number of automations that kick off when HA is started - such as for example if the automation input select has a value of “Enabled” for a room, if there is motion and the light is off then turn it on, if automation for that room is off then any and all smart plugs for table lamps should be turned on (to not drive people craz when tey try to turn on a table lamp), if any timers that should be running are not then to start them, etc., etc. - and in this case one thing that can be done is to resent any and all snooze button counts (input_numbers) to zero if the alarm is not going off…)
My philosophy is never to have an automation that does more than one thing, and keep them all simple, name them in a way that makes it easy to find them (I always start with the location, then the name of the sensor, then “if this → then that” and set mode for almost all of them to parallel, etc. Then it is SO MUCH easier to debug things - and add more functionality while keeping it simple. Having more automations DOES NOT slow things down. For example - what if you want something else to also take place from the snooze button being pressed - say the third time? In the case I outlined above, just add another automation for that instead ofr a monoloithic automation that gets more and more complicated!
Automations can even enable or disable other autromations. It is so flexible I love it.
Well whatever works for the individual is obviously the way to go on any of their personal projects. Many smaller automations would keep the complexity of each individual piece down, but to me makes it more difficult to keep track of dependencies and how they all interact and avoiding unexpected side effects.
While I can improvise my way through small-scale hobby programming, I am beyond useless in the bigger picture of software engineering/architecting. What I’ve dabbled in OOP I really like, and asynchronous can be doable if operations are somewhat independent of each other and their order of execution aren’t that important. For this particular automation though, order is important, which makes linear programming so much more predictable.
As long as they more or less serve a single function, I find monolithic self-contained automations easier to write and understand. And in a way, with how blueprints currently work, that seems to be what is encouraged by Home Assistant itself? Again, requiring “normal” users (as in someone who does not write their own systems of automations from scratch) to download several blueprints, read extensive documentation on how to set them up along with a bunch of helpers just ain’t the way to go if this project aims for mainstream adoption.
Uhh, I guess. But that assumes the sleepy person will always snooze ASAP, and does not take into account the time taken before snoozing. Which in the grand scheme of things doesn’t really matter of course, but I am admittedly too OCD about such things…
Anyway, with some inspiration from this thread, I believe I have come up with a workable solution. No I won’t go fully event driven, as that seems much more difficult to keep the actions correctly in sync, but I can break out the trigger that stops the alarm. With the automation in mode: restart, the stop trigger will automatically stop the main loop, the latter which now only has to deal with snooze (by way of wait_for_trigger).