After some time of fiddling with different approaches how to implement scenes in OH2 I think I found a way that is quite easy to implement with the text based rules engine. It is independent on the actual used technology (I use Milight and Tinkerforge) and needs nothing but a query-able persistence, 2 quite simple rules and of course some items. And you can even reconfigure the scenes from any GUI (i.e. save new states for all scene relevant items)
How it works
General information
All relevant items for the scene are persisted on every change (I use influxdb, but every query-able persistence can be used)
Define virtual number item (vWzScene) for the scene number
Following rules are a little bit simplified compared with my actual setup to show the concept
I use this currently for light scenes, but of course you can also add items for rollershutters, heating or whatever to the scenes
Load scene
If scene number is changed the scene is loaded by triggering a āLoad Scene Ruleā (WzSceneLoad)
To do so all scene relevant items are set to their historic state queried from persistence according to a time stamp
This time stamp is represented by a DateTimeItem (vWzScene_X, on per scene number)
All vWzScene_X items must belong to a group (gWzScene)
Save/Reconfigure scene
If you want to change the state of one or more items for a scene you now can modify the single items via GUI
By clicking the button for vWzScene_Save a āSave Scene Ruleā (WzSceneSave) is triggered
This rules writes the current date/time to the respective DateTimeitem (vWzScene_X) and so updates the time stamp
Of course all the time stamp items also have to be persisted and restored on startup (I use mapdb)
Limitations, Known issues
If your persistence strategy deletes or aggregates old data (for example with influxdb retention policy) you have to ensure not to delete data that will be queried
This can either lead to a wrong scene setup (wrong state for on or more items) or an error in the rule
To solve this issue you can e.g. update the time stamp automatically every time a scene is loaded (not tested, my database is not aggregated at the moment)
Another issue can be that in the loading rule the states for different items are changed too fast and your actual technology (in my case the Milight bridge) canāt follow
In this case add a small Thread::sleep between the sendCommand statements
Code
.items
// Groups
Group gWzScene "Scene LivingRoom"
Group pSave "Persistence Save" // mapdb -> everyChange, restoreOnStartup
Group pGraph "Persistence Graph" // influxdb -> everyChange
// LivingRoom Lights
Dimmer aWzDimmer "Dimmable Light" (pSave, pGraph) { channel="<whatever you use>" }
Color aWzRgb "RGB Light" (pSave, pGraph) { channel="<whatever you use>" }
Switch aWzSwitch "Switchable Light" (pSave, pGraph) { channel="<whatever you use>" }
// Scene items
Number vWzScene "Scene LivingRoom [%d]" (pSave)
Switch vWzScene_Save "Scene LivingRoom save" (gWzScene)
DateTime vWzScene_0 "Time 0 [%1$td.%1$tm.%1$tY %1$tT]" (gWzScene, pSave)
DateTime vWzScene_1 "Time 1 [%1$td.%1$tm.%1$tY %1$tT]" (gWzScene, pSave)
DateTime vWzScene_2 "Time 2 [%1$td.%1$tm.%1$tY %1$tT]" (gWzScene, pSave) // add as many scenes as you want to have
.rules
rule "WzSceneSave"
when
Item vWzScene_Save received command ON
then
val sceneItem = gWzScene.members.filter[i|i.name == "vWzScene_"+vWzScene.state].head as DateTimeItem
sceneItem.sendCommand(new DateTimeType(now.toString()))
vWzScene_Save.postUpdate(OFF)
end
rule "WzSceneLoad"
when
Item vWzScene received command
then
val timestamp = (gWzScene.members.filter[i|i.name == "vWzScene_"+vWzScene.state].head as DateTimeItem).state.toString
aWzDimmer.sendCommand(aWzDimmer.historicState(parse(timestamp),"influxdb").state as PercentType)
aWzRgb.sendCommand(aWzRgb.historicState(parse(timestamp),"influxdb").state as HSBType)
aWzSwitch.sendCommand(aWzSwitch.historicState(parse(timestamp),"influxdb").state as OnOffType)
end
So I hope this is helpful for some of you and would be happy to hear your feedback.
Iām assigning the historic states to individual items. Also thought about iteration over a group that contains all relevant items. But I got some issues with the different item types as Iām casting the historic states to different types (PercentTyp, HSBType, ā¦) Not really sure if I have to do this. or if there is a solution to do it with a group. But as I currently only have 5 items I skipped this task for now.
This is a cool concept for scene rules, but Iām a little confused on how exactly it works. Specifically, how do the rules know which scene (vWzScene_0, vWzScene_1, vWzScene_1) you are saving or loading? I must be missing something. Can you share a sitemap snippet?
Selection item=vWzScene label="Szene [MAP(wz_scene.map):%s]" mappings=[0="00: alles aus", 1="01: Standard", 2="02: Guten Morgen", 3="03: nur RGB", 4="04: Kino"]
How it works is as follows:
By using the selection via the sitemap the Number item vWzScene gets a command with the respective numerical value (0ā¦4 or whatever numbers of different scene you wanna have)
This triggers the rule
The time stamp is read from the DateTime items vWzScene_<No> with this line of code
val timestamp = (gWzScene.members.filter[i|i.name == "vWzScene_"+vWzScene.state].head as DateTimeItem).state.toString
It says (more or less): Search in the group gWzScene for a DateTimeItem with its name <<concatenated from the string āvWzScene_ā + state of item vWzScene>> (which gives e.g. vWzScene_0 if state of vWzScene = 0) and use the state of this item as time stamp
The following commands load the historical values for the different items based on this time stamp.
Saving a new scene works more or less the same.
One hint: To get the time stamp I would today write the rule a little bit different. I was not aware of this possibility a year ago but it feels more clean for me. Donāt know if it really has any impact on performance or whatever.
You can write the following instead of the line shown above.
import org.eclipse.smarthome.model.script.ScriptServiceUtil
val timestamp = (ScriptServiceUtil.getItemRegistry.getItem("vWzScene_"+vWzScene.state)).state.toString
Thanks for the clarification. Still not seeing where youāre savinig the state of the items. I see where you load them:
aWzDimmer.sendCommand(aWzDimmer.historicState(parse(timestamp),"influxdb").state as PercentType)
aWzRgb.sendCommand(aWzRgb.historicState(parse(timestamp),"influxdb").state as HSBType)
aWzSwitch.sendCommand(aWzSwitch.historicState(parse(timestamp),"influxdb").state as OnOffType)
But donāt see how to save the states to the influx DB.
Seems to be working like a champ now. I have a lot of items here so Iām going to implement groups in these rules versus listed each item. Iāll post my final code when Iām done. Thanks!
So instead of commanding each item in the load scene rule, I reconfigure it to use groups instead. This is a little easier for me to keep track of when you have dozens of items.
rule "Office Scene Load"
when
Item OfficeScene_No received command
then
if(OfficeScene_No.state != 0) { //office not in auto, set the lights
val timestamp = (ScriptServiceUtil.getItemRegistry.getItem("OfficeScene_"+OfficeScene_No.state)).state.toString
logInfo("Scenes","OfficeScene_"+OfficeScene_No.state + " started on " + timestamp)
OfficeScene_HSB_Group.members.forEach[hsb | { hsb.sendCommand(hsb.historicState(parse(timestamp),"influxdb").state.toString) } ]
OfficeScene_DIM_Group.members.forEach[dmr | { dmr.sendCommand(dmr.historicState(parse(timestamp),"influxdb").state.toString) } ]
OfficeScene_SW_Group.members.forEach[item | { item.sendCommand(item.historicState(parse(timestamp),"influxdb").state as OnOffType) } ]
}
end
I use openhab 3 and i get this error, when i try to save a scene:
2020-12-19 14:49:29.765 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'Szenen-1' failed: 2020-12-19T14:49:29.764312+01:00[Europe/Berlin] is not in a valid format. in Szenen
I think its a problem with the new time date format in OH3.
2020-12-19 18:42:33.413 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID āSzenen-2ā failed: Text ā2020-12-19T18:41:12.654244+0100ā could not be parsed, unparsed text found at index 29 in Szenen
Looking for some time for a good way to implement scenes, this seems very interesting. I receive however the same error code. Anyone a idea what is going on?
2020-12-19 18:42:33.413 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID āSzenen-2ā failed: Text ā2020-12-19T18:41:12.654244+0100ā could not be parsed, unparsed text found at index 29 in Szenen