Presence Simulation

I don’t know that it is required. It may not be any longer. NCO was having errors just using .state and following watou’s advice and adding the .toString seemed to fix it.

A lot of times things do not work, we try something, and that fixes it and we never really find out why.

Theoretically, it should work without the .toString but perhaps there is one or more Item types that will not accept generic State objects as an argument. Passing a String is universally supported though.

The ? means to skip past the line if the value is null. I personally avoid its use most of the time because if something is null I want there to be an error.

The best you can do is add a Switch and an if statement in your rule so the body of the existing rule only executes when the Switch is ON.

The Experimental Rules Engine will be able to handle this use case though.

1 Like

Small update/improvement to the Presence Simulation rule for anyone who want to re-use it:

Added the 3 variables on top to be easier to adjust the settings.

  • days = 7 (in days of course) is how long back in time the rule will go to pull state information from the items to be “replayed” to simulate presence
  • delay = 50 (in milliseconds) is the time gap between each sendCommand (to avoid overloading the bus)
  • persistence = "influxdb" (text) is the selected persistence service from which to pull the historical data. This is useful if you use a default persistence like rrd4j or mapdb and have influxdb as a 2nd tier persistence service.
var int days = 7
var int delay = 50
var String persistence = "influxdb"

rule "Simulate Lights on Vacation"
when
        Time cron "0 0/5 * * * ?"
then
        if (Vacation_Mode.state == ON) {
                gGF_Lights.members.forEach(light,i |
                        createTimer(now.plusMillis(i*delay)) [|
                        logInfo("Vacation",light.name + " got " + light.historicState(now.minusDays(days), persistence).state)
                        light.sendCommand(light.historicState(now.minusDays(days), persistence).state.toString)
                        ]
                )
        }
end
5 Likes

Hi @Dim,
I was looking at your code, and I’m probably going to use it for my presence simulation (so thank you!).

However, I do have some questions:
If I understand your code correctly, your code will set all lights (in group gGF_Lights) to their historic state. So if a light was OFF two weeks ago, and it is still OFF now, the light will still get the command “OFF”. Correct? I don’t know how many lights you have in your group, but wouldn’t it generate a lot of obsolete commands every five minutes?

The same remark goes for your logInfo. It would also write something to the log when there was no difference between the historic state and now.

Would it be possible to adapt to this code (I still need to set up a separate group and store in persistence, so I can’t verify for myself)?

        if (Vacation_Mode.state == ON) {
                gGF_Lights.members.forEach(light,i |
                        if (light.historicState(now.minusDays(days), persistence).state != light.state) {
                               createTimer(now.plusMillis(i*delay)) [|
                               logInfo("Vacation",light.name + " got " + light.historicState(now.minusDays(days), persistence).state)
                               light.sendCommand(light.historicState(now.minusDays(days), persistence).state.toString)
                        ]
                }
                )
        }

Hi @Dries !

I copied it from @rlkoshak, so he owns the “Intellectual Property Rights” :stuck_out_tongue:

Absolutely! This is my next step: to implement an if {} statement to check the current state versus the historical and to suppress the command if equal. Based on: (again… @rlkoshak recommendation :slight_smile:). I will post soon an update for this.

I believe that this will work… I will test it out and let you know!

what I tried to do (but failed so far) is to declare a variable(?) at the start of the rule for the “historicState(now.minusDays(days), persistence).state.toString” since it repeats in the rule and I wanted to use a “oldState” definition to clean up the statements :slight_smile:

1 Like

This is great Work. Thank you for sharing.

A quick question. I’m pesisting Lights Switches as well as Dimmers in a Group I call GLight.

Will the above turn on/off lights as well as set the Dimmers to the historical state?

It should be able to do this without any problem.

If there are issues (due to different type of items in the Group), you can split the items in subgroups
Then, you can create a new .forEach block and run it on the Dimmers subgroup.

This code seems to works, including only sending changes when there are changes between the current state and the historical state.

