Configurable scenarios

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