Ok, now I understand that we're not going to get significant improvements to the UI anytime soon. My second question/suggestion goes deeper. Note that I started writing this simply to get myself more organized around how to write rules, but ended up... someplace else.
A rule is fundamentally simple:
when (trigger) { operation }
where (and sorry about the formatting, I wrote this on my text editor and couldn't figure out a better way to make a hierarchical list):
# A. A trigger is basically an event whose occurrance will cause the
# operation to be executed. Events can be organized into four categories:
# 1. Activity from a device
# i. switch turning on/off, thermostat activity, motion sensor, etc
# ii. sensor data (like temperature, battery) state change - e.g.
# crossing a threshold, etc
# 2. Hubitat system events, including scheduled events (periodic or at
# a specific time), mode changes, etc
# 3. Signals about other rules:
# i. the trigger conditions for another rule
# ii. that another rule fired
# iii. when a rule is paused or resumed
# iv. another rule's "private boolean"
# 4. Complicated triggers:
# i. Conditional triggers, representing modifiers on the above
# ii. Compound triggers, representing a collection of the above
#
# B. An operation is a collection of one or more actions, which fall into
# four basic types:
# 1. Act:
# i. change a switch, activate a scene
# ii. delay/pause/wait (state) - wait for something to happen
# delay: wait a fixed amount of time
# pause: wait until externally interrupted
# wait (state) - explicit test per the tests described 2(i) conditional below)
# iii. restore device state
# iv. send/speak a notification
# 2. Logic:
# i. conditional - if (test) then {operation} else {operation}
# (test can evaluate device state, global variables, or the
# nameless boolean... or another rule's "private" boolean)
# ii. loop - repeat {operation} (periodically or by count)
# 3. Capture state: capture/poll device state, set global variables,
# set the nameless "private" boolean
# 4. Control other rules (run actions, pause/resume, cancel delays, set
# another rule's "private" boolean)
I struggle with a few things here.
First, providing a single boolean to track state seems arbitrarily limited. People need state. Working around this results in hacks such as people defining a virtual dimmer switch simply to emulate a scalar variable, or using global variables with curated variable names to work around the lack of good variable scoping.
Second, the interaction between rules [sections A(3) and B(4)] seems quite ad hoc. We can steal another rule's trigger, we can run another rule's operation. We can read or set another rule's private boolean - which most definitely means it is not private, at least not in the sense that most programming languages use the term. We can fire, pause, or resume another rule, or use another rule's being fired, paused, or resumed as a trigger ourselves. All in all, it seems like a twisty, intertwingled opportunity to accidentally hurt myself all the way down.
Third, the implementation of delay/pause/wait is tricky and I think needlessly complex. First, the implementation here seems muddled and mixed around with other concerns; we have three primitives that do largely the same thing based on how processing is restarted: "delay" waits for a fixed amount of time; "wait" responds either to an event or sensor data state change; and all can be interrupted by another rule (while "pause" waits only for that explicit interrupt). In the user interface, delay and wait are organized together, but they're somewhat mysteriously mixed in with repeat; meanwhile, pause is mixed in with setting variables and "run rule actions." But all of this suggests an intriguing thought to me: consider that this interaction basically introduces the concept of a trigger in the middle of an operation. Right? Delay is a one-time scheduled event; wait is just sticking a trigger in the middle of the rule; "pause/resume" is a trigger aimed only at that one rule.
Ok, so let's take a step back. I think RM could reorganize things to (1) make the interactions between rules easier to reason about, and (2) unify delay/pause/wait. Furthermore, I think I can propose a way to do that which would simplify the whole interaction model, and I don't think would require changing much of the existing, underlying code (although the operating system appears to not be what I thought it was).
First, I want to suggest the idea of a "named" trigger. I want RM to allow me to define a complicated trigger [per (A(4) above, either conditional or compount] as its own entity with a name. So if I have a collection of a dozen motion sensors, where I care when e.g. the external are active and the internal are not, I can define a trigger that fires appropriately and reuse it multiple times as needed. Then when I add another motion sensor, I only need to add it to one place. A named trigger should be able to use another named trigger, which means we can mix and match creatively - and this of course gives RM a nice place to detect cycles [a triggers b triggers c triggers a], which I don't think it can do today. And I'd want RM to show me all my pre-defined complicated triggers in a nice little list. Obviously this also gives me the ability to give a semantic name to a simple trigger, if I want. Seems like a small but pretty useful change to me.
when (gym_idle)
Off: Treadmill
But I want to be able to create named triggers ad hoc, and I want to organize all the wait/delay/pause logic explicitly around them. What I wait for is a trigger, always and only. If I want to delay, I attach the delay to the trigger, not the operation. If I want to repeat, I make the repetition part of the definition of the trigger, not the operation. And if I want to cancel it, I can cancel the trigger, presumably by name. (And if I haven't given it a name, I can still operate on it similar to how it works today - but then I can't reuse it with other rules.)
when (gym motion sensor *changed*)
if (gym Motion Sensor inactive)
send gym_idle delayed 0:05:00
else
cancel gym_idle
Ok, that's basically the idea. Why would you care? What does this do?
I. We can get rid of sections A(3) and B(4) entirely, the stuff that distorts the interaction between rules. It's just named triggers; anybody who wants one can send one, and anybody interested can catch it. I don't need to run some other rule's operation, I can send a trigger and then add it to the triggers that rule cares about. Much more tidy, and a lot easier to figure out a couple years later.
II. We can unify all the logic around state and repetition into a simpler implementation around the triggers. B(1)(ii) [wait] and B(2)(ii) [looping] and B(4) [control] unify. Repetition and delay is entirely about a trigger. Waiting is simply about a trigger. Pausing is entirely about a trigger.
So what would the organization look like? It's a lot simpler. I don't think there's anything you can do today that you can't do with this approach, but curious if I'm missing something. (I would really prefer a different programming UI for all of this, but I'm resigned to not getting it.)
# A. Triggers
# 1. A trigger is:
# i. Activity from a device (like before);
# ii. Hubitat system events (like before);
# iii. A signal (explicitly sent by some process, typically
# another rule)
# iv. Complicated triggers, conditional and compound.
# 2. Triggers can be named.
# 3. Triggers can be scheduled:
# i. be delayed, recurring, periodic, or at a specific time);
# ii. scheduled triggers can be paused/resumed, or cancelled.
#
# B. Operations
# 1. Act:
# i. change a switch, activate a scene, or send a (named) signal;
# ii. wait (for a trigger), which just means stop processing until a
# (named) trigger arrives;
# iii. restore device state;
# iv. send/speak a notification.
# 2. Logic:
# i. conditional - if (test) then {operation} else {operation}
# (test can evaluate device state, global variables, or the
# nameless boolean... or another rule's "private" boolean)
# 3. Capture state: capture/poll device state, set global variables,
# set the nameless "private" boolean
#
That's the environment I want to be coding in.
Thoughts?