Light switches

These days I am working on a Tutorial for lights and before I release it I would like some help with the buttons.

I have 6 rooms:

  1. BathRoom
  2. BedRoom
  3. MasterBedRoom
  4. LivingRoom
  5. Hallway
  6. Balcony

Each of these has at least one switch defined:

    	Contact Contact_BathroomL {gpio="pin:24 debounce:0 activelow:yes"}//SW3
    	Contact Contact_BathroomR {gpio="pin:25 debounce:0 activelow:yes"}//SW4

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)…)

Number Scene_MasterBedRoom "Scene" <sofa>

these scene always have the same start mapping:

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

Untill now i have made a seperate rule for each contact:

rule "Update bathroom light when button pressed"
when
    Item Contact_BathroomR changed from OPEN to CLOSED 
then
	
 	
	if(Scene_BathRoom.state == 0){
		if (vTimeOfDay.state.toString.contains("NIGHT")){
			Scene_BathRoom.sendCommand(2)
		}
		else{
			Scene_BathRoom.sendCommand(1)
		}
	}
	else {
		Scene_BathRoom.sendCommand(0)
	}

	
end

So can I modify this rule so that we only need 1 rule no mater how many light switches we have?

Add all contacts to the group: Group Group_LightSwitches
Add all scenes to the group: Group Group_RoomScenes