However can anyone tell me how the i in ... forEach(lamp, i | ... works? Is this some automagical incrementer? I’ve not been able to find anything in the documentation about this.

var int days = 7
var int delay = 50

rule "Simulate_lights"
when
    Time cron "0 0/1 * * * ?"
then
    if (Away_Button.state == ON)
    {
        GLights.members.forEach(lamp, i | 

            if (lamp.state.toString != lamp.historicState(now.minusDays(days)).state.toString)
            {
                createTimer(now.plusMillis(i * delay)) [|
                    if (lamp instanceof DimmerItem) 
                    {
                        logInfo("Dimmer",  "Changing dimmer: " + lamp.name + " from " + lamp.state.toString + " to " + lamp.historicState(now.minusDays(days)).state.toString)
                        lamp.sendCommand(lamp.historicState(now.minusDays(days)).state as PercentType)
                    }
                    else
                    {
                        logInfo("Switch",  "Changing switch: " + lamp.name + " to " + lamp.historicState(now.minusDays(days)).state.toString)
                        lamp.sendCommand(lamp.historicState(now.minusDays(days)).state.toString)
                    }

                ]
            }         
        )
    }
end

You can’t do it at the beginning of the rule because you don’t have access to the specific Light Item until you are inside the for loop. the best you can do is (I also included the check to prevent it sending the same state if the light is already at a given state):

var int days = 7
var int delay = 50
var String persistence = "influxdb"

rule "Simulate Lights on Vacation"
when
        Time cron "0 0/5 * * * ?"
then
        if (Vacation_Mode.state == ON) {
                gGF_Lights.members.forEach(light,i |
                        val oldState = light.historicState(now.minusDays(days), persistence).state
                        if(oldState != light.state) {
                            createTimer(now.plusMillis(i*delay)) [|
                            logInfo("Vacation",light.name + " got " + oldState)
                            light.sendCommand(oldState.toString)
                        }
                        ]
                )
        }
end
3 Likes

Hi @rlkoshak,

I was trying to set up presence simulation. I practically copied your entire code (renamed some groups and variables).

But I get an error when the rule is saved.

My rule:

// ***************************
// Global variables
// ***************************

var int presence_days = 1
var int presence_delay = 50
var String persistence = "influxdb"

// ***************************
// Presence Simulation
// ***************************

rule "Presence Simulation"
when
        Time cron "0 0/5 * * * ?"
then
        if (Virtual_PresenceSimulation.state == ON) {
                gPresenceSimulation.members.forEach(light,i |
                        val oldState = light.historicState(now.minusDays(presence_days), persistence).state
                        if(oldState != light.state) {
                            createTimer(now.plusMillis(i*presence_delay)) [|
                            logInfo("Pres_Sim",light.name + " got " + oldState)
                            light.sendCommand(oldState.toString)
                        }
                        ]
                )
        }
end

The error:

2017-07-19 19:36:37.585 [WARN ] [el.core.internal.ModelRepositoryImpl] - Configuration model 'presence_simulation.rules' has errors, therefore ignoring it: [19,25]: no viable alternative at input 'val'
[24,25]: extraneous input '}' expecting ']'

I cant’ find out what is wrong with the code.
I assumed the “}” and “]” got switched. So I adapted the last bit in the rule to:

                            light.sendCommand(oldState.toString)
                        ]
                        }
                )
        }
end

But it didn’t solve anything…

for some reason, the val within the forEach loop doesn’t work… we need to try other methods to shorten the code :slight_smile:

Is there a way to define a “free” text (something that is not evaluated at the moment of declaration) in the beginning that can be re-used?
something like:
(my_free_text variable?) oldState = “historicState(now.minusDays(presence_days), persistence).state”
and then use: light.oldState within the forEach loop ?
In this way, the evaluation will be done within the loop.

This works fine: (but with too much text :slight_smile:)
(I added a 2nd group loop for the group First Floor Lights also (gFF))

var int days = 7
var int delay = 50
var String persistence = "influxdb"

rule "Simulate Lights on Vacation"
when
        Time cron "0 0/5 * * * ?"
