OH 3.4 history.changedSince() always returns true

Hi,

I’m trying to modify a rule that is watching a group of squeezeboxes and turns them off if they’re not playing…

Previously I checked after a timeout of 2 minutes if the player is stopped and send a power off command.
but when the track changes exactly at this moment the player reports a stop for a short moment, and I get a false positive and the power is turned of while the player is still playing.

To avoid that I’m trying to use the history.changedSince which returns a boolean…

My prolem is, the returned boolean is always true. I think this happens because I’m not using an item name but a data variable - but I’m not sure if this is really the problem.

I also made sure that the changedSince period is a bit shorter than the timeout period before the check.

const { items, rules, triggers } = require("openhab");

let watchdogTimer;

rules.JSRule({
  name: "Squeeze - Squeezebox Watchdog",
  description: "switch squeezebox off on pause after 2 minutes",
  triggers: [
    triggers.GroupStateChangeTrigger("gSqueezePlaypause", "ON", "OFF"),
  ],
  execute: (data) => {
    if (watchdogTimer) clearTimeout(watchdogTimer);
    watchdogTimer = null;

    watchdogTimer = setTimeout(fn_watchdog, 125000);

    const twominutes = time.ZonedDateTime.now().minusMinutes(2);

    console.log("Starting Watchdog")

    function fn_watchdog() {
      console.log(items.getItem(data.itemName).history.changedSince(twominutes));

      if (items.getItem(data.itemName).history.changedSince(twominutes) === false) {
        if (
          items.getItem(data.itemName.replace("Playpause", "Power")).state !==
          "OFF"
        ) {
          console.log("Power Off", data.itemName.replace("Playpause", ""));
          items
            .getItem(data.itemName.replace("Playpause", "Power"))
            .sendCommand("OFF");
        }
      }
      clearTimeout(watchdogTimer);
      watchdogTimer = null;
    }
  },
  tags: [],
});

normally I would think the changedSince should be false if the item not changed for the last 2 minutes, but it returns true no matter if a player is running or not, and the player never gets powered off…

I’m looking for help how to call the item, which initiated the rule to launch. But maybe I’m on the wrong tracks…

I think I understand what you are after but I don’t understand why you need changedSince here at all.

The rule is triggered by any change to members of the Group. Then you set a timer for two minutes. If a timer already exists, you replace it with a new timer for two minutes.

So fn_watchdog will only run when there hasn’t been a change for two minutes. You already know it hasn’t changed since two minutes so the call to changedSince is superfluous.

As for why is changedSince always return true it’s hard to say. First of all, what is data.itemName? Be aware that the will be the value it was the last time the rule ran regardless of what it was when the timer was created.

What if you adjust the timer so it’s a little more than two minutes. The timing might be just such that you are including the record that caused the creation of the timer in the first place in the call to changedSince which will of course mean that it has indeed changed since two minutes. The timer is definitely going to run the function within milliseconds of that two minute mark which we already know was the last change.

We also may need to consider the behavior of rrd4j and changedSince. I haven’t used them together so I don’t know whether the fact that rrd4j saves a record every minute “breaks” calls the changedSince. It definitely does for some other calls like lastUpdate. I do’t think it does but it’s something to check.

But all things considered, you don’t need the calls to changedSince in the first place. The fact that the timer went off at all is all you need to know that the Item hasn’t changed in two minutes. Otherwise the timer would have been rescheduled.

Hm I think that’s not right because the rule triggers only if the player changed to stop but when I start the player in between this 2 minutes again, it would be powered off, even if it’s playing again - correct me if I’m wrong…

you’re trying to say the Item is still the same at the end of the timeout, is this even true if the rule was launched by another player since the timeout started? edit: I got that wrong… sorry

as I wrote I added 5 seconds more to the timeout (125000ms) against changedSince(twominutes)…

I actually use influxdb as default persistence service.

beside that I generally like to understand how to use this history.changedSince(), just to learn more about the abilities in openhab. I like playing around with rules and learn how things actually work.

I’m not used to reading file based triggers but it looks like the rule triggers when a member of gSqueezePlaypause changed from ON to OFF (you should verify that). Since the only type of Items that can change from ON to OFF are Switch Items the assumption is the Item is a Switch.

Since NULL and UNDEF do not get saved to persistence the only time where the changedSince will return true in those two minutes is if the triggering Item changed to ON in those two minutes. So why wait or mess with changedSince at all? If the Item changes from OFF to ON, if the timer exists cancel it. Then the timer will only run when the player has been OFF for two minutes.

