Configurable scenarios

Hi All!
These days I’m thinking about how to make the management of one of the most useful aspects of Home Automation truly flexible. I’m talking about scenarios.

For clarity, with “Scenario” or “Scene” I mean the execution of a series of commands to the connected devices activated by a single action.

From the perspective of my experience, a scenario is really useful and usable if you let the user (read: my wife ;-)) the ability to configure or correct it according to new requirements.
Define rigidly each scenario in the code is a solution that I would avoid.

Point to clarify is the term “configurable”: what needs to be possible to change in scenario setting by UI ?
Eg:

  • Such as lights switch off / on
  • Dim level of dimmable lights or color of rgb lights
  • Any delay between commands
  • Voice confirmation to the execution order (for example scenarios that perform actions that are not readily or easily verifiable)
  • ??

From the point of view of implementation, which way choose?
(1)

  • Define a predefined number of scenarios X (eg X=10)
  • Define N * X items (N items for each scenario); each item represents the state of the light for the scene which it belongs
    (some code written while I was thinking…)
    ITEMS
    Number ScenarioNum 
    Group gScene01	
    Switch Scene01_Light01	(gScene01)
    Dimmer Scene01_Light02 	(gScene01)
    Dimmer Scene01_Light03	(gScene01)
    ...
    Group gScene02	
    Switch Scene02_Light01	(gScene02)
    Dimmer Scene02_Light02	(gScene02)
    Dimmer Scene02_Light03	(gScene02)
    ...

RULE

rule "SCENE Activation"
when
	Item ScenarioNum changed
