@MCVFlorin,
Thanks for the update.
However, this script looks like it checks for a tripped sensor and if one is found, then it immediately shuts off the thermostat without any wait.
We would like the 1 min delay to occur after a tripped sensor is detected then to check again for a tripped sensor. Only if a sensor is tripped on the second check do we want the thermostat to be turned off.
Can you reorganize the script to work that way?
I read the explanation of the Luup.call_delay function in the wiki but didn’t understand it. Can you explain it?
Actually it waits 60 seconds before it checks if there still are tripped sensors, and only then it turns off the thermostat.
The call_delay function runs the function given as the first parameter after the number of seconds given as the second parameter have passed. So it’s like call_timer, only simpler, in that it handles only one type of timer, and the time is always specified in seconds.
And I’d like to know what exactly didn’t you understand from the explanation. I’m asking this because you’re not the first person to not understand the explanation, so this means that the explanation is not that good and needs reformulation.
@MCVflorin
I am new to lua programming. The script appears to 1st check to see if there is a tripped sensor. If one is found, a “break” occurs. I presume that the “break” causes it to do to the end of the 1st For Do loop and begin the segment that turns off the thermostat. If that is wrong, what happens after the “break”.
Also, what happens after the luup.call_delay causes a pause? What part of the script starts after the pause. Too bad the lines aren’t numbered, it would be easier for me to understand the progression, ie where the script goes after the break and after the call_delay.
Thanks
@MCVflorin,
Here is the wiki definition:
function: call_delay parameters: function_name (string), seconds (number), data (string), thread (bool)
returns: result (number)
The function function_name (the first parameter), which must be passed as a string, will be called in seconds seconds (the second parameter), and will be passed the string data. The function returns 0 if successful
I understand that the function, the name of which must be entered as a string, will be “called” after the specified delay (seconds). I am not sure what happens when you “call a function”. I would think that it means that the function, “checkForTrippedSensors”, is run again but in this case that would seem to initiate an endless loop?
In this script the function is being passed “” or no data. What is the point of that? Does that mean that the function will be “called” without passing it any data?
The function returns 0 if successful?? What does it mean for a function to return 0? If what is successful?
As you can see, I am clueless!
Thanks for your patience.
Functions are run only if they’re called, but they have to be declared first. The code runs sequentially:
- Declare and define the variables.
- Declare and define the checkForTrippedSensors function.
- Go through all the sensors an check for the tripped ones. If one is found, exit the for loop.
- If there was any tripped sensor, call the function checkForTrippedSensor in 60 seconds.
- The script ends (with return nil if nothing else is returned) and the scene runs.
- After 60 seconds the checkForTrippedSensor function runs.
Because the function runs after all the other code, and because it is not called again at the end of the function, it only runs once.
The call_delay function returns the value 0 if it successfully scheduled the given function to run after the given number of seconds have passed.
@MCVflorin
I understand now but it seems a little strange that the function is declared first but run last in the script and only if called! I hope to test the script today.
Thanks
@MCVflorin - Thanks very much!!!
It worked! I only tested it briefly but it seemed to work as designed!
The scene has a timer set to run every 5 min. I added commands to the script to arm the door and window sensors that I want to monitor. One would need to modified the script for the device# for the sensors that you want to monitor and for the thermostats that you want to shut off.
The next step would be to create a script to monitor the motion detectors during certain hours (i.e. 9 AM to 9 PM). If the motion detectors are not tripped for more than 30 min, I would also like to shut off the AC, presuming that no one is in the house. The down side of that second script would be that the AC would be shut off if the occupants were taking a nap. One could either make the period of inactivity longer (i.e. 60 min) before shutting off the AC or turn up the AC thermostat to say 78F. (My thermostats may not be zero’ed properly because we can tolerate settings below 72F there because it gets too cold. However, my renters tended to turn the thermostat down to 68F before I learned to limit the lowest setting to 75F. My guess is that it was because they openned windows or doors if too cold!)
Thanks for the help here. I have a client that has only one thermostat and one sensor…I tried getting this working and it doesn’t seem to work…would there be an issue with simply removing the other sensor and T-stats and leaving just one ID for each?
Also, does the sensor need to be armed?
The sensor does need to be armed.
If you only had one sensor, you wouldn’t need the For loops.
I don’t know how or if a For loop works if you only have one element in the table.
The code should work for any number of sensors and thermostats. For one sensor and one thermostat the code would look like this:
-- Begin user input variables
local wdSensorIdPair = { [42]="UPHall_W" }
-- Table of thermostats ID# with descriptor
local thermostats = { [4]="GRThermostat" }
-- End user input variables
Thanks! We have it working!
One problem is turning the T-Stat back on…Since this Vacation home is in a hot climate, we want to make sure that the thermostat is not off to too long.
What should we do? Make another scene that simply turns on the thermostat and set a timer to perform that every 30 min?
I assume this will screw up and turn on the thermostat if the door turns it off, but in this case, it’s more important to have it on than off.
Is there a way to have one scene disable another scene? Sorry for all the questions, this thread has been very helpful in getting this stuff to work but now my goal is to make it bulletproof…
Great that you have it working. Can you copy the luup code that you have that is working. I tried the one above and was unable to get it to work. I am not luup knowledgeable and do a copy and paste changing only the input numbers. I can use multiple doors in one config or use multiple configs to handle each of the possible doors (my preference) so I know which door triggered it.
Sure, here it is:
-- Begin user input variables
local wdSensorIdPair = {[10]="doorsensor1"}
-- Table of thermostats ID# with descriptor
local thermostats = {[9]="thermostat1"}
-- End user input variables
-- Check if there are tripped sensors. If there are, then turn the thermostats off.
function checkForTrippedSensors()
-- Assume that no sensors are still tripped.
wdSensorTripped = false
for k, v in pairs(wdSensorIdPair) do
local tripped = luup.variable_get ("urn:micasaverde-com:serviceId:SecuritySensor1", "Tripped", k) or "0"
if (tripped == "0") then
luup.log("Sensor '" .. v .. "' is not tripped.")
else
luup.log("Sensor '" .. v .. "' IS TRIPPED!")
wdSensorTripped = true
break
end
end
if (wdSensorTripped) then
for k, v in pairs (thermostats) do
luup.log ("Turning thermostat '" .. v .. "' OFF.")
luup.call_action ("urn:upnp-org:serviceId:HVAC_UserOperatingMode1", "SetModeTarget", {NewModeTarget = "Off"}, k)
end
end
end
-- Assume that no sensors are tripped.
local wdSensorTripped = false
-- If any sensor is tripped, set the global variable for tripped status.
for k, v in pairs (wdSensorIdPair) do
local tripped = luup.variable_get ("urn:micasaverde-com:serviceId:SecuritySensor1", "Tripped", k) or "0"
if (tripped == "0") then
luup.log ("Sensor '" .. v .. "' is not tripped.")
else
luup.log ("Sensor '" .. v .. "' IS TRIPPED!")
wdSensorTripped = true
break
end
end
-- If there was any tripped sensor, check again in 1 minute if there still are tripped sensors.
if (wdSensorTripped) then
luup.call_delay ("checkForTrippedSensors", 60, "")
end
Make sure you use the Device Number in the DEVICE OPTIONS tab for your sensors and thermostat. (See the screenshot attached)
Thanks to many others but especially to MCVFlorin, my script to shut off the AC after a specified amount of time seems to be working fine. It has already reduced my electric bill by about 40%! Can we add a block to also shut off the AC if the motion detectors have tripped after a set period (30 min) during the day (9 AM to 5 PM)? I gave it a try, see my next post to this string.
What happens if one of my sensor’s batteries dies, will the scrip continue as if it doesn’t exist or will that cause the script to fail?
Should we add some Lua code at the begining to arm all of the sensors or simply do that with the scene’s commands?
As many know, guests tend to leave AC run all day, even if they are gone all day. In HI where electricity is $.386/kwh, that can be very costly. I would like to also either extend this script or create another, which would turn off the AC if the motion detectors were not tripped for a given period (30 min) during the interval, 9 AM til 5 PM. What do others think about that period of 30 min and the interval? Would 15 min be too short? Should the interval be 9 AM til 9 PM?
The previous scene ran every 1 min, is that too frequent for this scene? I am concerned about overloading Vera and limiting its ability to do other functions.
Can changing shadows (from clouds) trip these motion detectors?
I have pasted below, my first attempt at this but now realize that it doesn’t handle two motion detectors appropriately. Ideally, the lastTrip variable should be the shortest time period for either sensor but instead, this script makes it pertain only to the last motion detector sensor checked. I suppose it should create a lastTrip variable for each sensor then compare and select the shortest of them. Any ideas on simple code to do that?
– Create scene that runs every 5 minutes
– Create Command in this scene to “arm” each W/D & MD sensor
– k is Device ID# and V is device abbreviation in tables
– Begin user input variables
– Table of thermostats ID# with abbreviation
thermostats = {[4]=“GRThermostat”, [20]=“LHThermostat”}
–Table of motion detectors ID# with abbreviation
MotionDetectIDPair = {[13]=“GR_MD”, [14]=“LL_MD”}
– End user input variables
–This portion makes the scene run only between startTime and endTime.
–[[The call string.sub(s,i,j) extracts a piece of the string s, from the i-th to
the j-th character inclusive. This call can also be written s:sub(i,j). s:sub(-2)
gives the last 2 characters of a string, since the default for j is -1, which is
the last character in the string.]]
–find(“%d+”) finds the first sequence of digits
local startTime = “09:00”
local endTime = “17:00”
local hour = tonumber( startTime:sub( startTime:find(“%d+”) ) )
local minute = tonumber(startTime:sub(-2))
if hour and minute then
startTime = hour * 100 + minute
–creates number like 0900 without colon
else
luup.log(“ERROR: invalid start time”)
return false
end
hour = tonumber( endTime:sub( endTime:find(“%d+”) ) )
minute = tonumber(endTime:sub(-2))
if hour and minute then
endTime = hour * 100 + minute
else
luup.log(“ERROR: invalid end time”)
return false
end
local currentTime = os.date(“*t”)
currentTime = currentTime.hour * 100 + currentTime.min
luup.log("startTime = " … startTime … "; currentTime = " … currentTime … "; endTime = " … endTime)
if startTime <= endTime then
– Both the start time and the end time are in the same day:
– if the current time is in the given interval, run the scene.
if startTime <= currentTime and currentTime <= endTime then
return true
end
end
return false
–Should period be a local or global variable?
local period = 30
local SS_SID = “urn:schemas-micasaverde-com:device:MotionSensor:1”
function find_time_MD_lasttripped()
for k,v in pairs(MotionDetectIDPair)
do
local lastTrip = luup.variable_get(SS_SID, “LastTrip”, k) or os.time()
lastTrip = tonumber(lastTrip)
if (os.difftime(os.time(), lastTrip) / 60) >= period then
return true
end
end
return false
–If between start and end time & time to last MD tripped > 30 min, AC turned off
for k,v in pairs(thermostats)
do
luup.variable_set(“urn:upnp-org:serviceId:HVAC_UserOperatingMode1”, “ModeStatus”, “OFF”, k)
luup.variable_set(“urn:upnp-org:serviceId:HVAC_UserOperatingMode1”, “ModeTarget”, “OFF”, k)
luup.log(“Setting Mode on Thermostat “…”#”…k…" “…v…” to OFF")
end
If one of your sensor’s batteries die it won’t report the tripped status to Vera anymore, so the Tripped variable won’t be updated. The script will run fine though.
If you want others to see this script you can create a thread on that board and I’ll sticky it.
I updated the previous code to also turn off the AC if none of the motions sensors were tripped in the last 20 minutes. The recommended timer for the scene with this code is still 5 minutes.
-- Begin user input variables
-- Table of window and door sensors ID# with descriptor
local wdSensorIdPair = {
[42]="UPHall_W", [31]="GRFrnt_D", [41]="GRLanai_D", [38]="UPLBckBR_W", [39]="UPRBckBR_W",
[36]="MastBR_D", [32]="LFrtBR_D", [34]="LBkBR_D", [35]="LwHall_D", [40]="LwHall_W"
}
-- Table of motion sensors ID# with descriptor
local motionSensors = {
[12]="HallSensor", [23]="KitchenSensor"
}
-- Table of thermostats ID# with descriptor
local thermostats = {[4]="GRThermostat", [20]="LHThermostat"}
-- End user input variables
-- Check if there are tripped sensors. If there are, then turn the thermostats off.
function checkForTrippedSensors()
-- Assume that no sensors are still tripped.
wdSensorTripped = false
for k, v in pairs(wdSensorIdPair) do
local tripped = luup.variable_get("urn:micasaverde-com:serviceId:SecuritySensor1", "Tripped", k) or "0"
if (tripped == "0") then
luup.log("Sensor '" .. v .. "' is not tripped.")
else
luup.log("Sensor '" .. v .. "' IS TRIPPED!")
wdSensorTripped = true
break
end
end
if (wdSensorTripped) then
for k, v in pairs(thermostats) do
luup.log("Turning thermostat '" .. v .. "' OFF.")
luup.call_action("urn:upnp-org:serviceId:HVAC_UserOperatingMode1", "SetModeTarget", {NewModeTarget = "Off"}, k)
end
end
end
local now = os.date("*t")
-- If it's between 9 AM and 5 PM then check the motion sensors.
if (now.hour >= 9 and now.hour <= 17) then
-- Assume that no motion sensor has been tripped in the last 20 minutes.
local motionSensorTripped = false
-- Check when was the last time each motion sensor was tripped.
for k, v in pairs(motionSensors) do
local lastTrip = luup.variable_get("urn:micasaverde-com:serviceId:SecuritySensor1", "LastTrip", k)
lastTrip = tonumber(lastTrip, 10) or os.time()
if (((os.time() - lastTrip) / 60) < 1200) then
motionSensorTripped = true
break
end
end
-- If no motion sensor has been tripped in the last 20 minutes (1200 seconds), then turn off the AC.
if (not motionSensorTripped) then
for k, v in pairs(thermostats) do
luup.log("Turning thermostat '" .. v .. "' OFF.")
luup.call_action("urn:upnp-org:serviceId:HVAC_UserOperatingMode1", "SetModeTarget", {NewModeTarget = "Off"}, k)
end
return -- AC has been turned off: don't check the door and window sensors anymore.
end
end
-- Assume that no sensors are tripped.
local wdSensorTripped = false
-- If any sensor is tripped, set the global variable for tripped status.
for k, v in pairs(wdSensorIdPair) do
local tripped = luup.variable_get("urn:micasaverde-com:serviceId:SecuritySensor1", "Tripped", k) or "0"
if (tripped == "0") then
luup.log("Sensor '" .. v .. "' is not tripped.")
else
luup.log("Sensor '" .. v .. "' IS TRIPPED!")
wdSensorTripped = true
break
end
end
-- If there was any tripped sensor, check again in 1 minute if there still are tripped sensors.
if (wdSensorTripped) then
luup.call_delay("checkForTrippedSensors", 60, "")
end
Does it handle the case where the sensor is still tripped?
AFAIK the LastTrip variable is updated every time the sensor reports it’s tripped, and I trust it. If LastTrip says that the sensor has been tripped 20 minutes ago, then I believe it. In this case I don’t care what Tripped says.
If I stay in the room for more than 20 minutes, in range of the motion sensor, wouldn’t the [tt]LastTrip[/tt] be more than 20 minutes ago? (Or leave the room / am out of range of the sensor, but return before the configured on-time/delay of the sensor.)
Doesn’t the motion sensor report it’s tripped every on-time minutes? This is how I knew these worked, though I may be mistaken.