then
        if (Vacation_Mode.state == ON) {
                gGF_Lights.members.forEach(light,i |
                        if(light.historicState(now.minusDays(days), persistence).state != light.state) {
                                createTimer(now.plusMillis(i*delay)) [|
                                logInfo("Vacation",light.name + " got " + light.historicState(now.minusDays(days), persistence).state)
                                light.sendCommand(light.historicState(now.minusDays(days), persistence).state.toString)
                                ]
                        }
                )
                gFF_Lights.members.forEach(light,i |
                        if(light.historicState(now.minusDays(days), persistence).state != light.state) {
                                createTimer(now.plusMillis(i*delay)) [|
                                logInfo("Vacation",light.name + " got " + light.historicState(now.minusDays(days), persistence).state)
                                light.sendCommand(light.historicState(now.minusDays(days), persistence).state.toString)
                                ]
                        }
                )
        }
end

Note: I didn’t use the main group “gLights” because it would send a command to all its members, including the subgroups gGF_lights and gFF_lights. If a command was to be issued against the subgroup, this would turn ON/OFF all lights in the subgroup. I wanted to avoid this, so I created 2 forEach loops for each subgroup.

Related items:

/* Control Groups */
Group	gAll
Group	gInfluxDB
Group	gMapDB
Group:Switch:OR(ON, OFF)	gLights		"All Lights [(%d)]"				<light>		(gAll,gMapDB,gInflux)
Group:Switch:OR(ON, OFF)	gGF_Lights	"Ground Floor Lights [(%d)]"	<light>		(gLights)
Group:Switch:OR(ON, OFF)	gFF_Lights	"First Floor Lights [(%d)]"		<light>		(gLights)
Group:Switch:OR(ON, OFF)	gVacation	"Vacation Group"				<present>	(gAll,gMapDB,gInflux)

/* and of course the famous Vacation_Mode switch item */
/* Switch it ON to enable Vacation Mode and let the rule do its thing */
/* You can trigger this switch from a presence detection solution also */
Switch					Vacation_Mode	"Vacation Mode"					<present>	(gVacation)

Maybe using a lambda to avoid repetition of code:

// ***************************
// Global variables
// ***************************

var int presence_days = 1
var int presence_delay = 50
var String persistence = "influxdb"

// ***************************
// Presence Simulation
// ***************************

val org.eclipse.xtext.xbase.lib.Functions$Function2<GenericItem, Integer, ?> updateLight = [
	light, i |
    val oldState = light.historicState(now.minusDays(presence_days), persistence).state
    if (oldState != light.state) {
        createTimer(now.plusMillis(i*presence_delay)) [|
            logInfo("Pres_Sim",light.name + " got " + oldState)
            light.sendCommand(oldState.toString)
        ]
    }
	true
]

rule "Presence Simulation"
when
        Time cron "0 0/5 * * * ?"
then
    if (Vacation_Mode.state == ON) {
        gGF_Lights.members.forEach[light,i |
			updateLight.apply(light as GenericItem, i)
		]
        gFF_Lights.members.forEach[light,i |
			updateLight.apply(light as GenericItem, i)
		]
   }
end
2 Likes

much, much better… thank you ! :slight_smile:

I’m all for doing this if you want to as a challenge or a learning opportunity. But from a practical perspective, there is a lot of effort being put in here to do something that ultimately will save maybe one line of code.

That is weird. Try using val to see if it likes that better. It should work. I know I’ve used vars in foreach loops like that before.

You could use anything that stores data but that you can define as a val. For example:

  • StringBuilder
  • HashMap
  • ArrayList

But again, that seems like a lot of work for ultimately little to no real savings in code simplicity.

1 Like

nice approach!
In this way, I can use the main gLights group and filter for SwitchItem types to avoid sending commands to the subgroups.

Tip: instead of historicState you could send a random percent value to the dimmers to simulate presence (…although nothing beats real historical data).
some variation of the rand used in:

You got me :slight_smile:
I also want to break it down to simple steps for noobs like me to understand these rules better :blush:

Ok, I have adapted the code to what you see below. Changes:

  • I do not use val=oldState anymore, I just refer to the entire “light.historicState(now.minusDays(presence_days), persistence).state”;
  • Near the end, I switched a “}” and a “]” (see my previous post).

I’m not getting any validation errors anymore. However, when the rule is executed, I get an error. :neutral_face:

The code:

// ***************************
// Global variables
// ***************************

var int presence_days = 1
var int presence_delay = 50
var String persistence = "influxdb"

// ***************************
// Presence Simulation
// ***************************

