Presence Simulation

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

Hi,
Assuming you are using influxdb as persistence, that’s the only thing you would need to adjust, indeed.

Good luck,
Dries

1 Like

Just a small question: does this work in OH3 as well? And where exactly do I enter this code?
If I do it via Rules --> Code, the code always “disappears” .
If i do it via Scripts --> the EXMAScript and the Rule DSL doesn’t do anything… :roll_eyes:

It should work in OH 3.

You need to save them to a .rules file in the conf/rules folder. The code above will not work in Ui created rule without changes.

Came here as the previous method I used (google calendar scheduler) is no longer supported in OH3.

Tried the great method in this thread but had a little difficulty at first but quickly found the answer here

In short, change this (from above code):
createTimer(now.plusMillis(i*presence_delay))

To this:
createTimer(now.toInstant().plusMillis(i*presence_delay).atZone(now.zone))

For completeness and for anyone else who is interested in this method here is what I have now which I can confirm works with OH3.

Make sure influxDB is setup, ether manually or via openhabian

Add following:

.items

Switch Swi_PreSim  // used in sitemap to turn simulation on/off

Group  gSim        // add this group to items you wish to be included

example light to be included:

Dimmer Dim_Lounge_Rear "Lounge Rear" <light> (gSim) { channel="xyz" }

.persist

Items {
gSim* : strategy = everyChange
}

.rules

// **************************
// 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 * ? *" //every 60 sec
    Time cron "0/30 0/1 * 1/1 * ? *" //every 30 sec

then

    if (Swi_PreSim.state == ON) {
        gSim.members.forEach(light,i |
            if(light.historicState(now.minusDays(presence_days), persistence).state != light.state) {
                //createTimer(now.plusMillis(i*presence_delay)) [|  OH2
                createTimer(now.toInstant().plusMillis(i*presence_delay).atZone(now.zone)) [| //OH3
                    logInfo("Pres_Sim",light.name + " state " + light.historicState(now.minusDays(presence_days), persistence).state)
                    light.sendCommand(light.historicState(now.minusDays(presence_days), persistence).state.toString)
                ]
            }
        )
    }
end

Pleased I found this as it is so much simpler than the Google method, no messing about with their API and all done locally, wish I discovered this a while ago!

2 Likes

What would this rule look like if it was ported to javascript on OH3?

Let me share with you how far I got so far.
For timers some investigation is needed:

triggers:
  - id: "1"
    configuration:
      cronExpression: 0 * * * * ? *
    type: timer.GenericCronTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: >-
        var logger =
        Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.Experiments");

        var PersistenceExtensions = Java.type("org.openhab.core.persistence.extensions.PersistenceExtensions");

        var ZonedDateTime = Java.type("java.time.ZonedDateTime");


        logger.info("Running presence simulation");

        var presence = ir.getItem("PresenceSimulation").state

        var presenceOffset = 7


        if (presence == ON) {
          var group = ir.getItem("gPresenceSimulation");
          group.allMembers.forEach(function (item) {

            logger.info("processing gPresenceSimulation member {}", item.name);

            var historicState = PersistenceExtensions.historicState(item, ZonedDateTime.now().minusDays(presenceOffset),"rrd4j").state;
            if (historicState != item.state) {

              logger.info("Pres_Sim {} set to {}", item.name, historicState);
              events.sendCommand(item.name, historicState)
            }
          })
        };
    type: script.ScriptAction
1 Like

Move the check to see if PresenceSimulation is ON in a Script Condition instead of the Script Action. That will make the Action a lot simpler.

Beyond that everything looks as I would expect. Is there anything not working?

What do you need the timers for here? Or are you just needing to look into Timers in general?

Thank you for the feedback on script condition.

I included timers to avoid overflow of the binding interface with simultaneous "sendCommand"s
I had to do so on my old installation, but I am not sure whether I still need with OH3 on raspi4 and newer v2 bindings.

triggers:
  - id: "1"
    configuration:
      cronExpression: 0 * * * * ? *
    type: timer.GenericCronTrigger
conditions:
  - inputs: {}
    id: "3"
    configuration:
      itemName: PresenceSimulation
      state: ON
      operator: =
    type: core.ItemStateCondition
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: >-
        var logger =
        Java.type("org.slf4j.LoggerFactory").getLogger('org.openhab.rule.' +
        ctx.ruleUID);// "org.openhab.model.script.PresenceSimulation");

        var PersistenceExtensions = Java.type("org.openhab.core.persistence.extensions.PersistenceExtensions");

        var ZonedDateTime = Java.type("java.time.ZonedDateTime");

        var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");


        var presenceOffset = 7

        var presenceDelay = 100000000


        var group = ir.getItem("gPresenceSimulation");

        group.allMembers.forEach(function (item, index) {

          logger.info("processing gPresenceSimulation member {}", item.name);

          var historicState = PersistenceExtensions.historicState(item, ZonedDateTime.now().minusDays(presenceOffset),"rrd4j").state;
          if (historicState != item.state) {

            logger.debug("Pres_Sim {} set to {}", item.name, historicState);
            ScriptExecution.createTimer(ZonedDateTime.now().plusNanos(index*presenceDelay), function(){
              events.sendCommand(item.name, historicState)
            })                             
          }
        });
    type: script.ScriptAction