then
   lock.lock()
   try {	    
      switch (ScenarioNum.state) {
         case 1 : {	// SCENE 01
            Light01.sendCommand(Scene01_Light01.state)				
            Light02.sendCommand(Scene01_Light02.state)			
            // ......
            // ......
         }
         case 2 : {	// SCENE 02
            // ......		
            // ......		
         }
         case 3 : {	// SCENE 03
            // ......		
            // ......	
         }
         ScenarioNum.postUpdate(0)
      }
      finally{
         lock.unlock()
      }
end

(Ok, the statements inside each case can be condensed into a lambda function …)
(If I have 20 Item for lights, I must have 20 * 10 = 200 item just to handle the scenario settings …!!)

(2)
Use JSR223 / Jython for more power and flexibility in the rules writing ?? (I want to deepen…)

And for the configuration page in the UI?
A page in sitemap like following is enough?
SITEMAP

Text label="Scene 01" {
   Frame {
      Switch item=Scene01_Light01 icon="settings"
      Slider item=Scene01_Light02 icon="settings"
      //....
      //....						
   }
}
Text label="Scene 02" {	
   Frame {
      Switch item=Scene02_Light01 icon="settings"
      Slider item=Scene02_Light02 icon="settings"
      //....
      //....						
   }
}
	

or create a small php page to be displayed in a webview …?

Before starting to implement something I would like to confront myself with the views of the comunity, usually these posts generate very very interesting discussions!
Thank you all.

Luca

So here is the problem. This is a universal problem in Home Automation and other areas where:

  1. We want automation that is flexible to do almost anything we can think of
  2. We want the automation to be configurable
  3. We want the automation to be configurable by non-technical people

Unfortunately many people do not realize that 1 and 3 are incompatible with each other. Flexible automation means coding (sure there may be cool GUIs and such but at the end of the day its coding) and coding is not friendly to non-technical people. So in order to get to 3, you have to limit the flexibility in 1.

There are lots of ways to do that and you are presenting one option (your option 2 doesn’t really say anything about what you would implement or how you would do it differently in jython verses the Rules DSL). The biggest challenge though will be defining a set of configuration parameters and presenting them in such a way that they do not overwhelm the non-technical user on the UI.

For scene activation, I’ve actually implemented something similar with my lighting, only on a much simpler scale. I don’t have dimmers, everything is triggered based on time of day, and I don’t have configuration parameters. The full example with code is here:

The tl;dr of it is I define a set number of “times of day” which represent my scenes. I then create an ON and OFF group for each time of day and add which lights I want to turn ON or OFF to the appropriate groups. In my rule I grab the appropriate group based on the value of my TimeOfDay and PreviousTimeOfDay Items and loop through them turning ON or OFF the lights as appropriate.

To start to approach what you are looking for I would add:

  • First the TimeOfDay rule needs to be updated so it no longer assumes that everything is a Switch. The instanceof command will be useful here.
    if(item instanceof SwitchItem) // do switch stuff
    else if(item instanceof DimmerItem) // do dimmer stuff
    ...
  • Next you need to create Items to store the configurations. Follow the same pattern illustrated above: create a Group to store the config Items and name the configuration Items so you can grab an Item’s config through its name. For example, if you are in Scene1 you can construct that Item’s configuation Item with something like:
val configItemName = item.name+"_"+ScenarioNum.state+"_Config"

Then you can grab that config Item out of the configuration group using:

val configItem = gConfigItems.filter[cnf | cnf.name == configItemName].head

Depending on the type of the Item you can then cast configItem to a NumberItem or SwitchItem or StringItem depending on what sort of Item you need to config the device.

  • Then just send the configed value to the Item.

  • To control which Items are a member of which group you can have a special config Item for each device that lists the groups Scenes its a member of. In the rule just check to see if the Item is a member of the Scene and proceed accordingly.

This can all be made completely generic so you need only the one rule. But Grouping and parsable consistent naming of Items is going to be key. You need to put Items in a Group so you can pull them out by name and you need to name Items such that you can construct the name of its associated Items (e.g. config Items) programmatically.

So the “server side” of what you want to do is pretty well handled. It will require a good deal of careful coding and tedious Items file configuration but very doable.

One gotcha you will face is you will need a way to bootstrap the values of the Config Items. I handle this by setting up persistence on the Config Items (a good idea as you don’t want all your configs to go away on an OH reboot) and temporarily writing a System started rule to initialize the Items with their first values. After reloading the rules file I remove the System started rule.

The big problem, as I stated above, is presenting all of these configuration parameters to the user in a way that is intuitive and not overwhelming. You need to think long and hard on this part of the problem because this is where the real challenge is. Remember the more options you provide to the user the more complicated your UI will become and the less friendly it will be to non-technical users. You will have to balance between what options you allow to be configured and the complexity of the UI.

Personally I would probably go the PHP route as you will have more flexibility in how you present the options. I would use the OH REST API to get current configs and to set new ones when they are updated. If you stick with the OH UI, I would probably greatly reduce the number of options that are configurable so you don’t have pages and pages of options. You will probably want to limit how many scenes are permitted in any case as each scene will grow the size and complexity of your config UI greatly.

For who want to have ability to adjust configuration of scenarios in “runtime”, from the app, here you will find a working example of it.
This is a stand-alone example, you have obviously to modify something to integrate it in your system.
First of all, light items, for example lights.items.

// LIGHTS ITEMS & GROUPS
Group 	gLights

Switch 		Room1_Light01			"Light 01"			(gLights)
Dimmer 		Room2_Light02			"Light 02"			(gLights)
Switch 		Room3_Light03   		"Light 03 RGB"			(gLights)
Color 		Room3_Light03Color		"Light 03 Color"		(gLights)
Dimmer 		Room3_Light03Dim 	   	"Light 03 Intensity"		(gLights)
Dimmer 		Room3_Light03Temp    		"Light 03 Color Temp"		(gLights)

Note that every lights item have name of room included in its name: this is usefull for example to ignore some rooms when save or apply a scene
Then, scene configuration items (for example scenesconfig.items). Every light item must have corresponding config item of same type. In this example every configuration item is named whit prefix “CNF_SCENE_” added to light item name.

//----------------------------------------------------------------------------------------------
// SCENE CONFIG ITEM & GROUPS
Group 		gSceneConfig
Group 		gScenePersist
// Enable Scene Edit
Switch 		CNF_SCENE_EditMode		"Edit Mode" 		<mobile>
// Number of Scene to Edit
Number 		CNF_SCENE_EditingSceneNum	"[MAP(scenemap.map):%s]" (gScenePersist)
// Light Configuration Items
Switch 		CNF_SCENE_Room1_Light01		"Light 01"		 (gSceneConfig)
Dimmer 		CNF_SCENE_Room2_Light02		"Light 02"		 (gSceneConfig)
Switch 		CNF_SCENE_Room3_Light03   	"Light 03 RGB"   	 (gSceneConfig)
Color 		CNF_SCENE_Room3_Light03Color   	"Light 03 Color"	 (gSceneConfig)
Dimmer 		CNF_SCENE_Room3_Light03Dim	"Light 03 Intensity"	 (gSceneConfig)
Dimmer 		CNF_SCENE_Room3_Light03Temp  	"Light 03 Color Temp"	 (gSceneConfig)

// Items for managing actions
Number 		CNF_SCENE_Action					
String 		CNF_SCENE_Result		"[%s]"	<graph11>
Switch		CNF_SCENE_ResultReset			

// items that will store scene configuration
Group 		gScenes
String 		CNF_SCENE_Scene1	(gScenes, gScenePersist)
String 		CNF_SCENE_Scene2	(gScenes, gScenePersist)
String 		CNF_SCENE_Scene3	(gScenes, gScenePersist)
String 		CNF_SCENE_Scene4	(gScenes, gScenePersist)
String 		CNF_SCENE_Scene5	(gScenes, gScenePersist)

// If ON all the relative room lights are ignored during scene configuration contruction 
// (So, when you will apply the scene no status of that room lights will be changed )
Group gSceneRoomIgnore
Switch 		CNF_SCENE_Ignore_ROOM1		"Ignore ROOM 1"	<impostazione> 	(gSceneRoomIgnore)
Switch 		CNF_SCENE_Ignore_ROOM2		"Ignore ROOM 2"	<impostazione>	(gSceneRoomIgnore)
Switch 		CNF_SCENE_Ignore_ROOM3		"Ignore ROOM 3"	<impostazione>	(gSceneRoomIgnore)

Switch 		CNF_SCENE_VoiceConfirm		"Vocal Confirm"

Then, rules to manage everything (for example scenes.rules):

import org.openhab.core.library.types.*
import org.openhab.core.library.items.*
import org.openhab.core.types.*
import org.openhab.core.persistence.*
import org.openhab.model.script.actions.*
import org.openhab.core.items.*
import java.util.regex.*
import java.util.String
import java.util.Calendar
import java.util.Date
import java.util.HashMap
import java.util.LinkedHashMap
import java.util.ArrayList
import java.util.Map
import org.eclipse.xtext.xbase.lib.*

// CONSTANTS
var String 	SCENE_ITEM_BASE_NAME 		= "CNF_SCENE_Scene"
var String 	CONFIG_ITEM_PREFIX 		= "CNF_SCENE_"
var String 	ROOM_IGNORE_PREFIX 		= "CNF_SCENE_Ignore_"
var String 	ITEM_SEPARATOR 			= ";"
var String 	INFO_SEPARATOR 			= ":"
var int		ACTION_RESET_CONF		= 2 
var int		ACTION_SAVE_CONF		= 3 
var int		ACTION_SAVE_ACTUAL_STATUS	= 4
var int		ACTION_SCENE_TEST		= 5

var String 	stempConfigStr 		= ""

var String 	sItemID 		= ""
var String 	sSceneItemName		= ""
var String 	itemClassName 		= ""

var boolean	bIgnoreItem 		= false
var String	sSceneConf 		= ""
var String	sMsgToSay		= ""

var Timer 	timerResetStatus 	= null


var java.util.concurrent.locks.ReentrantLock lock  = new java.util.concurrent.locks.ReentrantLock()

// System Started Rule: Initialize some items
rule "System Started"
when
	System started
then
	CNF_SCENE_EditMode.postUpdate(OFF)
	CNF_SCENE_Action.postUpdate(0)
	CNF_SCENE_EditingSceneNum.postUpdate(0)
	CNF_SCENE_Ignore_ROOM1.postUpdate(OFF)
	CNF_SCENE_Ignore_ROOM2.postUpdate(OFF)
	CNF_SCENE_Ignore_ROOM3.postUpdate(OFF)
end

rule "SCENE Configuration"
when
	Item CNF_SCENE_Action received command
then
	//lock.lock()
	//try {		
		logInfo("SCENE","---> SCENE RULE STARTED <-----")
		stempConfigStr = ""

		switch (CNF_SCENE_Action.state) {
			//--------------------------------------------------------------------------
			// RESET ACTUAL CONFIGURATION
			case ACTION_RESET_CONF: {
				logInfo("SCENE","Action Received:" + "ACTION_RESET_CONF")		
				gSceneConfig?.members.forEach[configItem|		
					itemClassName = configItem.getClass().getName()
					logInfo("SCENE","--configItem (" + configItem.name + ") is a = (" + itemClassName + ")")
					//if (configItem instanceof SwitchItem) {
					if (itemClassName.contains("SwitchItem")) {
						logInfo("SCENE","SWITCH_ITEM--ItemNAME (" + configItem.name + ")")
						configItem.sendCommand(OFF)
					}
					//else if (configItem instanceof DimmerItem){
					else if (itemClassName.contains("DimmerItem")) {
						logInfo("SCENE","DIMMER_ITEM--ItemNAME (" + configItem.name + ")")
						configItem.sendCommand(0)
					}
					//else if (configItem instanceof ColorItem){
					else if (itemClassName.contains("ColorItem")) {			
						logInfo("SCENE","COLOR_ITEM--ItemNAME (" + configItem.name + ")")
						configItem.sendCommand("0.0,0.0,0.0")
					}					
				] 
				stempConfigStr = ""
				sSceneItemName = SCENE_ITEM_BASE_NAME + CNF_SCENE_EditingSceneNum.state.toString
				gScenes?.allMembers.filter(s | s.name.contains(sSceneItemName)).forEach[sceneItem|
					sendCommand(sceneItem as StringItem, stempConfigStr)  						
				]
				
				// Update status
				CNF_SCENE_Result.postUpdate("Scene Resetted")
				CNF_SCENE_ResultReset.sendCommand(ON)
			}
			//--------------------------------------------------------------------------
			// SAVE ACTUAL CONFIGURATION TO SELECTED SCENE 			
			case ACTION_SAVE_CONF: {
				gSceneConfig?.members.forEach[configItem|						
					//logInfo("SCENE","Item Name = " + configItem.name)			
					sItemID	= configItem.name.replace(CONFIG_ITEM_PREFIX,'')
					itemClassName = configItem.getClass().getName()
					if (configItem.state.toString=="Uninitialized") {
						//if (configItem instanceof SwitchItem) {
						if (itemClassName.contains("SwitchItem")) {
							configItem.sendCommand(OFF)
						}
						//else if (configItem instanceof DimmerItem){
						else if (itemClassName.contains("DimmerItem")) {
							configItem.sendCommand(0)
						}
						//else if (configItem instanceof ColorItem){
						else if (itemClassName.contains("ColorItem")) {	
							configItem.sendCommand("0.0,0.0,0.0")
						}					
					}
					if (stempConfigStr!="") {
						stempConfigStr = stempConfigStr + ITEM_SEPARATOR
					}
					stempConfigStr = stempConfigStr + sItemID + INFO_SEPARATOR + configItem.state.toString							
				]   
				sSceneItemName = SCENE_ITEM_BASE_NAME + CNF_SCENE_EditingSceneNum.state.toString
				gScenes?.allMembers.filter(s | s.name.contains(sSceneItemName)).forEach[sceneItem|
					sendCommand(sceneItem as StringItem, stempConfigStr)				
				]
				
				// Update status
				CNF_SCENE_Result.postUpdate("Scene Saved")
				CNF_SCENE_ResultReset.sendCommand(ON)
			}
			//--------------------------------------------------------------------------
			// SAVE ACTUAL LIGHTS STATUS TO SELECTED SCENE 			
			case ACTION_SAVE_ACTUAL_STATUS: {			
				logInfo("SCENE","---> REQUIRED ACTION = [ACTION_SAVE_ACTUAL_STATUS]")
				gLights?.members.forEach[lightItem|								
					// Check if lights belong to a room that we want to ignore	
				 	bIgnoreItem = false
					gSceneRoomIgnore.members.filter(s|s.state==ON).forEach[roomItem|
						if (lightItem.name.lowerCase.contains(roomItem.name.replace(ROOM_IGNORE_PREFIX,'').lowerCase)) {
							bIgnoreItem = true				
						}
					]					
					// If it's not to be ignored..	
					if (!bIgnoreItem) {
						sItemID	= lightItem.name	
						itemClassName = lightItem.getClass().getName()						
						// check have valid state
						if (lightItem.state.toString=="Uninitialized") {
							//if (lightItem instanceof SwitchItem) {
							if (itemClassName.contains("SwitchItem")) {
								lightItem.sendCommand(OFF)
							}
							//else if (lightItem instanceof DimmerItem){
							else if (itemClassName.contains("DimmerItem")) {
								lightItem.sendCommand(0)
							}
							//else if (lightItem instanceof ColorItem){
							else if (itemClassName.contains("ColorItem")) {
								lightItem.sendCommand("0.0,0.0,0.0")
							}					
						}
						if (stempConfigStr!="") {
							stempConfigStr = stempConfigStr + ITEM_SEPARATOR
						}
						stempConfigStr = stempConfigStr + sItemID + INFO_SEPARATOR + lightItem.state.toString
					}					
				]			
				sSceneItemName = SCENE_ITEM_BASE_NAME + CNF_SCENE_EditingSceneNum.state.toString
				gScenes?.allMembers.filter(s | s.name.contains(sSceneItemName)).forEach[sceneItem|
					sendCommand(sceneItem as StringItem, stempConfigStr)				
				]

				// Write on the configuration item status of each light just saved	
				var arrItems = stempConfigStr.split(ITEM_SEPARATOR)
				for (i : arrItems) {
					val itemConfString = i.split(INFO_SEPARATOR)
					val configItemName = CONFIG_ITEM_PREFIX + itemConfString.get(0)
					gSceneConfig?.allMembers.filter(s | s.name==configItemName).forEach[configItem|				
						configItem.sendCommand(itemConfString.get(1))						
					]	
				}
				
				// Update status
				CNF_SCENE_Result.postUpdate("Scene Saved")
				CNF_SCENE_ResultReset.sendCommand(ON)
			}	
			//--------------------------------------------------------------------------
			// TEST SCENE 				
			case ACTION_SCENE_TEST: {				
				if (CNF_SCENE_EditingSceneNum.state>0) {
					CNF_SCENE_Result.postUpdate("Start Testing...")
					// get config string from scene item 	
					sSceneItemName = SCENE_ITEM_BASE_NAME + CNF_SCENE_EditingSceneNum.state.toString			
					gScenes?.allMembers.filter(s | s.name.contains(sSceneItemName)).forEach[sceneItem|
		                sSceneConf = sceneItem.state.toString
		            ]  	
					logInfo("SCENE","Saved Scene Config:" + sSceneConf)
					if (sSceneConf!="Uninitialized" && sSceneConf!="") {
						var arrItems = sSceneConf.split(ITEM_SEPARATOR)
						for (i : arrItems) {
							val itemConf = i.split(INFO_SEPARATOR)
							val itemName = itemConf.get(0)
							gLights?.allMembers.filter(s | s.name==itemName).forEach[lightItem|				
									//logInfo("SCENE","|||--->Item (" + configItem.name + ")--command (" + itemConf.get(1) + ")----")
									lightItem.sendCommand(itemConf.get(1))
									//Thread::sleep(200)				
							]	
						}
						sMsgToSay = "Ok!" 
						say(sMsgToSay)						
					} else {
						logInfo("SCENE","ATTENZIONE: SCENE NOT CONFIGURED YET")
						sMsgToSay = "Scenario " + CNF_SCENE_EditingSceneNum.state + "non ancora configurato" 
						say(sMsgToSay)						
					}
				}				
				// Update status
				CNF_SCENE_Result.postUpdate("Ok, scene Applied")
				CNF_SCENE_ResultReset.sendCommand(ON)
				
			}
		}
		CNF_SCENE_Action.postUpdate(0)	
		logInfo("SCENE","---> SCENE RULE COMPLETED <-----")
		//lock.unlock()
	/*
	}
	catch(e){
		logInfo("SCENE","--->RULE ERROR" + e.toString)
	}	
	finally{
		//lock.unlock()
	}
	*/
end



rule "SCENE Selection Change"
when
	Item CNF_SCENE_EditingSceneNum changed
then
	sSceneConf = ""
	//lock.lock()
//	try {	
		logInfo("SCENE","---------------------------------------------------------------------------")
		// Selected Scene Changed
		if (CNF_SCENE_EditingSceneNum.state>0) {
			// Selected Scene Changed, get config string from scene item 	
			sSceneItemName = SCENE_ITEM_BASE_NAME + CNF_SCENE_EditingSceneNum.state.toString			
			gScenes?.allMembers.filter(s | s.name.contains(sSceneItemName)).forEach[sceneItem|
                sSceneConf = sceneItem.state.toString
            ]  	
			logInfo("SCENE","Saved Scene Config:" + sSceneConf)
			if (sSceneConf!="Uninitialized" && sSceneConf!="") {
				var arrItems = sSceneConf.split(ITEM_SEPARATOR)
				for (i : arrItems) {
					val itemConf = i.split(INFO_SEPARATOR)
					val configItemName = CONFIG_ITEM_PREFIX + itemConf.get(0)
					gSceneConfig?.allMembers.filter(s | s.name==configItemName).forEach[configItem|				
							//logInfo("SCENE","|||--->Item (" + configItem.name + ")--command (" + itemConf.get(1) + ")----")
							configItem.sendCommand(itemConf.get(1))						
					]	
				}
			} else {
				gSceneConfig?.members.forEach[configItem|
					itemClassName = configItem.getClass().getName()	
					//if (configItem instanceof SwitchItem) {
					if (itemClassName.contains("SwitchItem")) {
						configItem.sendCommand(OFF)
					}
					//else if (configItem instanceof DimmerItem){
					else if (itemClassName.contains("DimmerItem")) {
						configItem.sendCommand(0)
					}
					//else if (configItem instanceof ColorItem){
					else if (itemClassName.contains("ColorItem")) {
						logInfo("SCENE","COLOR_ITEM--ItemNAME (" + configItem.name + ")")
						if (configItem!=null) {
							configItem.sendCommand("0.0,0.0,0.0")	
						}
					}					
				]				
			}
		}
/*		
	}
	catch(e){
		logInfo("SCENE","--->RULE ERROR" + e.toString)
	}
	finally{
		lock.unlock()
	} */
end




rule "SCENE Edit Status Reset"
when
	Item CNF_SCENE_ResultReset received command ON
then
	if (timerResetStatus == null) {
		timerResetStatus = createTimer(now.plusSeconds(5)) [|			
			CNF_SCENE_Result.postUpdate("-")
			timerResetStatus = null						
		]	
	} else {
		timerResetStatus.reschedule(now.plusSeconds(5))
	}
	CNF_SCENE_ResultReset.sendCommand(OFF)
end



rule "SCENE Edit Mode Activation Change"
when
	Item CNF_SCENE_EditMode received command
then
	if (CNF_SCENE_EditMode.state==OFF) {
		CNF_SCENE_EditingSceneNum.sendCommand(0)	
	}
end

Just a comment to the code, I don’t know why but this is not working for me:

if (configItem instanceof SwitchItem) {

so I had to use instead:

itemClassName = lightItem.getClass().getName()
if (itemClassName.contains("SwitchItem")) {

Then we need a transform file (scenesmap.map) for visualization on UI of custom scene names

0=Select scene
1=Scene 01
2=Scene 02
3=Scene 03
4=Scene 04
5=Scene 05
undefined=Select scene
-=Select scene

Persistence (for example rrd4j.persist):

Strategies {
	everyMinute		: "0 * * * * ?"
	everyHour 		: "0 0 * * * ?"
	everyDay 		: "0 0 0 * * ?"
	// if no strategy is specified for an item entry below, the default list will be used
	default = everyChange
} 
Items {
	gScenePersist*					: strategy = everyChange, everyMinute, restoreOnStartup
}

Finally, sitemap (for example scene.sitemap):

sitemap Scene label="Scene"
{ 
	Text label="Scene Config" icon="settings1" {
		Switch item=CNF_SCENE_EditMode 
		Text item=CNF_SCENE_EditingSceneNum icon="azioni" visibility=[CNF_SCENE_EditMode==ON] valuecolor=[CNF_SCENE_EditingSceneNum=="Uninitialized"="red",CNF_SCENE_EditingSceneNum==0="red",CNF_SCENE_EditingSceneNum>0="green"] {
			Switch item=CNF_SCENE_EditingSceneNum icon="azioni" label="Scene 01" mappings=[1="Select"] labelcolor=[CNF_SCENE_EditingSceneNum==1="green"]
			Switch item=CNF_SCENE_EditingSceneNum icon="azioni" label="Scene 02" mappings=[2="Select"] labelcolor=[CNF_SCENE_EditingSceneNum==2="green"]
			Switch item=CNF_SCENE_EditingSceneNum icon="azioni" label="Scene 03" mappings=[3="Select"] labelcolor=[CNF_SCENE_EditingSceneNum==3="green"]
			Switch item=CNF_SCENE_EditingSceneNum icon="azioni" label="Scene 04" mappings=[4="Select"] labelcolor=[CNF_SCENE_EditingSceneNum==4="green"]
			Switch item=CNF_SCENE_EditingSceneNum icon="azioni" label="Scene 05" mappings=[5="Select"] labelcolor=[CNF_SCENE_EditingSceneNum==5="green"]
		}
		Text label="Configure" icon="settings1" visibility=[CNF_SCENE_EditMode==ON] {
			Switch item=CNF_SCENE_Room1_Light01
			Slider item=CNF_SCENE_Room2_Light02
			Switch item=CNF_SCENE_Room3_Light03   			
			Colorpicker item=CNF_SCENE_Room3_Light03Color    	
			Slider item=CNF_SCENE_Room3_Light03Dim 	    
			Slider item=CNF_SCENE_Room3_Light03Temp    			
			
			Text label="Other"
			Switch item=CNF_SCENE_Ignore_ROOM1 
			Switch item=CNF_SCENE_Ignore_ROOM2
			Switch item=CNF_SCENE_Ignore_ROOM3
			Switch item=CNF_SCENE_VoiceConfirm
		}		
		Text label="Lights Status" icon="statoluci" {
			Switch item=Room1_Light01
			Slider item=Room2_Light02
			Switch item=Room3_Light03   			
			Colorpicker item=Room3_Light03Color    	
			Slider item=Room3_Light03Dim 	    
			Slider item=Room3_Light03Temp    			
		}
		Text item=CNF_SCENE_Result		
		Switch item=CNF_SCENE_Action label="-" labelcolor=["white"] icon="azioni" mappings=[2="Cancel",1="           ",3="SAVE"] visibility=[CNF_SCENE_EditMode==ON] 
		
		Switch item=CNF_SCENE_Action label="-" labelcolor=["white"] icon="azioni" mappings=[4="LEARN"] visibility=[CNF_SCENE_EditMode==ON] 
		
		Switch item=CNF_SCENE_Action label="-" labelcolor=["white"] icon="azioni" mappings=[5="TEST SCENE"] visibility=[CNF_SCENE_EditMode==ON]
	}
}

Now, let’s see the result:

First UI page:

Go inside:

Enable Edit Mode:

First, select scene that we want to edit:

Then back:

Now, to edit scene we have two option:
(1) Configure manually lights: menù Configure

then back again and press SAVE.
If you want to ignore a room during scene storing you can use the Ignore ROOM x switchs.

(2) “Learn” lights status from actual status: you have to switch/dim lights in your house as you want, then press button LEARN to save that configuration to be reused.

(If you want to reset a scene press *Cancel then SAVE

Finally, you can test scene by pressing TEST SCENE button: pre-saved lights status will be applyed to real ones

Enjoy!

10 Likes

Did you have the proper imports? What was the error?

I love the LEARN function. That is a really elegant way to configure a scene.

No errors, but the condition

if (configItem instanceof SwitchItem) {

is always TRUE

the others

if (configItem instanceof DimmerItem)

and

if (configItem instanceof ColorItem)

are never verified…

Imports I’m using in rule

import org.openhab.core.library.types.*
import org.openhab.core.library.items.*
import org.openhab.core.types.*
import org.openhab.core.persistence.*
import org.openhab.model.script.actions.*
import org.openhab.core.items.*
import java.util.regex.*
import java.util.String
import java.util.Calendar
import java.util.Date
import java.util.HashMap
import java.util.LinkedHashMap
import java.util.ArrayList
import java.util.Map
import org.eclipse.xtext.xbase.lib.*

I’m using openhab 1.7.0 on win7

OK, this is a very subtle thing if you are not familiar with how Object Oriented programming languages work and the relationships between SwitchItem, DimmerItem, and ColorItem. I made a detailed posting on the topic of OO in the context of the Rules language here:

The key point you should take away from that posting is that there is a hierarchy of types. In this case ColorItem inherits from DimmerItem and DimmerItem inherits from SwitchItem. What this means is you can cast to, use, and treat both a ColorItem and a DimmerItem as a SwitchItem. Further, you can treat a ColorItem as if it were a DimmerItem.

The class hierarchy looks something like:

Object
    Item
        GenericItem
            SwitchItem
                DimmerItem
                    ColorItem

Any class in this hierarchy actually is any type which is above it in the hierarchy. So a SwitchItem is a GenericItem, an Item, and an Object. A ColorItem is a DimmerItem, a SwitchItem, a GenericItem, an Item, and an Object.

Thus:

myColorItem instanceof SwitchItem  == True
myColorItem instanceof DimmerItem  == True
myDimmerItem instanceof SwitchItem == True
myDimmerItem instanceof ColorItem  == False
mySwitchItem instanceof ColorItem  == False
mySwitchItem instanceof DimmerItem == False

What what this means is that by testing for SwitchItem first it always evaluates to true because a ColorItem and a DimmerItem are also a SwitchItem.

To make this work the way you want you need to test for ColorItem first, DimmerItem second, and SwitchItem last.

Ok, now It’s clear that behavior…next time it’s better to take a look to the source!
Thank you!

Missing recording ignore items in saved configurations for reset/scene changes… I will work on it…

Excellent example! I will implement…

You have an tread created for this exemple somewhere here??