Magical light scenes

tutorial
lights
skatun
Tags: #<Tag:0x00007f1e668fe978> #<Tag:0x00007f1e668fdd20> #<Tag:0x00007f1e668fd9b0>
(Kim Skatun) #1

Introduction

This is the lighting tutorial (DESIGN PATTERN) which is a part of my tutorial list, I keep coming back to them, improving them add functionality to them etc, so if you like it then hit “like” it and you will get notified when changes occurs. I would like to thanks @rlkoshak for the help with this one.

Please see Design Pattern: What is a Design Pattern and How Do I Use Them for details on what a DP is and how to use them.

FIRST DRAFT

Please keep in mind that the tutorial is not quite finished and a few details might be missing. I decided to post it rather sooner than later. Comments are welcome!

!Update 23.04.2019
Added Screenshot of current layout in Basic UI

!Update 24.04.2019
Added the new rule that useses triggeringItem, this became available in OH2.3 and makes the rules way simpler

Implementation in openhab

In this tutorial I would like to share my generic approach for lighting that might also be useful for others, its quite long so lets just jump into it.

We will discuss:

  • Layout of room
  • Types of lights
  • Layout of light
  • Light controllers
  • Rules
  • Light hardware

Layout of room

I have 6 rooms:

  1. Bathroom
  2. Bedroom
  3. MasterBedroom
  4. Livingroom
  5. Hallway
  6. Balcony

My layout can been seen below

Your layout will be different, but the important thing is not to call two rooms the same and have a naming pattern that is easy to understand! Also remember that everything is case sensitive so be consequent with the names!

User interface

So what we figured out over the years, is to keep the UI as simple as possible, how do we know? Well feedback from some over 60 differnt AirBnb people. So this is how our display looks like for every room:

We do have admin sitemap where you can can control each light individually used for debugging, see what time of day it is and so on, we try to keep the everyday UI as simple as possible.

In the hallway for instance I only have one light, a simple on/off light so in the UI I do not have dimmer, and mood selector buttons of course.

Groups

So now you need to create a group for each room with your naming convection. The naming convetion is really important, pick one and stick to it. Its case sensitive so keep that in mind. I know its common to use gLivingRoom however with feedback from @rlkoshak I prefer to use Group_xxx so that it easily can be split later in the rules by “_”. For items, they all start with room name, so if you wnat use this DP, then follow same scheme otherwise you might have to modify rules to fit your naming convention

Group Group_Livingroom
Group Group_MasterBedroom
Group Group_Hallway
Group Group_Bedroom
Group Group_Bathroom
Group Group_Balcony

Types of lights

In openhab we have 3 types of lights:

  • Switch
  • Dimmer
  • Color

So you need to map your light to one of those types, an RGB light(Phillips hue) will then be mapped to a Color Item, a normal on off lamp will be mapped to Switch(SonOff MQTT) and a dimmable light will be dimmer(Zwave)

You can name your light items and map whatever binding to them as you like, my generic rule will handle them all. The important thing is that you add the room group and room scene(more on this later) it should belong to.

Layout of lights

When you build your own Taj Mahal you should think about how you should light it, and how you would use your place. A good light setting will make your home more welcome and relax and is something that is easy to forget when planning the home and is it hard to fix afterwards in a clean way.

So in my case I was not quite sure of how I would arrange furniture when I started the building progress so I ended up with a grid pattern of 120cm of the downlights(If I would start over again, I would probably change this slightly).

So now when you have the light plan ready, you should decide which light modes(Scenes) you would like. Here come the trick, you need to create a group for each light mode you would like EXCEPT OF: Every room needs to have at least ON/OFF of lights,( I personally also like to have NIGHT mode ). So go ahead and create light modes( such as reading, dinner, relax…) and decide which lights that should be activated in that mode,. When you have decided on these modes we need to create these groups :

