Hi, what is the best way to rollback a state of an item.
Use case:
1/ n Thermostats are in HEAT or AUTO mode (item)
2/ There is a “out of home” item → triggering the item to ON sends command to n Thermostat with value “OFF”
3/ Turning off the “out of home” item should revert the Thermostat’s to their previous state HEAT or AUTO depending on the state they were just befor turning on the out of home item.
rules.JSRule({
name: State back ,
id: Thermostats_state_back_to_original,
tags: [_DYNAMIC_],
overwrite: true,
description: Termostaty,
triggers: [triggers.ItemCommandTrigger(Radiators_AwayMode)],
execute: (event) => {
console.log(event.receivedCommand)
switch (event.receivedCommand) {
case “ON”: {
//items.getItem(`RadiatorAC${i}_homekit_TargetHeatingCoolingMode`).sendCommand("off");
//}
break;
}
case "OFF": {
// console.log("Wylaczono tryb wakacji")
//for (let i = 1; i <= RoomNumber; i++) {
//items.getItem(`RadiatorAC${i}_homekit_TargetHeatingCoolingMode`).sendCommand("auto");
// this should be changed to Last state befor console.log(items.getItem(“RadiatorAC1_homekit_TargetHeatingCoolingMode”).persistence.previousState);
//}
break;
}
}
}
});
running this gives me
2024-11-19 15:58:39.739 [INFO ] [ion.script.file.testpreviousstate.js] - previousState(t,e){return S(h.previousState(this.rawItem,…arguments))}
i can not find a example in the doc how to call it from jss
it’s a function call, not a property so at a minimum you’ll need to use parens: items.MyItem.persistence.previouaState()
the function takes up to two optional arguments: skipEqual and the name of the persistence service to use. When skipEqual is true, it will skip over any entries in the database until it finds one that is different from the Item’s current state. When false it will almost always just return the Item’s current state (i.e. the most recently saved entry into the database).
previousState() returns a PersistedItem which is an Object with a bunch of stuff. You can’t just postUpdate or sendCommand a PersistedItem. You need to get at the state.
Hopefully you realize that there’s a problem here to reach your end goal. If you pass true for skipEqual your rule will only be able to work if the state of the Item before leaving is different from the state you want to return the Item to. If it’s the same this rule won’t work.
Because of this limitation, I don’t think you can reach your end goal using just previousState().
What you really need is to do is save the state of the Item at the time that the out of home Item changes. There are lots of approaches you can do to achieve this:
Create an Item to store a copy of the state when leaving the home and restore the “real” Item using that state. Advantage is it’s simple, straight forward, and doesn’t depend on persistence beyond restoreOnStartup.
Create an Item to store a timestamp when the out of home Item changes. You can use that timestamp to pull the state of the thermostat mode Item using items.MyItem.persistedState(<timestamp>). You might need to fuzz the timestamp a bit subtracting a 100-500 msec to it to ensure that you account for how long it takes to save stuff to persistence.
Save the state as a variable in the .js file or to the cache. You could also save the timestamp and then use that to pull the state from persistence but I see no advantage to that. The problem with this approach is the saved state will not survive a restart of OH like saving it in an Item with restoreOnStartup.
You could save the pervious state of the mode Item as metadata and then retrieve it when it’s time to change back. This will survive an OH restart but it’s not really the sort of thing Item metadata is intended to be used for. But I’ve used it this way successfully before.
thank you i get the idea, i will probably use the approach with timestamp on the main item. I am still not sure how to get the persisted data corectly:
console.log("Radiators_AwayMode last change",items.getItem("Radiators_AwayMode").persistence.lastChange("mapdb"))
console.log("Radiators_AwayMode previousstate",items.getItem("Radiators_AwayMode").persistence.previousState(false,"mapdb"))
is showing
2024-11-19 18:04:48.073 [DEBUG] [pdb.internal.MapDbPersistenceService] - store called for Radiators_AwayMode
2024-11-19 18:04:48.074 [DEBUG] [pdb.internal.MapDbPersistenceService] - store called for Radiators_AwayMode
2024-11-19 18:04:48.100 [DEBUG] [pdb.internal.MapDbPersistenceService] - Stored 'Radiators_AwayMode' with state 'ON' as '{"name":"Radiators_AwayMode","state":"org.openhab.core.library.types.OnOffType@@@ON","timestamp":"Nov 19, 2024, 6:04:48 PM"}' in MapDB database
2024-11-19 18:04:48.101 [DEBUG] [pdb.internal.MapDbPersistenceService] - Deserialized 'Radiators_AwayMode' with state 'ON' from '{"name":"Radiators_AwayMode","state":"org.openhab.core.library.types.OnOffType@@@ON","timestamp":"Nov 19, 2024, 6:04:48 PM"}'
**2024-11-19 18:04:48.102 [INFO ] [ion.script.file.testpreviousstate.js] - Radiators_AwayMode last change null**
2024-11-19 18:04:48.104 [DEBUG] [pdb.internal.MapDbPersistenceService] - Deserialized 'Radiators_AwayMode' with state 'ON' from '{"name":"Radiators_AwayMode","state":"org.openhab.core.library.types.OnOffType@@@ON","timestamp":"Nov 19, 2024, 6:04:48 PM"}'
**2024-11-19 18:04:48.106 [INFO ] [ion.script.file.testpreviousstate.js] - Radiators_AwayMode previousstate {}**
2024-11-19 18:04:48.141 [DEBUG] [pdb.internal.MapDbPersistenceService] - Stored 'Radiators_AwayMode' with state 'ON' as '{"name":"Radiators_AwayMode","state":"org.openhab.core.library.types.OnOffType@@@ON","timestamp":"Nov 19, 2024, 6:04:48 PM"}' in MapDB database
Note, MapDB is always only ever going to show the current state of the Item. It only stores the current state of the Item. It only makes sense to query MapDB if you want to know when the Item became it’s current state. All other persistence queries are meaningless.
If it’s not already a ZonedDateTime, that’s what time.toZDT() does. It converts just about everything that can reasonably be converted to a ZonedDateTime.
What version of OH are you running?
Why on earth are you creating a brand new Item in the rule? There are methods on the Item to work just with the metadata:
You should not be recreating the Item every time this rule runs.
Why are you using epoch?
rrd4j isn’t going to work with a String Item. It only works with numbers.
This rule is really way overly complicated.
Option 1: Create an Item to Store Last Mode
Add an Item called Radiators_LastMode. Don’t give it a semantic tag. If you are not using a default persistence strategy make sure it’s saved on every change with restoreOnStartup.
Even that could be made shorter and more concise but I wanted to spell it out.
Since there are only two options, using a Switch statement makes the code more complex rather than less complex. We add some testing to handle the case where lastMode is NULL or UNDEF.
Option 2: Create an Item to Store the Timestamp of the Last Mode Change
Upon thinking about this I think this approach is kind of rediculous and even more strongly recommend against it than I do against the Item metadata approach. It’s way over complicated and requires you to have a usable persistence (not MapDB or rrd4j). But here is is:
We have to test that the call to persistence actually returned something and we look back 100 msec before the Item updated to avoid timing issues between when the rule ran and when the state was written to the database. Again, you can’t use rrd4j for a String Item.
The anonymous function pass to get() will get called if there is no entry in the cache for lastMode. Only this rule uses this value so we use the private cache.
Option 4: (Mis)Using Item Metadata to store the old state
Again, metadata really isn’t made for this but it could be used. But use the methods that you have to access and manipulate the metadata, don’t recreate the Item.
Should probably also test for “NULL” and “UNDEF” in all of the rules above too. Though there will be an error in the logs if the lastMode is “NULL” or “UNDEF” because these cannot be sent as a command to any Item.