Persistence based, GUI reconfigurable scenes

Tags: #<Tag:0x00007f61790d9b48> #<Tag:0x00007f61790d9a80> #<Tag:0x00007f61790d9968>

Hi everybody,

as proposed in the thread Setting light themes in rules today I will share my scene approach with you.

Introduction

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.

Greetings
Sebastian

8 Likes

Thanks so much for sharing, if I understand correctly then you are still assigning states to individual lights rather than a group?

May just be that I am misreading though?

Yes, you are right.

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.

1 Like

I like the solution, thanks for sharing. :slight_smile:

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?

Here is the sitemap line for loading a scene:

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

Greetings
Sebastian

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.

Any tips?

I see, it’s done via influx presistence. Now I do see some values in that DB, but when I try to load a scene, I’m getting this error:

Rule 'WzSceneLoad': cannot invoke method public abstract org.eclipse.smarthome.core.types.State org.eclipse.smarthome.core.persistence.HistoricItem.getState() on null

Still working my way thru

Yes, exactly. The values to load are stored via the definition in influx.persist. I use the strategy everyChange.

Regarding your error:
What is the time stamp of die DateTimeItem you used?

OH reboot needed!

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!

:ok_hand:
Glad you got it working.

I also use groups to define the items that should be persisted.

Items {
   pGraph* : strategy = everyChange
}

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.

Example goup items:

Group:Switch:OR(ON, OFF)            OfficeScene_SW_Group
Group:Dimmer:AVG                    OfficeScene_DIM_Group
Group:Color                         OfficeScene_HSB_Group

And example rule

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
2 Likes