If the rule triggers again the timer gets canceled and rescheduled. I originally missed that it only triggered when the item changes to OFF.

The code posted above uses exactly two minutes for both the amount of time for the timer and the changedSince.

You will probably have to look at what’s actually being saved to the database (e.g. when was the last update when the timer runs?) to know what’s going on.

it is a switch item

The problem I see is I got multiple rules that stop one player or playergroup and start another… in this cases the previous player wouldn’t have been shut down, because the timer is resetted, and another item is the last one calling.

sorry no :wink: it’s exactly like I wrote 125000 ms - not 120000… and that’s 5 seconds off - see above please

But the persistence seems to work, otherwise the boolean would be false or undefined all the time?
I see no errors in the logs… the syntax looks good so far I think.

Because the response was always true I thought this suggests that the state changed even if no player changed its state - this is what I don’t understand and I’m thinking about how to debug this…

thanks Rich :wink: I think this is a good way to start…

You have the same problem with your current approach too. But you can solve that by setting a separate timer per player. You can use a dict keyed with the triggeringing Item’s name. Then use a simple create/reschedule for OFF and cancel for on.

You could also use the Denounce rule template and not need to code anything. I think you’ll need to change one line in it to command instead of update the proxy item, which in this case will be your Player Item. But that will handle all the book keeping and fingers and stuff for you.

This really isn’t fundamentally different from the Denounce or Motion Sensor Timer design patterns.

Given that, the Open Door Reminder rule template might be another one that could work. This one will call a rule you when a boolean item (e.g. switch) remains in a given state for too long with the name of the item. So you could configure a rule based on this template on your gSqueezePlaypause to call a rule you write that can send the associated Player Item the OFF command only when the Play pause Item remains OFF for two minutes.

Again, the rule template handles all the timers and book keeping and all you need to write is what to do when the item stays off for too long.

When it can’t find anything in the database it returns null.

No I don’t, the described problem would only occur when I cancel the timeout also when a player starts like you proposed earlier. My current approach does nothing when a player starts playing, only the old player or playergroup will be powered off after 2 minutes. That’s completely different.

For sure, there are many ways that to lead to rome… but what the heck is wrong with my approach? In theory this is a very easy way to make sure the player didn’t change its state since the timer started.
I don’t know why you’re resisting so hard against my approach. I like to learn new things, like I mentioned before - but this means trial & error and maybe needs some helping hands sometimes.

But in general after understanding the logic and the syntax I’ll have learned more than I’d have with just using rule templates or blockly puzzles. But it feels like beeing pushed in the direction you want instead of helping me reaching the goal of my approach.

No front, but please try to understand it from my perspective.

Actually I came here to ask a question about history.changedSince and instead of concentrating on this we’re discussing if I need it at all and if it’s not even better to use rule templates… I mean this is fine, but not helpful to understand how changedSince works, what are the up- and downsides of using this, how can I make sure the actual player is used etc. …the use of a dict key seems useful information to follow my approach. But I never done this before in js - which I’m trying to learn more about at the moment.

Nevertheless I appreciate your help!

And your originally posted code always cancels the timer. So if a toner exists in one player and another turns off you’ll lose the old timer.

I only purposes the earlier approach because that’s what your code does. When the rule triggers, if the timer exists, it cancells it.

If you need the rubber to work for multiple players, you must have a separate timer for each. You can’t do this with only one timer.

It won’t work as designed using only one timer.

And there is something going on with changed since making it always return true which is most likely because the way persistence is saving data means it won’t work that way.

If and only if you only have one player.

Because it’s an XY Problem. You are asking about changedSince but your give what your age trying to achieve even if that worked your role won’t do what you want. And to make the rule do what you want, you won’t need changedSince in the first place.

But you know best. Good luck! You’ll figure this out eventually.

Ok this is what I ended up with…

I don’t know what exacty lets history.changedSince always be true, even if the item in my influxdb haven’t changed… so I decided to completely rethink the rule and here’s the code:

const { items, rules, triggers } = require("openhab");

// map
let players = new Map();