Group Group_Livingroom_Lights_Night
Group Group_Livingroom_Lights_Dinner
Group Group_Livingroom_Lights_Wall
Group Group_Livingroom_Lights_FirePlace
Group Group_Livingroom_Lights_Kitchen
Group Group_Livingroom_Lights_TV
Group Group_Livingroom_Lights_Cooking
Group Group_Livingroom_Lights_Reading
Group Group_Livingroom_Lights_Lounge 

and go back to your items(your lights) and add those groups to the item definition.

Color LL27 "LL27"(.....Group_LivingRoom,Group_LivingRoom_FirePlace,Group_LivingRoom_TV) {dmx="CHANNEL[55,56,57:100000]"}

You also need to make a map file and set it up in your sitemap, more about this later.

Light controllers

So I assume that you would like to control your light, there are several way to do so:

  • Computer(Laptop,PC,Tablet)
  • Physical device(Light switches, motion detectors,dimmers etc)
  • Voice(Alexa, google,siri…)

Now comes the nice part(At least I think so…) For each room I have defined a scene selector(Even if the hallway only have one light(Simple switch)…)

So as mentioned above we created some groups representing the modes we liked to have in our home, so now is the time to implement this:

SCENES

So to actually turn on and off the lights in a ROOM we do not use a switch item but rather a number item which again will be set by selection item, physically press of a switch or voice command.

So in the lights.items files you need to create number item for each room and also this needs to belong to the Group Group_Scenes which will be used later in our generic rule:

	Number Livingroom_Scene "Scene Selector"(Group_Scenes)
	Number MasterBedroom_Scene "Scene Selector"(Group_Scenes)
	Number Bathroom_Scene "Scene Selector"(Group_Scenes)
	Number GuestBedroom_Scene "Scene Selector"(Group_Scenes)
	Number Hallway_Scene "Scene Selector"(Group_Scenes)
	Number Balcony_Scene "Scene Selector"(Group_Scenes)

these scene always have the same start mapping:

  • 0=OFF
  • 1=ON
  • 2=NIGHT
  • 3=…
  • 4=…

so you need to manually fill in the names of your modes starting from 2, remember that for some rooms maybe just ON/OFF is OK, then you just add those. So in the sitemap the file would like this:

