Threshold Alert and Open Reminder [4.0.0.0;4.9.9.9]

Hi @rlkoshak
I have a question more related to the selected rule.
So I have installed the rules_tools and the JavaScript Addon
I have created:
-automation
–js
—node_modules
----index.js

module.exports = {
    get alerting() { return require('./alerting.js') },
    get utils() { return require('./utils.js') },
    get remsinderDoorDay() { return require('./reminder_door_day.js') }
  }

----reminder_door_day.js

var {alerting} = require('rlk_personal');
var logger = log('Open Door');

var item = items.getItem[alertItem];
if(isAlerting) alerting.sendAlert(item.label + ' is now closed', logger);
else alerting.sendAlert(item.label +' has been open for a long time!');

How can I select the rule reminder_door_day.js in the Rule Tempalte config? The rule is not listed in the selection list

Your riminder_day.js file doesn’t define a rule. When using .js files you gave to actually define the rule using JS Rule or rule builder.

Any code in a .js file that isn’t in a rule definition will only get executed when the file is loaded and isn’t a rule.

ok, understood. So I have added it into the code frame.
But is it possible to select no trigger somehow?

rules.JSRule({
  name: "Reminder - Door - Day - Alertrule",
  description: "Erinnerung falls Türen am Tag zu lang geöffnet sind",
  triggers: [triggers.SystemStartlevelTrigger(100)],
  execute: (event) => {
    var item = items.getItem[alertItem];
    if(isAlerting) alerting.sendAlert(item.label + ' is now closed', logger);
    else alerting.sendAlert(item.label +' has been open for a long time!');
  },
  tags: ["Reminder", "Door", "JSRule"],
  id: "ReminderDoorDay_AlertRule"
});

would that rule even be executed if I select it for the rule template?
I tested my config, but I get not any feedback on that a rule is triggered after timer is over

Yes, just leave the array of triggers empty.

triggers: [ ],

Yes, even with the trigger the rule would be called. I don’t see anything obviously wrong with it.

Put the threshold alert rule into debug logging. You should see it appempting to run the rule or it should be responsible obvious why it isn’t.

The logs are quite extensive.

I still try to get a general understanding of the JS modules and libs. I think I got it solved partly:
I created a folder ‘jpa_personal’ under automation/js/node_modules and created an index.js and added the alerting.js into the folder.
My file with the linked rule looks like:

var {alerting} = require('jpa_personal');
var logger = log('Open Door');

rules.JSRule({
  name: "Reminder - Door - Day - Alertrule",
  description: "Erinnerung falls Türen am Tag zu lang geöffnet sind",
  triggers: [],
  execute: (event) => {
    var item = items.getItem[alertItem];
    if(true) alerting.sendAlert(item.label + ' is now closed', logger);
    else alerting.sendAlert(item.label +' has been open for a long time!');
  },
  tags: ["Reminder", "Door", "JSRule"],
  id: "ReminderDoorDay_AlertRule"
});

So it works generally. But it seems that the alertItem is not injected by the rule:

[ion.script.file.reminder_door_day.js] - Failed to execute rule ReminderDoorDay_AlertRule: ReferenceError: "alertItem" is not defined: ReferenceError: "alertItem" is not define

I’ve never tried to call a rule defined in a .js file from another rule. I don’t know how it works. the two things I would try are:

  1. add isAlerting to the arguments: execute: (event, isAlerting) => {. The arguments are likely passed in some order so just log out isAlerting to start to see what it is.

  2. Blockly uses ctx[isAlerting] so try that.

There are significant differences between .js files rules and managed rules.

Some other things to note…

Typically you wouldn’t put a rule into a node module. I’m pretty certain that the add-on will not look for rules there. Node modules are used for functions and classes that your rules use, not to define rules. Also, you need to explicitly export anything you define in your node module.

This is all standard Node.js so you should be able to follow any of the many tutorials on creating a node module out on the Internet IIRC I cobbled together a bunch of posts on StackOverflow). There’s a basic set of steps in the JS Scripting docs as well: JavaScript Scripting - Automation | openHAB

