Suggestions for improved user experience with timers

Dear all,

this is my first post in this community (and it’s going to be a longer and more involved one), but I am a seasoned openhab user since OH2.0 and have looked at a lot of source code of OH core and addons. While I haven’t developed anything for OH yet, this topic might be just turn out to be my first venture in this project. I have a long software development history and know my way around in java; what follows are my observations from both a OH user and developer-oriented perspective.

Context and motivation

There has been a lot of discussions and proopsed solutions with regard to timers and I am also very interested in improving the current “situation”. What is the situation? From a user perspective (OH3 with main UI), all discussed solutions regarding one-shot scheduled execution of actions are based on rules (and scripts). In order to get a complete picture of how the setup “works”, one has to inspect the things and items layers (including profiles for their links) are configured and than also look at rules and “merge” this in your head. While I really like the OH3 main UI, it really makes it harder then it should be to get this overview (even for my own setup that I configured all by myself…but after some weeks pass by, I need to “rediscover” what exactly I did in order to accomplish a certain behaviour in my setup).

Let me give an example before continuing:
I have a robot vacuum cleaner that I like to start in 30 minutes, because I will be leaving the home in about 15 minutes. I would like to do this using either a thing (zigbee button) or from a touch display with BasicUI (possibly using other delay values). Essentially, I want to initiate something right now for sometime later on (because I keep forgetting to do that when actually leaving the building). I have a lot of use cases for those “one-shot” scheduled executions and most of them are not as trivial as with the vacuum cleaner: activate bed time scenario for kids per zigbee button: close shades now, power-on power-plug for sonos now, start playing sleeping music once sonos comes online, power-off sonos in 1 hour, send reminder via messenger to check on kids in 15 minutes).

While this is perfectly doable using rules, this approach sort of “clutters” my setup: In addition to essential control rules to make my things behave/react as I like (if this then that), I also have to implement timer related rules/scripts (if this then wait and than do that) and sometimes need proxy items (sometimes profiles for channel links are good enough) to achieve the desired behaviour.

Proposals

My first two proposals are mostly UI focused and should improve the user-experience when looking at my setup (settings in main UI).

  1. It would be great if the thing and item details page in main UI would list (and link to) rules that refer to this item or thing. Possibly grouped by trigger,action and condition usages. This should be straight-forward implementable, as rules are now with OH3 cleanly defined in syntax/structure. This would help greatly in order to understand the behavioural aspects of an item or thing. Bonus points: if the rule triggers a script execution, list and link to those scripts underneath the respective rule.

  2. It would be very helpful for organising rules (maybe also scripts?) if rules were assignable to (nestable) groups, in order to easily find those rules that I am interested in (that for example deal with things/items in the living room). Maybe even a full integration in the semantic model structure?

  3. Better (more user-friendly) setup/handling of timers; this is really a multiple-choice proposal-scenario (design-patterns) based on different approaches

  • Rule based delays
    This is essentially the combination of proposal #1 and #2; further refinements are probably possible and should be analyzed.
    Pros:
    → refinement of already existing concepts/implementations
    Cons:
    → inherent “distribution” of delay logic/configuration over items, rules and maybe even scripts
    → managing active timers (running rule instances) should also be implemented somehow as a first-class citizen(?)

  • Transformations/profiles and proxy items:
    Implement a DELAY transformation (and profile)
    Pros:
    → Uses and extends existing concepts (…and unit tests)
    Cons:
    → The delay can’t be done in the execution context of the transformation/profile due to blocking, thus a central service for asynchronosly handling the timers and the execution of the actions seems needed (including a settings page for listing/modifying active timers)
    → Requires proxy items (item “cluttering”) for good user experience and avoidance of unexpected side-effects regarding autoupdate and UI feedback “behaviour” and also timer state related items (time remaining, delayed actions, …)

  • Delay-implementing action
    Implement an action that implements the delay and gets contextual data (target thing-channel, state, …) passed as parameters.
    Pros:
    → no expectable side-effects
    → does not impact item/thing concept layers
    Cons:
    → Only really invokable from scripts (iirc)
    → This really isn’t an action in terms of the action concept
    Unknown:
    → Whether actions are executed asynchronosly or not (to avoid blocking of invocation context)

  • Delay-implementing proxy thing
    The idea here is to have items linked to (virtual) things that execute the respective command on the target thing after the delay (asynchronosly). Three implementation approaches come to mind:
    → each channel is configured to a specific delay, the command is taken from the invocation and the target thing-channel must be provided (metadata?)
    → each channel is configured to a channel-specific target thing/channel, the command is taken from the invocation and the delay must be provided (metadata?)
    → one generic channel, both command and thing-channel are passed in (metadata?)
    Pros:
    → fully consistent with the item->link->thing-channel data flow concept (and extendable via other mechanisms like expire)
    → status of pending (delayed) commands reportable by thing via (status) channel(s)
    → persistence of timers across system restarts easily implementable (if desired → config option?)
    → if based on implementation approach 3 (generic channel): other sorts of logic in additional delay channels can be implemented easily (for example: delay not in terms of absolute value but calculated from other item values…like a script?)
    Cons:
    → This really isn’t a thing in terms of the things concept
    → implementation specific: If implemented as individually configured things (one thing instantiated and configured for the “target” thing) → thing “cluttering”;