Selection item=Scene_Livingroom label="Scene Selector" mappings=[0="OFF", 1="ON", 2="AWAY, 3="DINNER", 4="READING",5="FIREPLACE", 6="BAR", 7="PARTY", 8="TV",9="COOKING", 10="WALL", 11="KITCHEN", 12="LOUNGE"] 

For the physical devices such a single push button it might be hard to cycle through different scene, and these might therefore be better off just using as controlling all lights on/off in the given room. In my setup I did run 4 wires to each button locations so in theory I could swap single button to double or triple button, where button presses can be used as scene selector and/or dimming function. But for now I leave this as an optional feature for others to implement.

So for the physical devices add them to the room(Group) they should control as well as make a group for all light switches:

Group Group_LightSwitches

Each of these has at least one switch defined:

    	Contact Contact_BathroomL (Group_LightSwitches,Group_BathRoom)  {gpio="pin:24 debounce:0 activelow:yes"}//SW3
    	Contact Contact_BathroomR (Group_LightSwitches,Group_BathRoom)  {gpio="pin:25 debounce:0 activelow:yes"}//SW4

Presence sensor such as PIR motion sensor can be set up in the same way to control the lights.

I am only discussing alexa here, since that the system I am familiar with. Voice commands are in Openhab supported through the hue emulator or the skill. This means we need to create dummy items for each light mode, I have made an excel tool which can create all these items and corresponding rules for you or you can create them like this:

Switch Alexa_Livingroom_Reading "Reading" (Group_AlexaEmulator) [ "Lighting" ]
Switch Alexa_Livingroom_Dinner "Dinner" (Group_AlexaEmulator)  [ "Lighting" ]
Switch Alexa_Livingroom_Tv "TV" (Group_AlexaEmulator)  [ "Lighting" ]
Switch Alexa_Livingroom_Lounge "Relax" (Group_AlexaEmulator)  [ "Lighting" ]
Switch Alexa_Livingroom_FirePlace "Fireplace" (Group_AlexaEmulator)  [ "Lighting" ]
Switch Alexa_Livingroom_Cooking "Cooking" (Group_AlexaEmulator)  [ "Lighting" ]
Switch Alexa_Livingroom_Kitchen "Kitchen" (Group_AlexaEmulator)  [ "Lighting" ]
Switch Alexa_Livingroom_Party "Party" (Group_AlexaEmulator)  [ "Lighting" ]
Switch Alexa_Livingroom_OnOff "Living Room"  (Group_AlexaEmulator) [ "Lighting" ]

##Rules
Now the fun begins, we add some logic to our lights! So basically the idea is that you can add lights, change light modes and so on, but the rules always stay the same and does not need to be touched to get the system to work.

We will have 3 seperate rule files:

  1. LightControl.rules
  2. LightSwitches.rules
  3. AlexaEmulator.rules

LIGHT RULE

So we start out with the simple rule that that sends it ON/OFF then later we add the rule for dimming and changing colors(Only needed for those who has RGB lights, dimmable or those with wharm/cold white lights)

SCENE CHANGER

import org.eclipse.smarthome.model.script.ScriptServiceUtil
rule "Update lights"
when
    Member of Group_Scenes changed 
then
    logInfo("Notification", "Scene changed: " + triggeringItem.name.split("_").get(0))
    val roomName= triggeringItem.name.split("_").get(0) as String
    val group =ScriptServiceUtil.getItemRegistry.getItem("Group_"+roomName +"_Lights") as GroupItem
    val dimmer=ScriptServiceUtil.getItemRegistry.getItem(roomName +"_Dimmer") as DimmerItem

    if(group === null) {
        logError("admin", "Cannot find Item " + "Group_"+roomName +"_Lights")
        return;
    }

    
    if (triggeringItem.state ==0) {
        logInfo("Notification", "Group to set OFF: " + group.name)
        group.members.forEach[ i | i.sendCommand(OFF) ]
        if(dimmer !== null) {
            dimmer.postUpdate(OFF)
        }
    }
    else if (triggeringItem.state ==1) {
        logInfo("Notification", "Group to set ON: " + group.name)
        group.members.forEach[ i | i.sendCommand(ON) ]
        if(dimmer !== null) {
            dimmer.postUpdate(ON)
        }
    }
    else{
        val sceneGroup = ScriptServiceUtil.getItemRegistry.getItem( "Group_"+roomName +"_Lights_" +transform("MAP", "Livingroom.map", ""+triggeringItem.state) ) as GroupItem
        logInfo("Notification", "Scene to set: " + sceneGroup.name)
        if(sceneGroup === null) {
            logError("admin", "Cannot find Item " + "Group_"+roomName +"_Lights_" +transform("MAP", roomName +".map", ""+triggeringItem.state))
            return;
        }

        group.members.forEach[ i |
            if (i.getGroupNames.contains(sceneGroup.name)){
                i.sendCommand(ON)
            }
            else{
                i.sendCommand(OFF)
            }
        ]
    }
end

DIMMER CHANGE

Then we have the case for those who has dimmable lights which might want to like to dimm the lights:

rule "Update dimmers"
when
	Member of Group_Dimmers changed 
then
	logInfo("Notification", "Dimmer changed: " + triggeringItem.name.split("_").get(0))
	val roomName= triggeringItem.name.split("_").get(0) as String
	val group =ScriptServiceUtil.getItemRegistry.getItem("Group_"+roomName +"_Lights") as GroupItem
	val dimmer=ScriptServiceUtil.getItemRegistry.getItem(roomName +"_Dimmer") as DimmerItem

	if(group === null || dimmer === null) {
        logError("admin", "Cannot find Item " +roomName +"_Dimmer or Group_"+roomName +"_Lights")
        return;
    }

	group.members.forEach[ i | 
		// We need to grab active scene, transform it to group
		val scene =ScriptServiceUtil.getItemRegistry.getItem(roomName +"_Scene") as NumberItem 
		//Just a nifty touch to update UI
		//if (scene.state==0 ){
			//scene.postUpdate(1)
		//}
		val sceneGroup = ScriptServiceUtil.getItemRegistry.getItem( "Group_"+roomName +"_Lights" +transform("MAP", roomName+ ".map", ""+scene.state) ) as GroupItem
		// only dim lights if its a member of the selected group
		if (i.getGroupNames.contains(sceneGroup.name)){
			//Coloritems and dimmer items
			if(i instanceof ColorItem || i instanceof DimmerItem) {
				logInfo("Notification", "Icolor/dimmeritem: " + i.name)
				i.sendCommand(dimmer.state)	
			}
			//Switch item
			else if(i instanceof SwitchItem) {
				logInfo("Notification", "Iswitchitem: " + i.name)
				// So normal lights get switched off when dim value passes 50%
				if (dimmer.state<50){
					i.sendCommand(OFF)
				}
				//it could have been dimmed below 50% and then dimmed back up again
				else{
					i.sendCommand(ON)
				}
			}
			//In case someone added light group to non light item
			else{
				logError("admin", "item in wrong group " +i.name)
			}	
		}		
	]
end 

###Color change
And finally we have those who would like to change colors of their lights depending on the mood, time of day etc…
Here its two method, one using a colorpicker other to use preselected colors. I am tempted to get away from the colorpicking, because I rarly use it. Also the preselected colors could be added to the scenes…

Color Picker

rule "Update color pickers"
when
	Member of Group_ColorPickers changed 
then
	logInfo("Notification", "Color changed: " + triggeringItem.name.split("_").get(0))
	val roomName= triggeringItem.name.split("_").get(0) as String
	val group =ScriptServiceUtil.getItemRegistry.getItem("Group_"+roomName +"_Lights") as GroupItem
	
	if(group === null ) {
        logError("admin", "Cannot find Item Group_"+roomName +"_Lights")
        return;
    }

	group.members.forEach[ i | 
		// We need to grab active scene, transform it to group
		val scene =ScriptServiceUtil.getItemRegistry.getItem(roomName +"_Scene") as NumberItem 
		//Just a nifty touch to update UI
		//if (scene.state==0 ){
			//scene.postUpdate(1)
		//}
		val sceneGroup = ScriptServiceUtil.getItemRegistry.getItem( "Group_"+roomName +"_Lights" +transform("MAP", roomName+ ".map", ""+scene.state) ) as GroupItem
		// only dim lights if its a member of the selected group
		if (i.getGroupNames.contains(sceneGroup.name)){
			//Coloritems
			if(i instanceof ColorItem) {
				logInfo("Notification", "Icolor/dimmeritem: " + i.name)
				i.sendCommand(triggeringItem.state)	
			}
		}		
	]
end 

Mood Selector

rule "Update mood"
when
	Member of Group_Moods changed 
then
	logInfo("Notification", "Color changed: " + triggeringItem.name.split("_").get(0))
	val roomName= triggeringItem.name.split("_").get(0) as String
	val group =ScriptServiceUtil.getItemRegistry.getItem("Group_"+roomName +"_Lights") as GroupItem
	
	if(group === null ) {
        logError("admin", "Cannot find Item Group_"+roomName +"_Lights")
        return;
    }

	group.members.forEach[ i | 
		// We need to grab active scene, transform it to group
		val scene =ScriptServiceUtil.getItemRegistry.getItem(roomName +"_Scene") as NumberItem 
		//Just a nifty touch to update UI
		//if (scene.state==0 ){
			//scene.postUpdate(1)
		//}
		val sceneGroup = ScriptServiceUtil.getItemRegistry.getItem( "Group_"+roomName +"_Lights" +transform("MAP", roomName+ ".map", ""+scene.state) ) as GroupItem
		// only dim lights if its a member of the selected group
		if (i.getGroupNames.contains(sceneGroup.name)){
			//Coloritems
			if(i instanceof ColorItem) {
				logInfo("Notification", "Colortem: " + transform("MAP", "Moods.map", ""+triggeringItem.state))
				i.sendCommand(transform("MAP", "Moods.map", ""+triggeringItem.state))	
			}
		}		
	]
end 

** Rules for physically devices **
So lets move on to the lightSwitch(Add motion sensor as same as physically switch) …

rule "Switch pressed"
when
    Item Group_LightSwitches changed from OPEN to CLOSED
then 
    Thread::sleep(5)
    Presence_Home.sendCommand(ON)
    val changedSwitch = Group_LightSwitches.members.filter[switchPressed | switchPressed.lastUpdate != null].sortBy[lastUpdate].last
    if(changedSwitch == null) logWarn("FILE", "Something is amiss, no Item has a valid lastUpdate!")

    val sceneItemTemp = changedSwitch.name.toUpperCase().replace("CONTACT","SCENE") as String
    val sceneItem = Group_RoomScenes.members.findFirst[roomName | sceneItemTemp.contains(roomName.name.toUpperCase())] as NumberItem
    
    logInfo("Light switch", "Scene is now " + sceneItem)
    if (sceneItem!=null){
        if(sceneItem.state == 0){
            if (vTimeOfDay.state.toString.contains("NIGHT")){
               sceneItem.sendCommand(2)
            }
            else{
                sceneItem.sendCommand(1)
            }
        }
        else {
            sceneItem.sendCommand(0)
        }
    }
end

VOICE COMMANDS
And finally the voice command

rule "Switch pressed"
when
    Item Group_AlexaEmulator received command
then 
    Thread::sleep(5)
    Presence_Home.sendCommand(ON)
    val changedSwitch = Group_AlexaEmulator.members.filter[alexaItem | alexaItem.lastUpdate != null].sortBy[lastUpdate].last
    if(alexaItem == null) logWarn("FILE", "Something is amiss, no Item has a valid lastUpdate!")

    val sceneItemTemp = changedSwitch.name.toUpperCase().replace("ALEXA","SCENE") as String
    val sceneItem = Group_RoomScenes.members.findFirst[roomName | sceneItemTemp.contains(roomName.name.toUpperCase())] as NumberItem
    
// Extract the SceneMode from SceneItemTemp
val sceneMode = 
    logInfo("Light switch", "Scene is now " + sceneItem)
    if (sceneItem!=null){
        if(sceneItem.state == 0){
            if (vTimeOfDay.state.toString.contains("NIGHT")){
               sceneItem.sendCommand(2)
            }
            else{
                sceneItem.sendCommand(1)
            }
        }
        else {
            sceneItem.sendCommand(0)
        }
    }
end

Requirements

You need to have the Map transformation to get this to work
You need to install the Alexa skill to get voice command to work

Common bugs

  1. UI does not get updated to ON if scene is OFF and you select dimmer to dimm lights to ON
  2. Changing colors does not set Dimmer value in UI
  3. No error checking if map files exist, plan to implement a default one if non exist
  4. Can not define mood colors for scene preset, would like to do this in map file somehow
  5. Openhab can not use map file in sitemaps
  6. autogeneration of selection item for sitemap and virtual items for voice assistance based on javascript from map files does not exist

Related Design Patterns(Link to Rich’s DP for now)

Design Pattern How Used
Design Pattern: How to Structure a Rule Overall structure of the first complex rule follows this DP
Assocaited Items Gets the Alerted Item based on the Online Item’s name
Working with Groups in Rules Get the Items around the time the rule was triggered, loop through those Items
24 Likes

Scenes in OH
Change light scene using Alexa
[Rules DSL] Get item from string name!
Design Pattern: Working with Groups in Rules
(Rich Koshak) #2

Another great posting! Keep them coming.

0 Likes

(Michiel) #3

Great post.

0 Likes

(Hasturo) #4

Hi,

i really need some help here. I dont get the dependencies between the Groups. Where is the Link between a Scene_ Room, Type Number, which you use at the Panel and the rest?

Best Regards, Hasturo

0 Likes

(Kim Skatun) #5

The scene item is an instance of the Number type. Same as simple on/off lamp is an instance of a switch type and so on…

So Number Scene_BathRoom can be set to any integer number 0,1,2,3…15
Predefined is 0 which is off 1 is on… rest is custom…like if you want only group of ur lights to be on you can set that to 3, or if you want to dim ur lights to 50% that can be scene 4 and so on…
Or what do you mean?

0 Likes

(Hasturo) #6

Ok, but how is the relation to the Room and Scene Groups i created? I have a Room Group(Group_Livingroom). A Scene Group(Group_Livingroom_Night), a Item of Type Number with the Name Scene_Livingroom but no other Content?

Ive created a Panel Selector for the Scene Livingroom Item with the Selection 0=OFF, 1=ON,2=Night

I created the LightControl.rules File from the Example and modified to rgbLights.Apply(Group_Livingroom,Group_Livingroom_Night). Also i have to add “Item” to the when clause.

I dont understand how “Scene_Livingroom” is related to the “Group_Scene” clause in the Rule File.

0 Likes

(Kim Skatun) #7

Hi @Hasturo,
The tutorial were written for OH2.1 and before @rlkoshak recomended me to change to roomname_ whatever syntax. So these days I am rewriting it. Main thing is that now we can use Member of Group as rule trigger and use triggeringitem, as well as use split((triggeringItem,"_").get(0) to get the associated item: Design Pattern: Associated Items

However it should work as intended as listed above,

The idea is that you put your lights into groups, all lights in livingroom belongs to that group, however you might have a group called livingroom_relax which then is a subset of your lamps in the livingroom.

The scene item is just for the sitemap which then lets you select the correct scene, the idea is that to make it as generic as possible 0,1,2 is predefined, above you add ur relax scene and so on…

The lambda function(where all the magic happens) takes the room group, and the scene group. The third parameter is hsbValue, maybe @rlkoshak can come with some input here. The idea is that in many cases(like your relax mode) you would like to dim your light to 50% and turn the light more yellow. hsbType can store both these information with a single item, hence the third parameter. I am unsure if I can make it optional somehow, to simplify it for lights with just ON/OFF.

I expect to update this tutorial by easter! But feel free to use it as it is for now.

0 Likes

(Rich Koshak) #8

As far as I know lambdas do not support optional arguments. But even if it did, an optional parameter really means that some default value is used instead of one passed into the lambda. Given that’s the case, just pass in a default value for those places where one would not otherwise pass in the HSBType.

0 Likes

(Robbe) #9

Great tutorial! Thank you!
I managed to get this working with limited OpenHab knowledge.

There are however some points that weren’t clear, would be nice if it was updated in the tutorial:

  1. I’ve spent alot of time to get the transform/map working. In the end I found that I had to install the transform/map binding.
  2. It wasn’t clear from the tutorial that I had to create a roomname.map file for each room. I also didn’t know how to do it.
  3. I’ve changed this in the code:
//val sceneGroup = ScriptServiceUtil.getItemRegistry.getItem( "Group_"+roomName +"_Lights_" +transform("MAP", "Livingroom.map", ""+triggeringItem.state) ) as GroupItem
val sceneGroup = ScriptServiceUtil.getItemRegistry.getItem( "Group_"+roomName +"_Lights_" +transform("MAP", roomName+".map", ""+triggeringItem.state) ) as GroupItem

transform(“MAP”, “Livingroom.map”, “”+triggeringItem.state) ==> transform(“MAP”, roomName+".map", “”+triggeringItem.state)

Hope this will help other people.

0 Likes

(Kim Skatun) #10

Thanks for the feedback, I am currently updating the tutorial from OH2.1 to OH2.5 and the only thing left to explain better is the map files, switches and voice commands. I hope to get to it this week. Let me know if you need any further assistance @RobbeM

0 Likes