rules.JSRule({
  name: "Squeeze - Squeezebox Watchdog",
  description: "switch squeezebox off on pause after 2 minutes",
  triggers: [
    triggers.GroupStateChangeTrigger("gSqueezePlaypause", "ON", "OFF"),
  ],
  execute: (data) => {
    console.log("Starting Watchdog");
    let player = items.getItem(data.itemName);
    const actualplayer = player.toString().split(" ", 1)[0].replace('Playpause','');

    if (players.has(actualplayer)) {
      clearTimeout(players.get(actualplayer));
    }

    players.set(
      actualplayer,
      setTimeout(() => {
        if (player.state === "OFF") {
          console.log(actualplayer + " turned off!");
          items.getItem(actualplayer + "Power")
            .sendCommand("OFF");
            clearTimeout(players.get(actualplayer));
        } else {
          console.log(actualplayer + " still playing!");
        }
        players.delete(actualplayer);
      }, 120000)
    );
  },
  tags: [],
});

Here’s a short explanation how the code works…

The map players in this rule serves as a way to store a reference to each active timeout. It maps the actualplayer string to the returned value from setTimeout, which is representing the timeout.

The actualplayer is the name of the Squeezebox player, which is extracted from the player.toString() string and processed to remove the “Playpause” suffix.

The map is used in the following ways:

  1. When the Squeezebox Watchdog rule is first triggered, the players map is checked to see if it already contains an entry for actualplayer.
  • If an entry exists, it means there is already an active timeout for this player, so the previous timeout is cleared using clearTimeout(players.get(actualplayer)).
  • If there is no entry for this player, a new timeout is set using setTimeout.
  1. When the timeout expires, the callback function passed to setTimeout is executed.
  • If the player’s state is “OFF”, it means that the Squeezebox player has not been played for 2 minutes, so the power for the player is turned off using items.getItem(actualplayer + "Power").sendCommand("OFF").
  • If the player’s state is not “OFF”, it means the Squeezebox player is still playing.

NOTE: the Timeout stores the player variable when it’s set, when the player variable changes, the state inside the Timeout is still the one stored before.

  1. The entry for this actualplayer is then removed from the map using players.delete(actualplayer).

The players map is used to keep track of the active timeouts for each Squeezebox player, so that multiple timeouts are not created for the same player, and so that previous timeouts can be cleared when a new timeout is created for the same player.

This is no answer to the initial question of this thread but a solution for my problem.

I’m unsure if I should mark this thread as solved, because this is no solution to get history.changedSince working…

If you’ve verified that there is no entry in the database in the time passed to changedSince it’s probably worth filing an issue.

You can easily set up an easy test to verify the behavior. Create a Test Item that gets saved to InfluxDB.