Usually you’d only put functions and classes in your personal library that get used from multiple rules. For an example, here is my alerting personal library:

exports.sendAlert = function(message, logger) {
  var logger = (logger) ? logger : log('sendAlert');
  logger.warn('ALERT: ' + message);
  actions.NotificationAction.sendBroadcastNotification(message, 'alarm', 'alert');
}

exports.sendInfo = function(message, logger) {
  var logger =  (logger) ? logger : log('sendInfo');
  logger.info('INFO: ' + message);
}

My index.js has:

module.exports = {
  get alerting() { return require('./alerting.js') },
  get utils() { return require('./utils.js') }
}

And here is an example of a rule called by Threshold Alert that uses this sendAlert.

configuration: {}
triggers: []
conditions: []
actions:
  - inputs: {}
    id: "1"
    configuration:
      type: application/javascript
      script: >
        var {alerting} = require('rlk_personal');

        var location = items[actions.Semantics.getLocation(items[alertItem]).name];


        if(isAlerting) {
          var msg = 'The humidity in ' + location.label + ' is low at ' + alertState + '! Remember to refill the humidifier.'
          alerting.sendAlert(msg, console.info);
        }

        else {
          console.info(location.label + ' is no longer too dry at ' + alertState);
        }
    type: script.ScriptAction

As a JS Rule the above would look like:

const {alerting} = require('rlk_personal');