Then do something like this:

 Group_LightSwitches.members.forEach[ room |

        val sceneItem= Group_RoomScenes.members.findFirst[g | g.name.contains(room.name)] as NumberItem
        if (sceneItem!=null){
          if(sceneItem.state == 0){
		if (vTimeOfDay.state.toString.contains("NIGHT")){
			sceneItem.sendCommand(2)
		}
		else{
			sceneItem.sendCommand(1)
		}
	}
	else {
		sceneItem.sendCommand(0)
	}


So all my switches are push buttons(I mounted a spring on them…) due to the fact that you can control the lights several ways(tablet,phones,pc,alexa…)

So the last feature I would like to implement is that if there is a long press then we will dim the lights:

Dimmer Dimmer_MasterBedRoom "Dimmer [%d %%]"

can this be implemented in the same rule, and if so how?

Finally in the same rule I would also add the presence detection, we are pretty sure someone is home if a button is pressed:

Switch Presence_Home "Somebody is at home" (Group_MyOpenhab)

@rlkoshak do you have some coments on this?

There are a lot of moving pieces here and I’m not sure I understand them all.

With the new triggeringItem implicit variable I think you can do this easily, but it will require using Switchs for your buttons (I’ve been told one cannot sendCommand to a Contact). Then you can find out which Switch received the command and select the right Scene Item using Associated Items DP.

Obviously, the Group Persistence hack would probably also work in this case and you could keep the trigger the same, though will need persistence and Thread::sleep.

Or are you referencing the not the Contacts but the actual light Switches? If you are talking about the actual light Switches then what you are proposing with the forEach looks reasonable.

Possibly. My first thought would be to start a timer when the button is first pressed. If it is still pressed when the timer goes off dim a bit and start the timer again. Repeat until the button is released or the light reaches 0. The Cascading Timers or Design Pattern: Recursive Timers.

Use Generic Presence Detection, add this as a sensor. Then be sure to apply the Expire binding to time it out after a certain amount of time.

Or use the Wasp in the Box algorithm. At a high level, if the wasp in the box buzzes you know it is in the box. If the box hasn’t been opened, no matter how long ago the wasp buzzed, you know it is still in the box. Your buttons are the buzz and door sensors are your box opening sensor.

Excellent idea, I need door sensors then… But how do you handle multiple persons?

I was trying to use the associated design pattern but I did not make myself clear enough. In your DP it is required one item linked to another item… In this case this will not work, because you might have two physical switches(contact items) linked to that room(Number item/scene).

So what I am trying to do is:
Contact Contact_BathroomL {gpio=“pin:24 debounce:0 activelow:yes”}//SW3
Contact Contact_BathroomR {gpio=“pin:25 debounce:0 activelow:yes”}//SW4

to link both these to
Number Scene_BathRoom "Scene" <sofa>

So I guess what we need to do is to extractBathroom from Contact_BathroomL if we somehow figure out that it was that switch that were pressed(This is where I am stuck :wink: ), then we get the associated scene by using:

val sceneItem= Group_RoomScenes.members.findFirst[g | g.name.contains(room.name)] as NumberItem

then we check if it light is off, if so, then check the time of day, if night, then night light on…

 val sceneItem= Group_RoomScenes.members.findFirst[g | g.name.contains(room.name)] as NumberItem
        if (sceneItem!=null){
          if(sceneItem.state == 0){
		if (vTimeOfDay.state.toString.contains("NIGHT")){
			sceneItem.sendCommand(2)
		}
		else{
			sceneItem.sendCommand(1)
		}
	}
	else {
		sceneItem.sendCommand(0)
	}

So the big question is how can I get the room name from any random pressed switch(contact item) in the light_switch item?

Does this make things more clear?

The algorithm can’t really detect who or how many people are home.

The only assumption it can make is if there is manual activity inside the home (e.g. buttons physically pressed, PIR detections) and the doors remain closed, someone must be at home until such time as a door opens. Once a door opens it cannot draw any conclusions as to whether someone is home or not. It is a great way to use sensors like you are proposing in presence detection, but it is not a complete solution by any means.

1 Like

You have to come up with a set naming scheme. Then you will know for certain that the second part of the name will always be the room name.

Personally, how I would, and have implemented this sort of thing is to just create a group for each Room, add the switches to that Group, and use Associated Items DP to get the Group and send the commands to that Group.

It is slightly different but illustrates the concept:

Group:Switch:OR(ON,OFF) gLights_ALL "All Lights"
        <light>

Group:Switch:OR(ON, OFF) gLights_ON
Group:Switch:OR(ON, OFF) gLights_OFF
Group:Switch:OR(ON, OFF) gLights_ON_MORNING    (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_MORNING   (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_DAY        (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_DAY       (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_AFTERNOON  (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_AFTERNOON (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_EVENING    (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_EVENING   (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_NIGHT      (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_NIGHT     (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_BED        (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_BED       (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_WEATHER
Group:Switch:OR(ON, OFF) gLights_WEATHER_OVERRIDE

Switch aFrontLamp "Front Room Lamp"
  (gLights_ALL, gLights_ON_MORNING, gLights_OFF_DAY, gLights_ON_AFTERNOON, gLights_ON_EVENING, gLights_OFF_NIGHT, gLights_OFF_BED, gLights_ON_WEATHER)
  { channel="zwave:device:dongle:node26:switch_binary" }

Switch aFamilyLamp "Family Room Lamp"
  (gLights_ALL, gLights_ON_MORNING, gLights_OFF_DAY, gLights_ON_AFTERNOON, gLights_ON_EVENING, gLights_OFF_NIGHT, gLights_OFF_BED, gLights_ON_WEATHER)
  { channel="zwave:device:dongle:node25:switch_binary" }

Switch aPorchLight "Front Porch"
  (gLights_ALL, gLights_OFF_MORNING, gLights_OFF_DAY, gLights_ON_AFTERNOON, gLights_ON_EVENING, gLights_OFF_NIGHT, gLights_OFF_BED)
  { channel="zwave:device:dongle:node27:switch_binary" }

Switch aFamilyLamp_Override (gLights_WEATHER_OVERRIDE)
Switch aFrontLamp_Override (gLights_WEATHER_OVERRIDE)
Switch aPorchLight_Override (gLights_WEATHER_OVERRIDE)
val logName = "lights"

rule "Set lights based on Time of Day"
when
  Item vTimeOfDay changed
then
  // reset overrides
  gLights_WEATHER_OVERRIDE.postUpdate(OFF)

  val offGroupName = "gLights_OFF_"+vTimeOfDay.state.toString
  val onGroupName = "gLights_ON_"+vTimeOfDay.state.toString

  logInfo(logName, "Turning off lights in " + offGroupName)
  val GroupItem offItems = gLights_OFF.members.filter[g|g.name == offGroupName].head as GroupItem
  offItems.members.filter[l|l.state != OFF].forEach[l | l.sendCommand(OFF) ]

  logInfo(logName, "Turning on lights for " + onGroupName)
  val GroupItem onItems = gLights_ON.members.filter[g|g.name == onGroupName].head as GroupItem
  onItems.members.filter[l|l.state != ON].forEach[l | l.sendCommand(ON) ]

end

Theory of operation: I use Associated Item DP to name my “scene” groups based on possible values for vTimeOfDay. I have two groups for each ToD, one for those lights that should turn on at the start of that ToD and one for those lights that should turn off at the start of that ToD. I also have some lights I want to turn ON or OFF based on whether or not it is cloudy outside, though I’ve not included all of that logic above.

When vTimeOfDay changes, I use Associated Item DP to get the ON and OFF Groups for that ToD and send the OFF command to those members of the OFF Group and ON to those members of the ON Group.

To change which lights come on at which time of day I just change Group membership.

So, to apply this to your situation, I would just create a Group to represent the Room, bull that Group out of a parent Group, and send the appropriate command to the Group.

Hi I came across this nifty code to find which lightswitch that got pressed: Could code below work?

    rule "Any switch triggered"
    when
    	Item Group_LightSwitches received update 
    then
     
        val changedswitch = Group_LightSwitches.members.sortBy[lastUpdate].last // Get the pressed contact
        val RoomName = changedswitch.name.replace("Contact","Scene")  //change contact with Scene
        val sceneItem = Group_RoomScenes.members.findFirst[g | RoomName.contains(g.name)] as NumberItem
       
        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

So I am almost there:

 rule "Switch pressed"
when
    Item Group_LightSwitches received update 
then 

	//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.replace("Contact","Scene")

	logInfo("Light switch", "Sensor is now " + sceneItemTemp)
	

	val sceneItem = Group_RoomScenes.members.findFirst[roomName | sceneItemTemp.contains(roomName.name)] 
       
	   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 

Log output

2018-01-16 00:22:54.191 [INFO ] [.smarthome.model.script.Light switch] - Sensor is now Scene_MasterBedroomL

2018-01-16 00:22:54.191 [INFO ] [.smarthome.model.script.Light switch] - Scene is now null

What am I missing?

Probably a small Thread::sleep before getting changedSwitch to give persistence a chance to catch up with the event that triggered the Rule.

The problem with sceneItem probably has to do with either you have changed the Group and not restarted OH. I think it is still the case that there is a small bug where changes to Groups do not always get picked up until OH restarts. If not, check your naming for your Items as well as RoomName to make sure they line up and are as expected.

1 Like

well thats not the case:
I know that sceneItemTemp=Scene_MasterBedroomL but is it a string?
I only have one number item in the group: Scene_MasterBedRoom
//Group_RoomScenes.members.forEach[i|logInfo("Light switch", "Scene is now " + i.name)]

so why does this return null?
val sceneItem = Group_RoomScenes.members.findFirst[roomName | sceneItemTemp.contains(roomName.name)] as NumberItem

Yes, sceneItemTemp is a String.

I don’t understand the relevance of this statement.

All I can say is it is returning null because there is no member of Group_RoomScenes that returns true for sceneItemTemp.contains(roomName.name).

So verify that Group_RoomScenes has all the members you think it does and sceneItemTemp is indeed what you think it is.

The log print out that
sceneItemTemp is Scene_MasterBedroomL
and when I loop through all members of Group_RoomScenes and print out its name I get the only name in that group:
Scene_MasterBedRoom

The string Scene_MasterBedRoom is part of the string Scene_MasterBedroomL so why does this fail:

val sceneItem = Group_RoomScenes.members.findFirst[roomName | sceneItemTemp.contains(roomName.name)] as NumberItem

Either its the findfirst or the .contains that fails.

I guess I could rewrite it to something like this:

 Group_RoomScenes.members.foreach[roomName |

if (sceneItemTemp.subString(0,roomName.name.length()).equalsIgnoreCase(roomName.name)){
    val sceneItem=roomName as NumberItem
}
]

This is less elegant than what I am trying todo :slight_smile:

It is the case.

Scene_MasterBedRoom != Scene_MasterBedroom

You capitalized Room in one but have lowercase in the other so the contains is returning false. You either need to correct the case to match or to a toLower to eliminate case differences.

So I finally got it to work, I am unsure if the delay is needed or not. It seems to work well with the 5ms delay, but might also work without it:

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

My issue was:

  1. The default persitance engine were not set
  2. received update needs to be changed from OPEN to CLOSED
  3. .contains is NOT case sensitive

So now I have one rule instead of one rule per switch :slight_smile: I will now move on to do the same for the voice commands.
Thanks for the help @rlkoshak

1 Like