var item = items.getItem('Test');
item.postUpdate( (item.state == "ON") ? "OFF" : "ON") );
setTimeout( () => {
  console.log('changedSince true: ' + items.getItem('Test').history.changedSince(time.toZDT('PT-1M'));
  console.log('changedSince false: ' + items.getItem('Test').history.changedSince(time.toZDT('PT-30S')); 
}, time.toZDT('PTM50S').getMillisFromNow());

You can set that in a Script and manually trigger it, or since it’s a temporary rule use the Scratchpad.

That will toggle the Test Switch Item, and set a timer for 50 seconds from now to call changedSince with a one minute ago. We know that the Item changed 50 seconds ago so changedSince from one minute ago should return true, and 30 seconds ago should return false.

However, is the database where you are looking for the data configured as the default persistence? If not, you’re querying the wrong database. That still doesn’t explain the behavior but it does mean you might be looking in the wrong place for the records.

1 Like

hmm…

I’ll check this but since all my other calls to history are working e.g. history.previousState() I think my influxdb is up and running without errors. influxdb is my default persistence service.

we will see if this is really a serious bug…

thanks :wink:

I changed your code because there was an error using your time variables…

instead I used this:

var item = items.getItem('Test');
var minute = time.ZonedDateTime.now().minusMinutes(1);
var seconds = time.ZonedDateTime.now().minusSeconds(30);
item.postUpdate( (item.state == "ON") ? "OFF" : "ON");
setTimeout( () => {
  console.log('changedSince true: ' + item.history.changedSince(minute));
  console.log('changedSince false: ' + item.history.changedSince(seconds)); 
}, 50000);

and here are the log entries:

2023-02-01 22:19:08.701 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'Test' changed from OFF to ON

==> /var/log/openhab/openhab.log <==
2023-02-01 22:19:58.776 [INFO ] [automation.script.ui.TestChangeSince] - changedSince true: true
2023-02-01 22:19:58.796 [INFO ] [automation.script.ui.TestChangeSince] - changedSince false: true

my persistence is set to store every item, it uses the * wildcard.

seems like the changedSince is broken somehow…

would be nice if you or someone could test this on their installation, to be sure this not only occurs on my system…

Yes, that was a typo. It should be PT50S. It’s an ISO8601 duration string that time.toZDT() can convert to a ZonedDateTime. I also had some missmatched parens.

I only use rrd4j so I’m not sure the test would work the same. But this corrected version produces…

var item = items.getItem('TestSwitch');
item.postUpdate( ((item.state == "ON") ? "OFF" : "ON") );
setTimeout( () => {
  console.log('changedSince true: ' + items.getItem('TestSwitch').history.changedSince(time.toZDT('PT-1M')));
  console.log('changedSince false: ' + items.getItem('TestSwitch').history.changedSince(time.toZDT('PT-30S'))); 
}, time.toZDT('PT50S').getMillisFromNow());
2023-02-01 14:41:10.148 [WARN ] [ore.internal.scheduler.SchedulerImpl] - Scheduled job 'org.openhab.automation.script.ui.scratchpad.timeout.1' failed and stopped
org.graalvm.polyglot.PolyglotException: Java heap space
        at org.rrd4j.core.Archive.fetchData(Archive.java:303) ~[?:?]
        at org.rrd4j.core.RrdDb.fetchData(RrdDb.java:986) ~[?:?]
        at org.rrd4j.core.FetchRequest.fetchData(FetchRequest.java:163) ~[?:?]
        at org.openhab.persistence.rrd4j.internal.RRD4jPersistenceService.query(RRD4jPersistenceService.java:330) ~[?:?]
        at org.openhab.core.persistence.extensions.PersistenceExtensions.getAllStatesBetween(PersistenceExtensions.java:1263) ~[bundleFile:?]
        at org.openhab.core.persistence.extensions.PersistenceExtensions.internalChanged(PersistenceExtensions.java:307) ~[bundleFile:?]
        at org.openhab.core.persistence.extensions.PersistenceExtensions.changedSince(PersistenceExtensions.java:286) ~[bundleFile:?]
        at org.openhab.core.persistence.extensions.PersistenceExtensions.changedSince(PersistenceExtensions.java:259) ~[bundleFile:?]
        at java.lang.invoke.DirectMethodHandle$Holder.invokeStatic(DirectMethodHandle$Holder) ~[?:?]
        at java.lang.invoke.LambdaForm$MH/0x00000001015e0c00.invoke(LambdaForm$MH) ~[?:?]
        at java.lang.invoke.LambdaForm$MH/0x0000000100d41400.invokeExact_MT(LambdaForm$MH) ~[?:?]
        at <js>.changedSince(/openhab/conf/automation/js/node_modules/openhab/items/item-history.js:68) ~[?:?]
        at <js>.:=>(<eval>:7) ~[?:?]

:scream:

After a restart I get the same thing. There appears to be a bug in rrd4j.

Note: I’m on the 4.0 SNAPSHOT

I tried mapdb…

2023-02-01 14:51:41.812 [INFO ] [nhab.automation.script.ui.scratchpad] - changedSince true: true
2023-02-01 14:51:41.817 [INFO ] [nhab.automation.script.ui.scratchpad] - changedSince false: true

So this looks to be a more universal problem.

Thanks a lot, good to know… can you tell me where exactly to file an issue for this? I never done this before… is there a github page or something?

See How to file an Issue.

This needs to be filed on core. Reference this thread and mention that it happens in 3.4.1 and the 4.0 snapshot.

1 Like

Thanks :wink:

Ooops looks a bit overwhelming should I look for issues in openhab-js or is it better to look in openhab-core??

No, this is definitely a problem in core. There isn’t an issue on this (I follow the issues in core fairly closely).

thanks i use core…

I don’t believe it’s a bug in core.

  1. Core has its unit test for this feature openhab-core/PersistenceExtensionsTest.java at a50a0886e89d47c1fcfd80dad6f80496a31dc1b4 · openhab/openhab-core · GitHub

  2. I tested this in jruby too:

SwitchItem1.toggle
SwitchItem1.persist(:influxdb)
after 30.seconds do
  logger.info "Changed since 40s: #{SwitchItem1.changed_since?(40.seconds.ago, :influxdb)}"
  logger.info "Changed since 20s: #{SwitchItem1.changed_since?(20.seconds.ago, :influxdb)}"
end

Result:

12:15:05.460 [INFO ] [enhab.automation.jrubyscripting.test3] - Changed since 40s: true
12:15:05.462 [INFO ] [enhab.automation.jrubyscripting.test3] - Changed since 20s: false
  1. I doubt that this is even a bug in jsscripting, as I’d imagine it would just be a simple call to core from jsscripting library.