rules.JSRule({
  id: 'low-humidity-proc',
  name: "Low Humidity Alert",
  description: " Send an alert when the humidity get too low",
  triggers: [],
  execute: (event) => {
    const location = items[actions.Semantics.getLocation(items[alertItem]).name;
    if(isAlerting) {
      const msg = 'The humidity in ' + location.label + ' is low at ' + alertState + '! Remember to refill the humidifier.';
      alerting.sendAlert(msg, console.info);
    }
  }})

Furthermore you need to be careful in how you create a node module. If you’ve not installed “jpa_personal” using npm, the next time you install a node module or update the installed node modules npm will remove jpa_personal.

1 Like

Sorry I don’t get it.
Maybe we should start with with the first point, before going forward with the library.
In the Main UI I have configured a Rule on the basis of the Threshold Template.
I started with a rule in a .js file that I have selected in the Threshold config.
Do I understand that right, that this is not a common approach? Better would be to select a script in this config. Is it possible to create a .script file in the scripts folder based on JS?

This should be fine.

What is uncommon are:

  • putting a rule into a node_module
  • using a rule template with a rule in a .js file

Most people who bother with rule templates don’t use .js files and use managed rules instead. That doesn’t mean that using .js files won’t work or that there is something wrong with .js files over managed. It’s just those who tend to use .js files also tend to create their rules through the UI. Those who use .js files tend to either not be aware of or would refuse to use a rule template because it can only be used through the UI. That doesn’t leave too many users.

There really is no difference between a Script (i.e. rules shown under Settings → Scripts) and a Rule. All a Script is is a rule that consists of a single action (no triggers, no conditions) and is tagged with “script”. In all other respects it’s a rule.

Add the tag “script” to your rule you posted above and it will be a Script.

No, that’s only for Rules DSL.

Ok, got it. But I thin the rule in a node_module was a misunderstanding. I had just the alterting.js inside the node_module and the rule that was selected in the Template just in the automation/js folder.-
I tried now to use the manged rule approach, and pasted the code into a rule in the UI and now its working with the injection.

I really like the idea of my first approach: Using Templates inside the UI and linking to selfmade rules/scripts that are filebased. escpecially because of all the advantages you have if you create a filebased rule in Visual Studio Code.

Do you have any idea how to get the injected objects to my filebased rule?

If neither of the ideas I listed above don’t work I have no other ideas:

Unfortunately I get just an undefined back for the additional arguments
And for ctx I get back:
Failed to execute rule ReminderDoorDay_AlertRule: ReferenceError: “ctx” is not defined: ReferenceError: “ctx” is not defined
Do you know if I need to define it somehow?

Unfortunately not. It might be that .js files simply do not support having data passed to them when called from another rule.

What is from your point of view the best approach to create filebased JS rules, but be able to reuse rules that are offered in the market place? Copy the code from it into an own file?

Just copying the code from the some template isn’t going to work because they are built around calling another rule. Moving the code to a gone isn’t going fix that issue. You’d have to rework the code so everything is handled in the rule itself and adjust the error checking and such accordingly.

Personally, I would keep it easy and for rule templates that call other rules, define the rule that gets called in the UI too. Typically these rules will be really short and simple anyway since the template does all the complicated stuff for you and really common code like sending alerts will go in a personal library.

If you insist on having as much code as possible in files, you could create a “forwarder” rule in the UI that takes the passed in data, encodes it (e.g. JSON), and commands a String Item with the encoded String.

You could also potentially use the shared cache instead. Put the variables that get passed into the cache and then pull the data out of the cache in the called rule. I might experiment with this at some point to better support Rules DSL users.

But right now, either seems like an awful lot of effort to save writing a handful of lines of JS in the UI.

If anyone else is failing at installing this template from the marketplace:

2024-04-14 14:12:40.383 [ERROR] [ty.CommunityRuleTemplateAddonHandler] - Rule template from marketplace is invalid: Cannot add element, because an element with same UID (rules_tools:threshold_alert) already exists.

You need to remove the old threshold template (from OH3) before you can install the new one.

1 Like

Good catch. It is a good idea to remove the old Open Reminder template if you’ve installed that for OH 3 too.

I have now spent hours trying to get this template to work but I failed.

It’s a pretty simply use case - turning off the lights after no motion has been registered for a few minutes, but the rule simply won’t turn off the light. Here is what I get in the logs:

2024-04-15 00:12:26.902 [INFO ] [shold Alert.Toilet_presence_rule_OH4] - Rule triggered without an Item event, GroupItemStateChangedEvent checking the rule config
2024-04-15 00:12:26.903 [INFO ] [shold Alert.Toilet_presence_rule_OH4] - Cancelling any running timers
2024-04-15 00:12:26.905 [INFO ] [shold Alert.Toilet_presence_rule_OH4] - Items without thresholdAlert remPeriod metadata will not repeat alerts
2024-04-15 00:12:26.909 [INFO ] [shold Alert.Toilet_presence_rule_OH4] - No end alert rule configured, no rule will be called when alerting ends
2024-04-15 00:12:26.912 [INFO ] [shold Alert.Toilet_presence_rule_OH4] - These Items do not have thresholdAlert metadata and will use the default properties defined in the rule: Motionsensor3_occupancy
2024-04-15 00:12:26.914 [INFO ] [shold Alert.Toilet_presence_rule_OH4] - Threshold Alert configs check out as OK

After that, nothing happens.

Here is my config:

// Properties
var group = 'Toilet_presence_triggers';
var thresholdStr = 'OFF';
var operator = '==';
var comparison = (currState, threshold) => { return currState == threshold; };
var invert = false;
var defaultAlertDelay = 'PT1m';
var defaultRemPeriod = '';
var namespace = 'thresholdAlert';
var alertRuleUID = 'Toilet_lights_off';
var endAlertUID = '';
var dndStart = '00:00';
var dndEnd = '00:00';
var gkDelay = 0; 
var hystRange = '';
var rateLimitPeriod = '';
var reschedule = false;
var initAlertRuleUID = 'Toilet_lights_off';

I haven’t been able to wrap my head around how exactly the script works - especially what the three different rules (alert, initial, end) are for and which ones I need. (aside: I don’t see the benefit of combing reminders and threshold into one template just in order to increase the number of possible use cases. Already the reminder template was very abstract and difficult to fgure out for a particular use case…), but I followed the motion sensor example and that didn’t work either.

I think I will go back to creating my own set of rules for this, but I’m still curious to understand whether this is a bug or user error. The behavious makes no sense to me and there is no error in the logs…

When you run the rule manually all it does is check the config. It won’t actually do anything after that. It needs to be triggered by an Item event to actually do the job. Those log statements are what you would see when triggering the rule manually.

Based on your config, when the rule is triggered by an Item event, if the motion sensor Item becomes OFF (thresholdStr), it immediately calls Toilet_lights_off (initAlertRuleUID). If remains in the OFF state for 1 minute (defaultAlertDelay) it will call Toilet_lights_off again (alertRuleUID).

There is no reschedule or DND period to worry about.,

How does your motion sensor work? Normally one would expect it to become ON when motion is detected, right? Does it ever turn OFF on it’s own though? If so this rule template may not work because if it turns OFF before the 1 minute is up, the call the the alert rule will be cancelled.

You do not show what the Toilet_lights_off rule does. Is it smart enough to tell the difference between the initial alert and the subsequent alert?

initial: gets called immediately when the alerting condition is met (when the motion sensor becomes OFF based on the config above).
alert: gets called after the Item has remained in the alerting condition for the defaultAlertDelay time. Based on the config above that’s when the Item remains OFF for one minute.
end: gets called when the Item exits the alerting state but only if the alert rule was already called. It’s purpose is to handle a case where, for example, you’ve been alerted that a door is open and want an alert when it’s closed.

They are both the same rule. The only difference between the old threshold alert and the open reminder rule was the condition. Open reminder only supported == while threshold alert supports <, <=, ==, !=, >=, >. Literally everything else is the same.

As far as I can tell, it’s a configuration error. And it might be possible that the motion sensor you are using won’t work with the template anyway.

I would expect to see a config along the following lines:

// Properties
var group = 'Toilet_presence_triggers';
var thresholdStr = 'ON; // we care when the motion sensor detects motion
var defaultAlertDelay = 'PT1m';
var alertRuleUID = 'Toilet_lights_off'; // rule to turn off the light
var initAlertRuleUID = 'Toilet_lights_on'; // rule to turn on the light

Everything else is the default.

Thank’s so much for your detailed reply!

I realize that this was a weird version of the config to post (I got confused because I started to try pretty much any combination of settings, regardless of whether they made sense to me). It should have been:

var thresholdStr = 'OPEN';

i.e. when motion is triggered, the timer starts. The only reason I added an initAlert rule is because it said so in the motion sensor example.

And the delay is set to 1 minute instead of 10 just to facilitate testing (i.e. so that I don’t have to wait 10 minutes to see if it works).

I was wondering why it says that because I was not triggering the rule manually but by turning on the lights. Here are the triggers for the rule:

triggers:
  - id: "2"
    configuration:
      groupName: Toilet_presence_triggers
      state: OPEN
    type: core.GroupStateUpdateTrigger
  - id: "3"
    configuration:
      itemName: All_toilet_lights
      state: ON
      previousState: OFF
    type: core.ItemStateChangeTrigger
conditions: []

So it is trigger ID 3 that triggered the rule most of the time, but I have also triggered it with the motion sensor a couple of times (i.e. trigger ID 2)

It’s an Aqara P1. It does turn off (i.e. switch back to CLOSED) after, I think, 30 seconds (detection intervall is set to 30).

No, it just turns off the lights (but double-checks that the motion sensor hasn’t been triggered, i.e. is not OPEN). I wouldn’t even know how to make it see the difference between those two alerts (and neither do I understand why I would need two alerts).

configuration: {}
triggers: []
conditions:
  - inputs: {}
    id: "3"
    configuration:
      itemName: Motionsensor3_occupancy
      state: OPEN
      operator: "!="
    type: core.ItemStateCondition
actions:
  - inputs: {}
    id: "1"
    configuration:
      command: OFF
      itemName: Shellytoilet_Power1
    type: core.ItemCommandAction
  - inputs: {}
    id: "2"
    configuration:
      command: OFF
      itemName: Shellytoilet_Power
    type: core.ItemCommandAction

Yea, so that was wrong in two ways: the trigger should have been ON/OPEN, not OFF, as mentioned above. And I don’t think I need any initAlert rule at all, since I treat the rule as a timer that only does something x minutes later.

I do see in your proposed config, however, that. I could use initAlert to turn the lights on (which is currently handled by other rules). But let’s keep things simple for the time being. If I also use the rule to turn the lights on, things get complicated very quickly because there is also a door sensor that turns the lights on, but that one will usually go back to CLOSED within seconds and this should not turn the lights off. And then there is the good old light Switch, of course. These three triggers need to be “debounced” against each other (e.g. turning off the light switch and then walking out should not trigger the lights to go back on). This works flawlessly with my own set of rules in the bathroom, but I’m trying to keep all that out of the picture here until I get the template to do the most basic thing: turn off the light.

I think one of the things that confuses me might be that motion in the toilet is “the alerting condition”. There is nothing problematic about motion in the toilet. In common sense terms, the alerting condition is that the toilet lights are on (when there is no motion). But for some reason I thought that the rules requires the motion to be the alerting condition because that is what is being monitored (i.e. once triggered, nothing shlould be done as long as there is motion). Maybe that’s where my mistake is? But if I were to make “lights on” the alert condition, I’m not sure how the rule could consider the presence of absence of motion.

This (w/o the initAlertRule) I have tried many times. It doesn’t work. With this setting, the lights stay on indefinitely.

Edit:
I also tried var thresholdStr = 'OFF' (or rather: CLOSED), thinking that maybe what’s “alerting” is that there is no motion, which tells us that we need to turn off the lights - in fact, this is how I had the reminder template set up in OH3 - but it doesn’t work in OH4. The lights wont turn off either way. Not if the alert condition is OPEN and not if it is CLOSED. Isn’t this logically impossible?

See below for a tl;dr with specifically what you need to do to make it work.

OK, looking at the code I think you’ve uncovered a bug. I’m missing GroupItemStateChangedEvent in the switch statement which tells it what to do (run the config check or process the event). I think the problem is coming from the second trigger you added. Based on the name of the Item (All_toilet_lights) I’m guessing its a Group Item but you’ve used an Item Changed trigger instead of a Member of Group trigger. That’s unexpected and I never tested that case.

You should not trigger the rule with both the motion sensors and the lights. And the trigger needs to be a member of trigger, not an item state changed trigger.

It’s still a bug I’ll fix later today to handle that case (maybe someone just wants to run the rule off of just one Item) but this bug isn’t keeping the rule from working for your specific use case. Remove trigger 3.

OK, that could be a problem with just a basic GroupStateUpdatedTrigger (i.e. member of state updated) but it’s not actually a problem here because of the added state which makes it only trigger if the update was to OPEN. The CLOSED events will be ignored which is what we want.

Everything in this table gets passed to Toilet_lights_off:

Variable Purpose
alertItem name of the Item that generated the alert
alertState current state of the Item
isAlerting boolean, when true the Item meets the threshold comparison, when false the Item exited the threshold state
isInitialAlert boolean, when true the Item just now met the threshold comparison, when `false, the Item has been in this state for awhile.
threshItems JavaScript Array of JavaScript Item Objects whose state meet the alerting criteria
threshItemLabels JavaScript Array of the Item labels whose state meet the alerting criteria
nullItems JavaScript Array of JavaScript Item Objects whose state is NULL or UNDEF
nullItemLabels JavaScript Array of the Item labels whose state is NULL or UNDEF

You would have to use an Inline Script Action to access these variables. But when using Blockly you can access them using

image

Using JS Scripting you just reference the variable by name.

console.info('Toilet_lights_off was called based on ' + alertItem + ' in state ' + alertState);

So, using JS Scripting your rule could look something like this to both turn on and off the lights:

if(isInitialAlert). items.All_toilet_lights.sendCommand(ON);
else items.All_toilet_lights.sendCommand(OFF);

That will cause the light to turn on when motion is first seen and then turn the light off when it is 10 minutes later, assuming the following configuration of the threshold rule.

// Properties
var group = 'Toilet_presence_triggers';
var thresholdStr = 'OPEN'; 
var defaultAlertDelay = 'PT10M';
var alertRuleUID = 'Toilet_lights_on'; 
var initAlertRuleUID = 'Toilet_lights_on'; 

When initAlertRuleUID is called, isInitialAlert is true so that’s how the called rule knows this is when motion was seen for the very first time. Later, the same rule is called but isInitialAlert will be false which is how the called rule knows that it’s been 10 minutes since motion was seen.

Agree starting simple is best. So you can just keep your Toilet_lights_off rule as you have it now and just remove the initialAlertRule config by setting it to ''. Then your rule will only be called 10 minutes after the last motion is seen.

Your reasoning is correct. The “alert” condition is indeed the lights being on 10 minutes after the last motion was seen. In order to detect that case, you need to know when the last motion was seen which means the rule needs to be triggered based on the motion, not the light. The motion is the event that needs to be tracked.

It will work something like this:

  1. motion is seen triggering the rule
  2. the rule creates a timer that will expire in 10 minutes
  3. the rule is triggered again when motion is seen
  4. the rule reschedules the timer
  5. no motion for ten more minutes
  6. the timer finally expires and call Toilet_lights_off
  7. Toilet_lights_off sends command OFF to the lights

The config I posted above without the initialAlertRuleUID will do this.

If you were to make the “lights on” be the trigger for the rule, then the rule will call Toilet_lights_off ten minutes after the lights were turned ON no matter how they were turned on. If that was indeed what you wanted, you should just use Expire. But since you want the time to start based on the last motion seen, you need to use motion detection as the trigger.

I think your second rule trigger is one problem. It’s not clear why it’s not working beyond that but there could be a lot of things. We need debug logs. Change the logging level for “org.openhab.automation.rules_tools.Threshold Alert” to DEBUG in any of the usual ways (edit log4j2.xml, use the karaf console, use the API Explorer) or you can uncomment line 7 in the script action. The line looks like this:

//osgi.getService('org.apache.karaf.log.core.LogService').setLevel(console.loggerName, 'DEBUG');

Be sure to comment the line back out once the logging level has been set. It’s possible to totally blow away your logging config if the rule runs too fast and tries to set the level every time.

We could set it up that way but lets stick with OPEN for now. Either way will work pretty much the same. You’d just have to take account of that 30 seconds the device implements between the last motion seen and turning the Item to CLOSED. One thing that is currently preventing CLOSED from working is you’ve set the rule trigger to only trigger the rule when the Item is updated to OPEN so it will never see the CLOSED state and therefore will never have a chance to set the timer.


tl;dr:

  1. Make sure the motion sensor(s) are members of Toilet_presence_triggers
  2. The rule only has one trigger: Members of Toilet_presence_triggers updated to OPEN
  3. Group: Toilet_presence_triggers
  4. Default Alert Delay: PT1M (eventually PT10M), PT20S might be even better for testing.
  5. Alerting Rule: Toilet_lights_off
  6. All other properties leave at the default
  7. Set the logging level to DEBUG for the rule (see above)
  8. Trigger the rule by updating one of the motion sensor Items to OPEN
  9. Wait the alert delay plus a little bit
    10,. Capture the logs and post them in a reply.

If there is another bug I’d like to find and fix it. But I’ll need logs for that. If it’s a configuration problem, this may provide some insight I can use to make configuration easier. I know it’s a complicated rule but I need to balance between posting several nearly identical rule templates (and having to maintain them) with easier config with having a more complicated configuration but having a rule template that I can actually maintain.