Conclusion
I really want OH to make one-shot delayed execution of commands easier to undertand and use and also better to maintain (more complete picture in a given context in main UI, mostly proposals #1 and #2) and am willing to tackle this myself over the next weeks/months. I would be really grateful for any feedback: I don’t want to implement something that works but won’t be accepted into mainstream.

I personally favor the deplay-implementing proxy-thing approach with a generic channel that is fully “configurable” via metadata; however, it does bother me quite a bit that it isn’t a thing in the sense of the concept; maybe it should be packaged not as a binding but rahter as a misc add-on (while still being a thing provider)?

Regards,
Jürgen

As always - once posted more details come to mind: I think a delay profile implementation would probably be the conceptually cleanest solution; one could even implement a reverse-transformation for use in the sitemap context thus allowing the rendering of the delayed state at the presentation level and thus avoiding additional (proxy) items and such. This tranformation would look up from a central instance if the to be displayed item has an active delayed command and thus change the rendering of the item.

And one additional thought to this approach: the UI feedback could also happen immediately at activation time: either with autoupdate activated (and the delay profile suppressing or invalidating by changing the issued command to the current value of the thing) or with inhibited autoupdate per context in the sitemap definition and changed rendering based on the reverse-transform invocation.

I would ask for more clarification on that because behaviors are exactly the job of rules. So how does defining behaviors clutter the config when that’s what they are there for?

Not so easy though when you consider:

  • Script Actions and Script Conditions are arbitrary code written in one of many different languages.
  • In those scripts the Item may not be referenced directly by name. They may be a member of a Group and referenced with triggeringItem or the Item’s name may be dynamically generated at runtime.

A comprehensive way to identify all the rules that an Item is used in is likely impossible. But some times good enough is good enough.

You can apply tags to rules and search on the tag right now. Before going down a big complicated “Semantic Model” for rules see if that can get you what you are after. Though note that you can also apply semantic tags to rules too. It’s just that nothing is done with them…yet.

Just to be clear, there are three supported “timers” in OH right now (I’m ignoring time based triggers as those are kind of different).

  1. Expire which is defined as metadata on an Item. When an Item changes state, after a time it is commanded or updated back to a default state.

  2. Thread.sleep which blocks execution of a Script for the given number of milliseconds.

  3. Timer object which can only be created and interacted with in a Script. This is basically a schedulable: “execute this function at this time”.

2 and 3 really only make sense inside a Script.

Are you proposing a new type of timer?

I don’t understand what “Rule based delays” is really describing. I don’t see how 1 and 2 are in any way related to timers.

I could see a profile which would basically be the opposite side of the coin to Expire. Expire reverts an Item after a time. This would prevent an Item from updating/commanding until after a time. However, profiles only work with Channel links. You can’t apply a profile to a proxy Item, limiting its usefulness. Note, openHAB has plenty of support for handling asynchronous actions and scheduled actions.

I’m very much behind there being a Delay Action to choose from in UI rules. This is much asked for and essentially would be an implementation of Thread.sleep that one can select as an independent action. However, what could/would it do with any contextual data? It would just pause the rule for the configured amount of time.

createTimer already is how one delays actions in Scripts though and your cons seems to imply that is what you are talking about. And yes, createTimer is an openHAB Action just like logInfo, executeCommandLine, sendCommand and all the rest. createTimer doesn’t block until the scheduled action runs. I’m not sure how one can make that simpler than it already is.

createTimer(<when to run it>, <what to do>)

Example:

createTimer(now.plusSeconds(10), [ | MyItem.sendCommand(ON) ])

The Delay-implementing proxy thing is a non-starter I think. It’s just using a much more complicated brand new subsystem to do something that could already be done in a profile. That sort of thing belongs in profiles anyway.

I guess I don’t really understand the root problem you are trying to solve because the problem you describe which I’ll summarize as “openHAB has a lot of concepts with complex relationships between them” is solved by anything proposed concerning Timers. Even if you have an easier way to define a Timer, you still have to understand the relationships between your Things and Channels and Items and Rules.

Your suggestions to add a hierarchy to rules and be able to see where Items are used does seem to address your original problem but have nothing to do with timers.

First off: thanks for your detailed response!

Let’s make a quick comparison with the transform service: its assumably intended purpose is to modify the data passed to it; using it to implement delays somehow doesn’t feel right, albeit there’s no apparent issue at hand (except maybe technical aspects like blocking). I feel just the same about rules: having a rule “just” to implement a delay doesn’t feel right - rules should be about implementing “interesting” behaviours. About 35 of my 80 rules are solely implementing a delay those timer rules clutter my rules listing (that’s my user experience).

Maybe we have a different understanding about what are rules and what are scripts:

To me this is a (cleanly defined in syntax/structure) rule:

triggers:
  - id: "1"
    configuration:
      itemName: Befehl_Ausfuhrung
    type: core.ItemStateChangeTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      sink: enhancedjavasound
      sound: barking.mp3
    type: media.PlayAction

And this a script:
createTimer(now.plusSeconds(10), [ | MyItem.sendCommand(ON) ])

I was referring to parsing rules, which should be trivial; and yes, parsing scripts is much harder for many reasons (thus only linking to scripts invoked from rules).

They are not - they are about making the UI better with regard to rules&scripts. Sorry for any confusion…

Yes! That’s also my favorite approach right now.

Yes,yes,yes! Making this behaviour (delayed execution) more accessible during configuration from the UI is exactly, what I want to achieve (while discussing all technically feasible implementation alternatives) - this applies to both creating new delay behaviours but also how to “view” the setup and get an understanding that such a delayed execution is in use.

Unrelated to timers in the UI. The java rules engine has a lot of timers built in if you are interested, see details in the examples 8 - 12 in:

As far as I can see there are some different timers and concept of timers

  • Create delay (In 10 seconds turn this switch on)
  • Create or replace current delay (reschedule) (if the a timer is running reschedule, otherwise start a new timer, valid for instance for motion detection)
  • Repeating timer (for 5 times with the delay of 2 seconds in between attempt to turn this switch on)
  • Cron timers / Scheduled executions
  • Lock out timer, (You are not allowed to sounds this alarm more than once every 20 seconds)

/S

Transformation can’t be used for that since all it does is string processing. If it will block for certain time then it will either cause side effects or be cancelled. Transformation is synchronous call.
Profile however is asynchronous by nature. Profile is a valid way to do delays/filtering and so on since it has access to both source and target of command/state.
The idea of delayed command is fine as long as we don’t need to retain its execution. If we have to persist it somehow implementation gets a bit more fancy, yet still manageable.

I think I will give it a try and see if its possible to implement that. I already made some thoughts on profiles myself and see a need to make better use of them. For example to limit amount of events pushed to handler or to introduce publication threshold. In my humble opinion, if we have a closer look on how transition between Number and Quantity is being made, then we will see that “state description” does not steer only a UI label. The pattern become an hidden actor which impacts handler. In fact it took the work which should be done by profiles.

Reason why I would welcome more profiles in standard is very basic. The more standardized profiles we have the less rules and items end user have to manage. Transformations are far from being optimal for many cases. I consider pulling a javascript to scale a Number from 10 to 100 as an prime example of over engineering.
While I keep seeing a lot of “design patterns” and advice on how to do things with current OH design, I do believe it should also motivate us to think how to simplify and improve the user end. Rules and scripts are always there for complex scenarios and advanced users.

You are absolutely right: the current interface is limited - but it doesn’t have to stay that way…

And: There are always ways to “abuse” even such limited interfaces: since no transformation configuration (ie: regex) is required (data should remain unchanged, only be delayed) the delay information can be coded in the transformation parameter (where a regex would be for the regex transform for example). This would even “feel” right: was is the parameter to a delay transformation? the delay value.

The issue of blocking is technically more involved as the delay should be implemented in another execution context, presumable managed by some (new) central service instance (which would issue the command to the thing after the timer expired). And since the item, the link and the thing-channel aren’t available, those (or at least the item name) would also need to be coded into transform parameter.

That’s all a little bit hackish, but it can be implemented without (java) interface changes. Maybe interface changes for TransformationService should be considered, tough.

Yes!

The thing is that transformation does not know in which context it is working. It might be state description, it might be ie. mqtt handler reading or writing value. You can’t really know when it is called.
Personally I consider TransformationService kind of legacy of OH 1.x (and probably 2.x) which is there to serve cases where basic operation on retrieved must be made. Technically its Function<String, String>. I’d rather see its use decreasing in favor of profiles. Profile on other hand in some ways can be seen as both Consumer<State> and Supplier<State>. More importantly it knows in which direction it is begin executed. In other words if it was triggered by an handler or an item. :wink:

Profile is statefull and has 1:1 relationship with link. Because there is a factory for it it can also reference other services. Technically speaking it can hold a reference to “timer manager” who manages timers and an future which is a state to be published at certain time. In practice you can see:

Thing Handler ---> DelayProfile ---> Item
                     |      ^
                     |      | (Future)
                     v      | 
                Delay/TimeoutManager --> ScheduledThreadPool

I think it looks similar to expiration handling in some ways.

Good point - didn’t think about that.

Sounds like a valid implementation approach.

But profiles are tied to channels. Are we inventing virtual channels now? Or is this a “timer binding” idea? (See Expire binding 1.x, now defunct)

That seems to be a pretty subjective feeling. Rules are there to implement behaviors. What constitutes “interesting” is completely subjective. But since the beginning, rule have been and always were intended to implement behaviors, no matter how complex or simple they may be. When event X occurs and all you want to do is send a command to Item Y, a rule is what you’d use even though it’s just one command.

In my mind it doesn’t make OH simpler or easier to use to create alternative ways to do things like this. All it really does is just move the problem. Users now have to figure out whether or not their problem becomes interesting enough that this new way to do timers will not work or not. And if they need to grow their system later they may have to abandon their new timers for rules anyway.

I completely agree that a transformation is absolutely not the place for any sort of timers.

It sounds like you probably have lots of room for consolidation. There are tons of approaches that could be used but without seeing the rules I couldn’t recommend one. If using JS or Python for the rules language I’d probably set some custom Item metadata on the Item with the delay and the ID of the rule to call and have just 1 rule to implement the delay.

In fact, I’ve already written such a rule that might work. See https://github.com/rkoshak/openhab-rules-tools/tree/main/debounce. Though that will require proxy Items. NOTE: when OH 3.2 is released rules like this will be findable and installable just like add-ons.

But you still have to implement the “thing to do after the delay” which will still require “interesting” code which will require a rule. So all you’ve really done is eliminate one line of code from each of your rules and replaced it with a brand new concept for implementing timers. I’m not yet convinced that’s a fair trade. (I could be convinced. I’m not completely against the idea, just skeptical based on my years of experiencing helping users on this forum.)

Which is one way to define a rule. You can also have rules defined in text files. Any new concept for timers (with one exception) would have to work with both.

In UI rules there are several different types of Actions one can choose from, one of which is a Script Action. The same goes for Conditions where one can choose a Script Condition among several different types of conditions.

But what you call a “script” is part of a rule. It doesn’t exist independently. You can have a rule without a “script” but you cannot have a “script” without a rule. If you are parsing “rules” then you are by definition also parsing scripts unless you say “rules except scripts”. The stuff you see under “Scripts” in MainUI are a special type of rule that consists of only a single Script Action with the tag “Script” applied. It’s still a rule though.

And if you are ignoring Script Actions and Script Conditions then you are missing a huge area where Items are referenced and used. I don’t think you can just ignore them and have a tool that is complete. And as I said there will still be cases where you can’t identify when I Item is used by a given rule, which is probably OK. This is a case where good enough is good enough.

Finally, that syntax/structure you show only exists in MainUI. The actual syntax and structure used by rules varies based on the language and how it’s defined. But for this conversation we can probably focus on the JSON returned by the REST API as all the other text based ways to define a rule can be acquired in this form.

For example:

{
  "status": {
    "status": "IDLE",
    "statusDetail": "NONE"
  },
  "editable": true,
  "triggers": [
    {
      "id": "1",
      "configuration": {
        "groupName": "Debounce"
      },
      "type": "core.GroupStateChangeTrigger"
    }
  ],
  "conditions": [
    {
      "inputs": {},
      "id": "3",
      "configuration": {
        "type": "application/javascript",
        "script": "event.itemState.class != UnDefType.class"
      },
      "type": "script.ScriptCondition"
    }
  ],
  "actions": [
    {
      "inputs": {},
      "id": "2",
      "configuration": {
        "type": "application/javascript",
        "script": "var logger = Java.type(\"org.slf4j.LoggerFactory\").getLogger(\"org.openhab.model.script.Rules.Debounce\");\n\n// Get Metadata query stuff\nthis.FrameworkUtil = (this.FrameworkUtil === undefined) ? Java.type(\"org.osgi.framework.FrameworkUtil\") : this.FrameworkUtil;\nthis._bundle = (this._bundle === undefined) ? FrameworkUtil.getBundle(scriptExtension.class) : this._bundle;\nthis.bundle_context = (this.bundle_context === undefined) ? this._bundle.getBundleContext() : this.bundle_context;\nthis.MetadataRegistry_Ref = (this.MetadataRegistry_Ref === undefined) ? bundle_context.getServiceReference(\"org.openhab.core.items.MetadataRegistry\") : this.MetadataRegistry_Ref;\nthis.MetadataRegistry = (this.MetadataRegistry === undefined) ? bundle_context.getService(MetadataRegistry_Ref) : this.MetadataRegistry;\nthis.Metadata = (this.Metadata === undefined) ? Java.type(\"org.openhab.core.items.Metadata\") : this.Metadata;\nthis.MetadataKey = (this.MetadataKey === undefined) ? Java.type(\"org.openhab.core.items.MetadataKey\") : this.MetadataKey;\n\n// Load TimerMgr\nthis.OPENHAB_CONF = (this.OPENHAB_CONF === undefined) ? java.lang.System.getenv(\"OPENHAB_CONF\") : this.OPENHAB_CONF;\nload(this.OPENHAB_CONF+'/automation/lib/javascript/community/timerMgr.js');\n\n/**\n * Get and check the item metadata.\n * @return {dict} The metadata parsed and validated\n */\nvar checkMetadata = function(itemName) {\n  var USAGE = \"Debounce metadata should follow debounce=ProxyItem[command=true, timeout='2s', state='ON,OFF'].\"\n  var cfg = MetadataRegistry.get(new MetadataKey(\"debounce\", itemName));\n  if(cfg === null) {\n    throw itemName + \" does not have debounce metadata! \" + USAGE;\n  }\n  \n  if(cfg.value === undefined || cfg.value === null) {\n    throw itemName + \" does not have a proxy Item defined! \" + USAGE;\n  }\n  if(items[cfg.value === undefined]) {\n    throw \"Proxy Item \" + cfg.value + \" does not exist! \" + USAGE;\n  }\n  if(cfg.configuration[\"timeout\"] == undefined || cfg.configuration[\"timeout\"] === null) {\n    throw itemName + \" does not have a timeout parameter defined! \" + USAGE;\n  }\n  var dict = {\"proxy\": cfg.value,\n              \"timeout\": cfg.configuration[\"timeout\"],\n              \"command\": \"command\" in cfg.configuration && cfg.configuration[\"command\"].toLowerCase() == \"true\",\n              };\n              \n  var stateStr = cfg.configuration[\"state\"];\n  if(stateStr === undefined || stateStr === null) {\n    throw itemName + \" does not have proper debounce metadata \" + cfg.toString();\n  }\n  var split = stateStr.split(\",\");\n  dict[\"states\"] = [];\n  for(st in split) {\n    dict[\"states\"].push(split[st]);\n  }\n  return dict;\n}\n\n/**\n * Called when the debounce timer expires, transfers the current state to the \n * proxy Item.\n * @param {string} state the state to transfer to the proxy Item\n * @param {string} name of the proxy Item\n * @param {Boolean} when true, the state is sent as a command\n */\nvar end_debounce_generator = function(state, proxy, isCommand) {\n    return function() {\n        logger.debug(\"End debounce for \" + proxy + \", new state = \" + state + \", curr state = \" + items[proxy] + \", command = \" + isCommand);\n        if(isCommand && items[proxy] != state) {\n          logger.debug(\"Sending command \" + state + \" to \" + proxy);\n          events.sendCommand(proxy, state);\n        }\n        else if (items[proxy] != state) {\n          logger.debug(\"Posting update \" + state + \" to \" + proxy);\n          events.postUpdate(proxy, state);\n        }\n      }\n}\n\nthis.timers = (this.timers === undefined) ? new TimerMgr() : this.timers;\nvar cfg = checkMetadata(event.itemName);\n\nif(cfg[\"states\"].length == 0 || \n  (cfg[\"states\"].length > 0 && cfg[\"states\"].indexOf(event.itemState.toString()) >= 0)) {\n  logger.debug(\"Debouncing \" + event.itemName + \" with proxy = \" + cfg[\"proxy\"] \n               + \" timeout = \" + cfg[\"timeout\"] + \" and states = \" + cfg[\"states\"]);\n  this.timers.check(event.itemName, cfg[\"timeout\"], \n                    end_debounce_generator(event.itemState, cfg[\"proxy\"], cfg[\"command\"]));    \n}\nelse {\n  logger.debug(event.itemName + \" changed to \" + event.itemState + \" which is not debouncing\");\n  end_debounce_generator(event.itemState, cfg[\"proxy\"], cfg[\"command\"])();\n}\n"
      },
      "type": "script.ScriptAction"
    }
  ],
  "configuration": {},
  "configDescriptions": [],
  "uid": "debounce",
  "name": "Debounce",
  "tags": [
    "rules_tools"
  ],
  "visibility": "VISIBLE",
  "description": "Waits a configured time before passing an Item's state to a proxy"
}

That’s the JSON representation of that Debounce rule I linked to above.

Here is what a “Script” looks like:

{
  "status": {
    "status": "IDLE",
    "statusDetail": "NONE"
  },
  "editable": true,
  "triggers": [],
  "conditions": [],
  "actions": [
    {
      "inputs": {},
      "id": "script",
      "configuration": {
        "type": "application/javascript",
        "script": "var logger = Java.type(\"org.slf4j.LoggerFactory\").getLogger(\"org.openhab.model.script.Rules.Expamples\");\nlogger.info(\"About to test createTimer\");\nvar ScriptExecution = Java.type(\"org.openhab.core.model.script.actions.ScriptExecution\");\nvar runme = function(){ logger.info(\"Timer expired!\"); }\nvar ZonedDateTime = Java.type(\"java.time.ZonedDateTime\");\nvar now = ZonedDateTime.now();\nvar timer = ScriptExecution.createTimer(now.plusSeconds(1), runme);\n"
      },
      "type": "script.ScriptAction"
    }
  ],
  "configuration": {},
  "configDescriptions": [],
  "uid": "timerexample",
  "name": "createTimer example",
  "tags": [
    "Script"
  ],
  "visibility": "VISIBLE",
  "description": "Shows how to create a timer"
}

Notice it’s the identical structure. It just doesn’t have triggers or conditions, the single script action and the tag “Script”.

While it’s not yet a tool built into OH, there is a script that does a lot of the work of parsing through the OH entities to figure out where they are used. See [script] find items or other objects in OH environment

It would be indeed awesome if that were turned into a Developer Tool in MainUI.

When/if you ever file an issue, separate the two. It’s confusing and not really appropriate to try to deal with both at the same time in the same issue.

And I still stand by the idea that adding basically the equivalent to Thread.sleep as a separate UI Action is a good idea. So focus on that. But note that this must be very simple. All that such a UI action should do is block execution of the rule for the defined amount of time. Then you can do some simple time sequencing in a UI rule without needing to resort to “code”. This action doesn’t need to know or care about any context. All it needs to do is block execution for the configured amount of time. With that you can select it as one of the actions from the UI.

Implementing something like this will required changes to OH core as well as MainUI.

There is already an issue open for a Debounce profile which is basically what we are talking about implementing. [profiles] Proposing debouncing profile · Issue #2172 · openhab/openhab-core · GitHub

I think both a Delay Action one can choose from in the UI Rules and a Debounce profile (or what ever you want to call it, but there is already an issue for Debounce) are good ideas.

The finding which Items are used where and such are also good ideas but need to be treated in a separate issue.

I’ll try to be objective for the start. Rules are worst way to implement basic things cause:

  1. They bring higher complexity than eventual result is. Putting “consistency” without comparison of “complexity” makes it look appealing. Given there are multiple ways to write rules, each with own time management quirks, you can’t make it consistent.
  2. More over, rules are working beside item-channel relation which means that eventual event must arrive in one item in order to get processed. You can’t implement effective filtering mechanism in rules, especially if frequency of updates is pretty high.
  3. Time management within various rules is supported, yet has multiple border conditions which user has to learn prior writing his own scenario.
  4. Since introduction of profiles somewhere at the end of ESH there is a CommunicationManager component. It calls profiles which can and should do basic behavior handling.
  5. Unit testing of rules, does it exist?

Now, switching to subjective mode. I am not a progressive type kind of guy, yet point where you state that “it always has been this way” proves in some terms that the efforts or pressure to improve situation are insufficient. Some of “design patterns” are simply workarounds for openHAB shortages which were retained over years. Fact that they are called a “design pattern” in current system design doesn’t make them a final or the only one solution.

Fair enough. I will let you discuss it further.

But we trade one complexity for another.

A user starts and wants something simple. So they choose to use a profile. Then they out grow that. Sorry, you have to rewrite it now using UI rules. When they out grow that, sorry, you have to throw that away and use Blockly. Out grow that. Sorry, you have to throw that away and rewrite it in Rules DSL. Out grow that. Sorry, you have to rewrite it in JavaScript (or choose some other language of choice).

We’ve replaced the complexity of using rules to implement behaviors with a progression of different progressively more complex ways to implement it which then has to be completely thrown away when the needs become even a little more complex. Simple is fine if that’s all you’ll ever need but if you do need more complex you basically have to start over.

If I were king I’d rather effort were spent making one or two ways to implement behaviors that can handle both the simple cases in a simple way and has a path to handle the complex case. What we have now is a system that seems almost purposefully designed to create rework for everyone. And adding more “simple” features just means there are more steps that will be outgrown, thrown away and reworked.

Profiles have some significant limitations. You can only apply one Profile at a time to an ItemChannelLink. You can’t use them on Items without links. You can’t use them on Event Channels. You can’t chain them together. That doesn’t mean I don’t think they should exist nor that I think there shouldn’t be a timer type profile. In fact, Expire should be a profile except for the fact that one can’t use it unless there’s a link present.

In JavaScript, GraalVM JavaScript, Jython, jRuby (I think) and Java it’s possible. I’ve not looked into it myself but others have implemented some unit tests of rules. There have been some PRs in recent months also to address this a little as well and there was some work in the Helper Libraries. Obviously we’ve a long way to go but it’s not an area that is being ignored.

But unit tests is kind of a non-sequitur in this discussion. If we are talking about implementing simple things what do we need unit tests for? Do we need unit tests for Profiles?

I’ve never claimed otherwise. And they have nothing to do with this conversation.

I’m coming from the long term experience I’ve had trying to help users, especially new users, figure out which of the many many ways to accomplish the exact same thing they should be using in which circumstances. And year-by-year I’m not seeing the number of choices get smaller. It’s not becoming easier for users to know when they should use a Profile and when to use Expire or a Transformation or a Rule. And if they do need to use a Rule which language? UI or text based? You have two choices in JavaScript but installing one completely breaks the other. jRuby, GraalVM JavaScript, and Java rules bear almost no resemblance to each other nor to Rules DSL, JavaScript, and Jython.

It’s a f$%^ing mess and only getting worse. So I’m going to be very hard to convince that adding yet another system, approach, or concept to OH, no matter how simple, is going to make things better. It will only add yet another choice the user needs to make, a choice a new user in particular is ill equipped to make.

I’m all for adding more Profiles. I think the delay function described above should be a profile. I’m also all for adding some more simple actions to UI rules so users can go a bit further before they have to scrap it all and reimplement it in a “real language”.

The number one frustration I deal with from users is not that there isn’t a simpler way to do something. It’s that the simple things don’t do enough. By the time you make the simple things complex enough to satisfy them you’ll basically have rules.

Supportive of Rich’s view here. For a newcomer, there is a bewildering array of elements - Items, Things, Rules, channels, sitemaps, apps, profiles, cards, metadata.
“I just want to turn on the light”
Okay here’s a way.
“Cool. Can I do that only when it’s dark?”
Here’s another method.
“… but just for five minutes?”
Ah, here’s this new thing to learn
“Except on weekends”
Alright …
“And longer time if Grandma is at home”

We shouldn’t plan answers tailored to “I just want” problems.

I don’t get why you and others put ourselves into a guard man in an open source project of such a scale. Making more guarding points such this definitely reduces amount of contributions and innovation coming to the project. That’s what eventually lead to ESH definitive end as well as openHAB stagnation.
A lot of problems you talk about such as “which language” comes from basic fact that you actually force a use of external tool (programming language) to do a basic operation such as $x*10 and $x*100. So mess you talking about is just result of decade long guarding that “things been done this way”.
If you wish to continue that way, I’m fine with it, yet I will rather try making own approach than wait for consensus or permission to do things in OH.

@rossko57 Lets have a look on how simple things looks a like right now.

(U - user, O - openhab)
U: I have couple of things which have a heating period, how can I get it into system?
O: Sure, you can use binding which will map it to DateTimeType
U: Even if its not a date, but period?
O: Yes, it is capable of doing that.
U: Sure, lets go this way.
O: Actually, one of your bindings uses a quantity type which is number of seconds.
U: I don’t mind that.
O: Me too, but you can’t sum quantity and date time.
U: I understand, then lets convert DateTimeType to quantity and have seconds.
O: Great, lets write our first rule.
U: ???
O: It will allow you to convert date time into seconds.
U: Got it, lets do a rule then, where it is?
O: First, pick your destiny - DSL, Python, Ruby or JavaScript
U: I know neither
O: Pick JS, its simplest.
U: If you say so…
O: Create Number:Time temporary a.k.a ‘proxy’ item for desired value
U: Done
O: Create DateTimeType item for received value.
U: Done
O: Write the code
U: …
O: Remember that JS and Java time handling can differ.
U: Java?
O: Nevermind, just use milliseconds from date time, divide it by 1000 and create quantity and update proxy item.
U: … give me a minute
… two hours later
U: I think I have it!
O: Great, you see its simple
U: I don’t think so, I just spent 4 hours on that.
O: You’re new so you have to learn. With this mechanism you can handle any situation, isn’t it great?
U: I just needed to swap your DateTimeType to Number:Time.

… we can expand this conversations and multiply it with many “but” conditions needed to write a rule. Point is, this above conversation does not have any if, its a basic misalignment of data coming from bindings. Making things conditional will always require place to enter a logic. Unconditional and sequential state transformation require no advanced mechanism.

Please don’t get me started on a fact that for very long time, if not still, a recommended way to scale/divide a number is “state description pattern” combined with an transformation (possibly a javascript). Separate one for each “scale”.

The decisions made by the developers to proliferate and add new ways to do things makes my job harder to help support new users on this forum. So if choices are made that make my job harder I’ve pushed back.

Notice though how not once has my pushing back ever led to a change in direction. Never have I been listened to. Instead more and more different ways to do the same thing keeps getting added with no thought to what it means to the users. Each and every little new feature makes wonderful sense in isolation but when you look at things holistically all those simple little features add up to overwhelming complexity to the end users.

I can’t say I’ve ever had a developer ask “what’s the impact of this change? What does adding this feature mean to the overall usability of OH? Maybe it makes more sense to implement in a different way.” No, new features just drop on us and we scramble here on the forum to learn it and start helping user use it.

I’ve tried to raise these issues. I’ve tried to answer these questions even when they are not asked. I’ve tried to advocate for the end users. But I guess that makes be a “gatekeeper”. A pretty poor one at that since I’m mostly ignored anyway.

I don’t force anyone to use anything. I do my best to try to help people use what’s there. I try to help them navigate the huge range of possibilities. But given the choice between having one tool to learn to do such calculations and three or four to choose from, each of which has it’s own limitations, quirks, and optimal use cases ,I’m going to choose the one tool every time. It’s a lot easier to learn one moderately complex tool than all the quirks of four different ones, especially when that knowledge is transferrable to other parts of OH.

I’ve tried my best to make the developers understand the implications of the changes they want to make here and on GitHub. That was my intent in this thread: to first understand what is being proposed and then maybe guide the developer towards a path that would be easier for users to adopt. I would think the amount of time and effort I’ve spent on this forum would give me at least a little bit of credibility on that topic. I’m pretty much entirely ignored at best or met with hostility at worse, so I guess not.

I guess I was too thick to get the hint. The message is now understood. “Stay in your lane. Unless you are writing the code your opinion doesn’t matter. Your experience doesn’t matter. You’re just standing in the way.”

Though I think if you actually read and understood what I’ve said in this thread you would see we are in almost perfect agreement. Can you describe your little dialog in response to @rossko57 as anything but a mess? That’s exactly what I’ve been fighting to fix. You seem to think I like it this way. That I’m fighting to keep it this way.

Adding new ways to do things without removing the old ways isn’t going to do anything but add even more lines to that dialog. It doesn’t make anything simpler. So I have fought against it in the past. But no more. I now understand my lane and my role. Developers gonna do what developers gonna do. Don’t dare push against ideas that will make things worse. I’ll just watch from the sidelines until it all collapses under the weight of all the “simple” options.

I can only hope that with the upcoming marketplace of rules can help make it so many users simply don’t need to implement behaviors at all. They just need to install them.

For the record:

  • I like Profiles, I just wish they were more generally applicable beyond just on Links, and there should be more of them: debounce, scale, etc
  • Expire should be a Profile except for the fact that Profiles only work on links, not Items
  • I think transformations should be removed from all bindings; the Profile should be used and chaining of profiles allowed in the profile
  • Units of Measurement are more trouble than they are worth; they either need to be reworked so they work more like/can be used with regular Numbers or removed
  • There needs to be more UI rule actions so user’s don’t have to resort to code so soon
  • Blockly should be removed until it’s more complete
  • Sharable libraries should be supported in all OH rules languages
  • There should be no more than two or three choices in rules languages and they should be relatively similar in structure and use so users can convert from one language to another one
  • Ease of use within OH should trump the “purity” of the language for rules
  • Most importantly of all, knowledge should be transferable. OH should be consistent. Users should only have to learn how to add two numbers once and apply that everywhere it’s needed. Not have one way to do it in a transform and another way to do it in a Profile and N ways to do it in Rules where N is the number of different Rules languages; and then multiply that all by 2 because you have the UI and text based configs.

I’ve fought really hard for the last two. That’s what I’ve done in this thread, trying to avoid yet another way to do timers.

Anyway, I’m tired. I’m tired of being ignored. Developers gonna do what developers gonna do. There’s lots of good work going on. Maybe it’ll get better. But I’m not going to push it any more. I’ll stay in my lane.

1 Like

OK, so I am the new guy here…but I’ve been in “professional” software development (at least with regard to being paid) for decades and thus have my own set of oppinions regarding everything being discussed here.

I want to adress the recent turn in discussions from an entirely different approach, please bear with me:

  1. good usability usually means that there is a low entry barrier with a continous but low learning curve; that usually implies that only a handful of concepts must be understood by the users (some of which might assumably be general knowledge the average joe/jane/… already “has”)

  2. trying to achieve good usability is usually a long and winding journey that inevitably includes wrong turns, roadblocks and lots of things that weren’t on the roadmap or even imagined before

  3. achieving good usuability is a team effort and monocultural teams share the same fate as monocultures in real life in the long run → they fail to adapt and cease to exist

  4. maintaining good usability is even harder once feature creep starts happening that impacts the meanwhile well established fundamental concepts (back to #1)

What am I trying to say (referring back to the points above):

  1. All of your different arguments seem plausible and relevant; but the big picture is probably bigger than any one can grasp by themself - it requires teamwork.

  2. OH1 → OH2 → OH3: the changes with each major version are huge and it’s completely expectable for the current state to be in not-so-perfect-conditions. Keep going.

  3. To everyone active in the “social” ecosystem (forums/…): THANK YOU FOR YOUR WORK! And please stay involved and thus add your views and experiences (and even oppinions, thats perfectly fine) to the mix. That’s essential for the success in the long run.

  4. Strive for a good balanced approach and have your technical (and conceptual) debt always on your mind.

What I am really trying to say:

This should be fun - keep in mind that we share a common goal (or at least interest) and “working” together is the only option.

Jürgen

First off, Jurgen, welcome to the openHAB community!!!

so you know your way around

Yes!, just the talent that we need!!!
:+1:
Great discussion and great first post
yes, openHAB isn’t perfect but it is super powerful and working together, we can lower the barrier for noobs. Yannick has blessed us with a new awesome UI interface that significantly lowers that learning curve.

Right on Lukasz :+1:
everyone’s contribution is important and new perspective can bring forward progress. bravo gentlemen

I’m done with my first debounce attempt.

Sources: https://github.com/ConnectorIO/connectorio-addons/tree/master/bundles/org.connectorio.addons.profile.timer

Binary: org.connectorio.addons.feature.profile-3.0.0-20210915.kar, drop it into addons folder and look for features starting with co7io- prefix. One you look for is named co7io-profile-timer.
Above KAR contains few more profiles coming from same source repository:

  • Quantity (converts a number to a quantity and back to number reflecting specified base unit)
  • Scale (multiply values from handler/divide values from item)
  • isg (see Modbus combined registers)
  • profiles (chained profiles)

Last one is a “multi profile” profile which is a very experimental thing. It allows to chain multiple profiles one by one. They are executed in specified order from item to handler and in reverse order from handler to an item (as far I remember).
I have tested it with just most basic situations (scale + quantity). Anyhow, source code and very basic tests are there to learn. UI is obviously lost with multi profile profile so any advanced configurations will definitely not fly.

Enjoy or not. Your choice.

3 Likes