rule "Presence Simulation"
when
        Time cron "0 0/5 * * * ?"
then
        if (Virtual_PresenceSimulation.state == ON) {
                gPresenceSimulation.members.forEach(light,i |
                        if(light.historicState(now.minusDays(presence_days), persistence).state != light.state) {
                            createTimer(now.plusMillis(i*presence_delay)) [|
                            logInfo("Pres_Sim",light.name + " schakelt " + light.historicState(now.minusDays(presence_days), persistence).state)
                            light.sendCommand(light.historicState(now.minusDays(presence_days), persistence).state.toString)
                        ]
						}
                        
                )
        }
end

The error:

2017-07-20 09:08:26.139 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model 'presence_simulation.rules'
2017-07-20 09:10:00.692 [ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule Presence Simulation: cannot invoke method public abstract org.eclipse.smarthome.core.types.State org.eclipse.smarthome.core.persistence.HistoricItem.getState() on null

I didn’t notice any logInfo-entries, so that means that not a single light was switched successfully (and I made sure some had a different state than the historicstate).

Any advice on how to troubleshoot? This is my first rule using persistence stuff (historicState).

To exclude persistence itself from being the problem, I created this test rule. This was working fine (correct historicState was called):

when   
    Item  TEST_Trigger changed from OFF to ON
then
	logInfo("test","Test rule triggered ABC")
	val oldState = KNX_GV_Bureau_StaandeLamp_0_0_27.historicState(now.minusMinutes(13), persistence).state.toString
	val newState = KNX_GV_Bureau_StaandeLamp_0_0_27.state.toString
	logInfo("test","Old state = " + oldState)
	logInfo("test","New state = " + newState)	

Add a logInfo to show which light it is failing on. You might just replace the body of the forEach (temporarily) with a log to show what the historicState is returning for each of your Lights.

Thanks. With this approach I was able to identify the problem. It seems that one of my KNX switches didn’t return a proper status update. Very strange, since these KNX switches have been set up at least 5 years ago and I never noticed the problem. It seems my presence simulation is working now. :slight_smile:

Another question: the cron job is scheduled every 5 minutes (in the example code). Would you consider that as the recommended setting? I’m thinking of setting this to every minute. With the job set every five minutes, it might become obvious that lights are always turned on/off at 18h00, 18h05, 18h10… Or would it put too much of a burden on the OH system (I have approx. 20 switches in my presence simulation group)?

A minute is an eternity for a computer. I wouldn’t worry about any performance problems.

You could add a bit of noise to when they start by adding a random number to the timer so nothing starts exactly on any minute and the lights don’t go on all at the same time.

2 Likes

Ok, clear!

I have made some adaptations now:

  • Job runs every minute instead of every five minutes;
  • I have changed the delay from “50” to “1000”. I realize the initial purpose of “delay” was to avoid flooding OH with many commands at the same time. But with a delay of 1000 also the lights are turned on slightly more spread in time. If you have 20 lights, the last light will be switched 20 seconds after the rule has started. I know it’s not random, but someone trying to break in needs to be pretty smart to see a pattern. :slight_smile:
// ***************************
// Global variables
// ***************************

var int presence_days = 1
var int presence_delay = 1000
var String persistence = "influxdb"

// ***************************
// Presence Simulation
// ***************************

rule "Presence Simulation"
when
	Time cron "0 0/1 * 1/1 * ? *"
then
	if (Virtual_PresenceSimulation.state == ON) {
		gPresenceSimulation.members.forEach(light,i |
			if(light.historicState(now.minusDays(presence_days), persistence).state != light.state) {
				createTimer(now.plusMillis(i*presence_delay)) [|
					logInfo("Pres_Sim",light.name + " schakelt " + light.historicState(now.minusDays(presence_days), persistence).state)
					light.sendCommand(light.historicState(now.minusDays(presence_days), persistence).state.toString)
				]
			}
		)
	}
end

4 Likes

Just setting this up for my home.
I want to use your script Dries.

The only thing which I need to change is the gPresenceSimulation with my lights group i want to manage right ? In my case that would be gLight.

I also got the Virtual_PresenceSimulation switch item.

Did I miss anything which I need to add to my .items file etc. ?

regards