Luup Scenes Events
m (→Calculate sunrise and sunset) |
(→Thermostat conditioned by door/window) |
||
(22 intermediate revisions by 5 users not shown) | |||
Line 1: | Line 1: | ||
− | + | == Adding Lua code to scenes and events == | |
− | == Adding Lua code to scenes and events == | + | |
− | [[Image:SceneFlowchart.png]] | + | [[Image:SceneFlowchart.png]] |
− | You can add Lua script to scenes and events for simple tasks, like making a scene or event conditional. | + | You can add Lua script to scenes and events for simple tasks, like making a scene or event conditional. Conditional meaning "do something only '''if''' some condition is met", such as attaching a condition to your "Come Home" scene so it is only run '''if''' the temperature is over 70 degrees. Basic conditional expressions are easy, and, if that's all you're looking to do, skip to the walk-through below. |
− | If you're more technically inclined you can also do very advanced things too. | + | If you're more technically inclined you can also do very advanced things too. For an overview of all the advanced things you can do with Vera's Luup engine, and how scenes and events fit in, see the [[Luup Intro]] page. |
− | To add Lua code to a scene, create the [[Scenes]] and click 'Luup scene'. | + | To add Lua code to a scene, create the [[Scenes]] and click 'Luup scene'. Fill in your Lua code in the input box. To do a simple condition, see the sample below. Or if you want to do advanced scripting you can use any [[http://lua.org Lua]] commands as described in the [[http://www.lua.org/manual/5.1/ Lua reference manual]] as well as the variables and functions that the Luup engine adds to Lua documented here: [[Luup Lua extensions]]. The Lua code will be run every time the scene is activated either by the user or a scene or a timer. The Lua code is run before the commands that you included in the scene. If your Lua code ends with this: "return false" then the commands in the scene will not be run. |
− | You can also add Lua code to an event by clicking 'Luup event'. | + | You can also add Lua code to an event by clicking 'Luup event'. Since events are attached to scenes anyway, there is usually little difference between adding the code to an event, or to the scene the event is part of. If the Lua code in an event returns false, then the event is aborted, meaning the scene that the event is attached to will not be triggered by the event. The main reason for attaching Lua code to an event is if you have multiple events to a scene. For example, you may have a scene called "Turn lights on in hallway" which is triggered by 2 events: 1) The front door opens, and 2) the motion sensor in the hallway is tripped. Perhaps whenever the front door opens you always want the scene to be activated, but you only want the motion sensor to activate the scene before 10:00. In this case, you would add Lua code to the motion sensor's event which does something like (pseudo-code): "if time>10:00 return false". That way the event from the motion sensor will be aborted if it's after 10:00, but the event from the front door will always activate the scene regardless. |
− | When you edit the Lua code in a scene or event you must click 'Save' before the code is saved. | + | When you edit the Lua code in a scene or event you must click 'Save' before the code is saved. Then you can activate the scene or trigger the event to see what happens when your code is run. If you're doing some advanced scripting that you'll need to debug this can be tedious, but there are easy ways to test your Lua script immediately without saving first as explained in [[Lua Debugging]]. |
− | If you're doing advanced Lua scripting, you should note that all the Lua code in your scenes and events run in a single Lua instance, which is separate from any Lua plugins. | + | If you're doing advanced Lua scripting, you should note that all the Lua code in your scenes and events run in a single Lua instance, which is separate from any Lua plugins. This means if you set a global variable in one scene, or create a function inside the Lua code in a scene, then in another scene's Lua code you can use that global variable or call that function. You do not need to worry about conflicting with a Luup plugin, though, since they have their own Lua instance, meaning they have their own global variables and functions. |
== Walk-through #1 -- At 12 noon, turn off the interior lights if the temperature is over 80 degrees == | == Walk-through #1 -- At 12 noon, turn off the interior lights if the temperature is over 80 degrees == | ||
Line 21: | Line 21: | ||
In this walkthrough we'll assume it's Device #3, but use the actual device number of''your'' thermostat. Next, visit [[Luup Variables]] to get a list of all the variables for devices. A variable is a piece of information about the current state of a device, such as whether it's on or off, it's current temperature, etc. | In this walkthrough we'll assume it's Device #3, but use the actual device number of''your'' thermostat. Next, visit [[Luup Variables]] to get a list of all the variables for devices. A variable is a piece of information about the current state of a device, such as whether it's on or off, it's current temperature, etc. | ||
− | Look down at ''Thermostat'', and copy the name of the service/variable which corresponds to the current temperature, namely. | + | Look down at ''Thermostat'', and copy the name of the service/variable which corresponds to the current temperature, namely. In this case, it's |
urn:upnp-org:serviceId:TemperatureSensor1 CurrentTemperature | urn:upnp-org:serviceId:TemperatureSensor1 CurrentTemperature | ||
Line 33: | Line 33: | ||
Third, the last step is to add the condition. To the right of the scene's description you'll see the button "Luup Scene". Click it and in the code box, copy and paste the following: | Third, the last step is to add the condition. To the right of the scene's description you'll see the button "Luup Scene". Click it and in the code box, copy and paste the following: | ||
− | local lul_temp=luup.variable_get("urn:upnp-org:serviceId:TemperatureSensor1","CurrentTemperature",3) | + | <source lang="lua"> |
− | if( tonumber(lul_temp) | + | local lul_temp = luup.variable_get("urn:upnp-org:serviceId:TemperatureSensor1","CurrentTemperature", 3) |
+ | if (tonumber(lul_temp) < 26.6) then | ||
return false | return false | ||
end | end | ||
+ | </source> | ||
Don't forget to change the "3" to whatever is the actual device number of your thermostat. Assign the result of <tt>luup.variable_get</tt> to a variable first, rather than putting it directly in the <tt>tonumber()</tt>, because <tt>luup.variable_get</tt> actually returns 2 values: the value of the variable, and the time when the variable was modified. The '<tt>tonumber</tt>' is needed because all of a device's variables are stored as plain text--not numbers--so if you want to do arithmetic or numeric comparison of a variable, you need to put <tt>tonumber()</tt> around <tt>luup.variable_get</tt>. | Don't forget to change the "3" to whatever is the actual device number of your thermostat. Assign the result of <tt>luup.variable_get</tt> to a variable first, rather than putting it directly in the <tt>tonumber()</tt>, because <tt>luup.variable_get</tt> actually returns 2 values: the value of the variable, and the time when the variable was modified. The '<tt>tonumber</tt>' is needed because all of a device's variables are stored as plain text--not numbers--so if you want to do arithmetic or numeric comparison of a variable, you need to put <tt>tonumber()</tt> around <tt>luup.variable_get</tt>. | ||
Line 44: | Line 46: | ||
Now click 'Update', and then click 'Save' to save everything. The "return false" means "don't run this scene". So if the current temperature is <26.6, the scene will be aborted and won't run. The timer will make it trigger every day at 12 noon. | Now click 'Update', and then click 'Save' to save everything. The "return false" means "don't run this scene". So if the current temperature is <26.6, the scene will be aborted and won't run. The timer will make it trigger every day at 12 noon. | ||
− | To test it, you can click the scene button on the dashboard. The lights will turn off only if the temperature is over 80 degrees. If it's less than 80 degrees, the scene won't do anything. Since this scene is something that happens automatically and you probably won't execute manually, you can go to the scene again and check the "Hidden" box so the scene doesn't show up on Vera's 'Dashboard' and on your remote controls, like the iPhone. If you want to have a scene that turns off the lights which you can run whenever you want from 'Dashboard'' or your remote control, you should create another scene that has the same commands and simply don't add the timers and Luup conditions, and don't check the "Hidden" box. | + | To test it, you can click the scene button on the dashboard. The lights will turn off only if the temperature is over 80 degrees. If it's less than 80 degrees, the scene won't do anything. Since this scene is something that happens automatically and you probably won't execute manually, you can go to the scene again and check the "Hidden" box so the scene doesn't show up on Vera's 'Dashboard' and on your remote controls, like the iPhone. If you want to have a scene that turns off the lights which you can run whenever you want from 'Dashboard''or your remote control, you should create another scene that has the same commands and simply don't add the timers and Luup conditions, and don't check the "Hidden" box. '' |
You can substitute other service/variables and device ID's to make other types of conditions. The "if" statement above also supports nesting with ( and ), as well as the keywords 'and' and 'or'. So the following means the scene would abort if the temperature is <26.6 and >25, unless it's <23: | You can substitute other service/variables and device ID's to make other types of conditions. The "if" statement above also supports nesting with ( and ), as well as the keywords 'and' and 'or'. So the following means the scene would abort if the temperature is <26.6 and >25, unless it's <23: | ||
− | local lul_temp=luup.variable_get("urn:upnp-org:serviceId:TemperatureSensor1","CurrentTemperature",3) | + | <source lang="lua"> |
− | if( (tonumber(lul_temp) | + | local lul_temp = luup.variable_get("urn:upnp-org:serviceId:TemperatureSensor1","CurrentTemperature", 3) |
− | and tonumber(lul_temp) | + | if ((tonumber(lul_temp) < 26.6 |
− | or tonumber(lul_temp) | + | and tonumber(lul_temp) > 25) |
+ | or tonumber(lul_temp) < 23) then | ||
return false | return false | ||
end | end | ||
+ | </source> | ||
If the scene doesn't run, it's possible there is a syntax error. The easiest way to test this is to copy the Lua code from the scene, then go to Devices, Luup Plugins, and "Test Luup code". Paste the code in the box and click 'go'. If the info box above the 'go' button has a check and says "Message sent successful", your code is okay. If there's an error, it says: "Code failed". | If the scene doesn't run, it's possible there is a syntax error. The easiest way to test this is to copy the Lua code from the scene, then go to Devices, Luup Plugins, and "Test Luup code". Paste the code in the box and click 'go'. If the info box above the 'go' button has a check and says "Message sent successful", your code is okay. If there's an error, it says: "Code failed". | ||
− | To see if that's true, use putty, or telnet or ssh to log-in to Vera, as explained in detail in [[Lua Debugging]], and type: | + | To see if that's true, use putty, or telnet or ssh to log-in to Vera, as explained in detail in [[Logon_Vera_SSH]] and [[Lua Debugging]], and type: |
cd /var/log/cmh | cd /var/log/cmh | ||
− | + | tail -f LuaUPnP.log | grep '^01' | |
Now click 'Save' in Vera's setup page, even if it's gray, as that will cause Vera to restart the Luup engine and log any syntax errors. See: [[Lua Debugging]] for in-depth details on how to debug. | Now click 'Save' in Vera's setup page, even if it's gray, as that will cause Vera to restart the Luup engine and log any syntax errors. See: [[Lua Debugging]] for in-depth details on how to debug. | ||
− | == | + | == Walk-through #2 -- Only run the scene during the daytime == |
− | + | In the Luup tab for the scene paste this: | |
− | === | + | <source lang="lua"> |
+ | return luup.is_night() == false | ||
+ | </source> | ||
− | + | That works because if the return is 'true' the scene runs, and if it's 'false' it doesn't. So during the daytime the expression is true. Or, an alternative long form: | |
− | = | + | <source lang="lua"> |
+ | if (luup.is_night()) then | ||
+ | return false | ||
+ | else | ||
+ | return true | ||
+ | end | ||
+ | </source> | ||
− | + | == Samples == | |
− | + | This page is a wiki which anyone can edit. If you have some Lua code you think other users might find useful, feel free to add it here. | |
− | + | === Lighting and Switch Actions === | |
− | + | Did you see the sample here already: http://wiki.micasaverde.com/index.php/Luup_Scenes_Events | |
− | local lul_tmp = luup.variable_get("urn:upnp-org:serviceId:SwitchPower1","Status",5) | + | ==== Turn an appliance switch or a Danfoss thermostat on for device #5 ==== |
− | if( lul_tmp=="1" ) then | + | |
+ | <source lang="lua"> | ||
+ | luup.call_action("urn:upnp-org:serviceId:SwitchPower1", "SetTarget", {newTargetValue = "1"}, 5) | ||
+ | </source> | ||
+ | |||
+ | ==== Turn an appliance switch or a Danfoss thermostat off ==== | ||
+ | |||
+ | <source lang="lua"> | ||
+ | luup.call_action("urn:upnp-org:serviceId:SwitchPower1", "SetTarget", {newTargetValue = "0"}, 5) | ||
+ | </source> | ||
+ | |||
+ | ==== Do something if switch device #5 is on ==== | ||
+ | |||
+ | <source lang="lua"> | ||
+ | local lul_tmp = luup.variable_get("urn:upnp-org:serviceId:SwitchPower1", "Status", 5) | ||
+ | if (lul_tmp == "1") then | ||
--something to do goes here | --something to do goes here | ||
end | end | ||
+ | </source> | ||
− | ====Dim switch #6 to 30%==== | + | ==== Dim switch #6 to 30% ==== |
− | luup.call_action("urn:upnp-org:serviceId:Dimming1","SetLoadLevelTarget",{ newLoadlevelTarget="30" },6) | + | <source lang="lua"> |
+ | luup.call_action("urn:upnp-org:serviceId:Dimming1", "SetLoadLevelTarget", {newLoadlevelTarget = "30"}, 6) | ||
+ | </source> | ||
− | ==== | + | ====Switch on, and switch off 2 seconds later ==== |
+ | <source lang="lua"> | ||
+ | local device = 29 | ||
+ | -- Switch on device 29: | ||
+ | luup.call_action("urn:upnp-org:serviceId:SwitchPower1","SetTarget",{ newTargetValue="1" },device) | ||
+ | luup.call_delay( 'switch_off', 2) -- Call the switch off function after a delay of 2 seconds | ||
+ | function switch_off() | ||
+ | luup.call_action("urn:upnp-org:serviceId:SwitchPower1","SetTarget",{ newTargetValue="0" },device) | ||
+ | end | ||
+ | </source> | ||
− | + | ==== Blinking lights (and how to delay for a number of seconds) ==== | |
− | = | + | http://forum.micasaverde.com/index.php?topic=5127.0 |
− | + | === Motion Sensor Actions === | |
− | + | ==== Arm motion sensor #7 ==== | |
− | = | + | <source lang="lua"> |
+ | luup.variable_set("urn:micasaverde-com:serviceId:SecuritySensor1", "Armed", "1", 7) | ||
+ | </source> | ||
− | + | ==== Disarm motion sensor #7 ==== | |
− | + | ||
− | luup. | + | <source lang="lua"> |
+ | luup.variable_set("urn:micasaverde-com:serviceId:SecuritySensor1", "Armed", "0", 7) | ||
+ | </source> | ||
− | + | Note, arming and disarming isn't a concept within UPnP or Z-Wave. It's just a flag that the Luup engine uses, and is stored in a variable we created called "Armed". | |
− | + | === Scene Actions === | |
− | ====Change the Temperature on Thermostat (Cool) device #19==== | + | ==== Run Scene #5 ==== |
+ | |||
+ | Thanks "denix" on the forum for the correct syntax. "Actually, the 4th parameter IS required, but it's not used. Otherwise the command fails with an error message" | ||
+ | |||
+ | <source lang="lua"> | ||
+ | luup.call_action("urn:micasaverde-com:serviceId:HomeAutomationGateway1", "RunScene", {SceneNum = "5"}, 0) | ||
+ | </source> | ||
+ | |||
+ | In this case we left the device number off (the 4th parameter to <tt>luup.call_action</tt>), because the "<tt>RunScene</tt>" action is handled by the Luup engine itself--not by some device within Z-Wave, etc. | ||
+ | |||
+ | However, normally you don't need to <tt>luup.call_action</tt> in Lua code. Rather, whatever actions, or commands, you want to run, you put into the scene itself, and the only Lua code is to simply check if some condition is true and abort the scene if the condition isn't met. | ||
+ | |||
+ | === Thermostat Actions === | ||
+ | |||
+ | ==== Change the Temperature on Thermostat (Cool) device #19 ==== | ||
+ | |||
+ | <source lang="lua"> | ||
luup.call_action("urn:upnp-org:serviceId:TemperatureSetpoint1_Cool", | luup.call_action("urn:upnp-org:serviceId:TemperatureSetpoint1_Cool", | ||
− | "SetCurrentSetpoint", { NewCurrentSetpoint="68" }, | + | "SetCurrentSetpoint", {NewCurrentSetpoint = "68"}, |
19) | 19) | ||
+ | </source> | ||
− | ====Change the Temperature on a Thermostat (Heat) device #19==== | + | ==== Change the Temperature on a Thermostat (Heat) device #19 ==== |
+ | |||
+ | <source lang="lua"> | ||
luup.call_action("urn:upnp-org:serviceId:TemperatureSetpoint1_Heat", | luup.call_action("urn:upnp-org:serviceId:TemperatureSetpoint1_Heat", | ||
− | "SetCurrentSetpoint",{ NewCurrentSetpoint="68" }, | + | "SetCurrentSetpoint", {NewCurrentSetpoint = "68"}, |
19) | 19) | ||
+ | </source> | ||
− | ==== | + | ==== Change the Thermostat Operating mode device #19 ==== |
− | local IP_address | + | <source lang="lua"> |
− | + | luup.call_action("urn:upnp-org:serviceId:HVAC_UserOperatingMode1", | |
− | + | "SetModeTarget", {NewModeTarget = "Off"}, | |
− | + | 19) | |
+ | </source> | ||
+ | |||
+ | <br> | ||
+ | |||
+ | === Camera Actions === | ||
+ | |||
+ | ==== 'Privacy' mode for Foscam FI8908[w] ==== | ||
+ | |||
+ | <source lang="lua"> | ||
+ | local IP_address = '<IP address of camera>' | ||
+ | local username = '<username>' | ||
+ | local password = '<password>' | ||
+ | local timeout = 5 | ||
− | + | function move_up() | |
− | + | luup.inet.wget('http://' .. IP_address .. '/decoder_control.cgi?command=0', timeout, username, password) | |
− | + | end | |
− | + | ||
− | + | ||
− | + | -- center the camera; takes some time, so we have to wait 2 minutes for the command to complete | |
− | + | luup.inet.wget('http://' .. IP_address .. '/decoder_control.cgi?command=25', timeout, username, password) | |
− | + | luup.call_delay('move_up', 120) | |
+ | </source> | ||
+ | === Misc Actions === | ||
− | ==== | + | ==== Playing an announcement ==== |
− | http://forum.micasaverde.com/index.php?topic= | + | http://forum.micasaverde.com/index.php?topic=5466.msg36405#msg36405 |
− | === Calculate sunrise and sunset === | + | === Calculate sunrise and sunset === |
− | See http://forum.micasaverde.com/index.php?topic=2073.msg8132#msg8132 | + | See http://forum.micasaverde.com/index.php?topic=2073.msg8132#msg8132 |
− | ... or use DAD: http://forum.micasaverde.com/index.php?topic=5466.0 | + | ... or use DAD: http://forum.micasaverde.com/index.php?topic=5466.0 |
− | === Access the web === | + | === Access the web === |
− | ==== Invoke URL | + | |
− | Based on code by Jim/jgc94131 | + | ==== Invoke HTTP URL with <tt>GET</tt> request (Method 1) ==== |
+ | |||
+ | <source lang="lua"> | ||
+ | -- 5 Second timeout | ||
+ | local status, result = luup.inet.wget("http://www.yahoo.com", 5) | ||
+ | </source> | ||
+ | |||
+ | ==== Invoke HTTP URL with <tt>GET</tt> request (Method 2) ==== | ||
+ | |||
+ | Based on code by Jim/jgc94131 <source lang="lua"> | ||
require('ltn12') | require('ltn12') | ||
− | local http=require('socket.http') | + | local http = require('socket.http') |
+ | |||
+ | -- 5 Second timeout | ||
+ | socket.http.TIMEOUT = 5 | ||
+ | |||
local response_body = {} | local response_body = {} | ||
local request_body = '' | local request_body = '' | ||
− | + | ||
local r, c, h = socket.http.request{ | local r, c, h = socket.http.request{ | ||
url = 'http://website/page?parameter1=value¶meter2=value', | url = 'http://website/page?parameter1=value¶meter2=value', | ||
Line 166: | Line 254: | ||
headers = { | headers = { | ||
["Content-Length"] = string.len(request_body), | ["Content-Length"] = string.len(request_body), | ||
− | ["Content-Type"] = | + | ["Content-Type"] = "application/x-www-form-urlencoded" |
}, | }, | ||
source = ltn12.source.string(request_body), | source = ltn12.source.string(request_body), | ||
sink = ltn12.sink.table(response_body) | sink = ltn12.sink.table(response_body) | ||
− | } | + | } |
+ | </source> | ||
+ | |||
+ | ==== Invoke HTTP URL with <tt>POST</tt> request (Method 3) ==== | ||
+ | |||
+ | <source lang="lua"> | ||
+ | local http = require("socket.http") | ||
+ | |||
+ | -- 5 Second timeout | ||
+ | http.TIMEOUT = 5 | ||
+ | |||
+ | -- The return parameters are in a different order from luup.inet.wget(...) | ||
+ | result, status = http.request("http://192.168.0.113/runprocess.htm", "run=run") | ||
+ | </source> | ||
=== Access the current time === | === Access the current time === | ||
Line 176: | Line 277: | ||
The Lua function | The Lua function | ||
+ | <source lang="lua"> | ||
os.date (format, time) | os.date (format, time) | ||
+ | </source> | ||
converts a time value `time` into a human readable date/time string, according to `format`. If you leave out the optional `time` parameter, it defaults to current time. The `format` parameter defaults to a fairly complete format. If you specify '*t' as the format, it will return a table instead of a formatted string. | converts a time value `time` into a human readable date/time string, according to `format`. If you leave out the optional `time` parameter, it defaults to current time. The `format` parameter defaults to a fairly complete format. If you specify '*t' as the format, it will return a table instead of a formatted string. | ||
+ | <source lang="lua"> | ||
t = os.date('*t') | t = os.date('*t') | ||
− | + | t = {year=2010, month=2, day=19, yday=50, wday=6, hour=22, min=45, sec=45, isdst=false} | |
+ | </source> | ||
− | The fields are year, month, day of month, day of year, day of week, hour in 24 hour clock, minutes, seconds and if it's Daylight Savings Time. | + | The fields are year, month, day of month, day of year, day of week, hour in 24 hour clock, minutes, seconds and if it's Daylight Savings Time. |
Current hour: | Current hour: | ||
+ | <source lang="lua"> | ||
os.date('*t').hour | os.date('*t').hour | ||
+ | </source> | ||
Current minute: | Current minute: | ||
+ | <source lang="lua"> | ||
os.date('*t').min | os.date('*t').min | ||
+ | </source> | ||
Current second: | Current second: | ||
+ | <source lang="lua"> | ||
os.date('*t').sec | os.date('*t').sec | ||
+ | </source> | ||
Do something between 16:00 and 21:15: | Do something between 16:00 and 21:15: | ||
− | + | <source lang="lua"> | |
− | + | local t = os.date('*t') | |
− | + | local current_second = t.hour * 3600 + t.min * 60 + t.sec -- number of seconds since midnight | |
− | + | local min_time_in_seconds = 16 * 3600 + 0 * 60 -- 16:00 | |
+ | local max_time_in_seconds = 21 * 3600 + 15 * 60 -- 21:15 | ||
− | + | if (current_second > min_time_in_seconds) and (current_second < max_time_in_seconds) then | |
− | + | -- do something | |
− | + | else | |
− | + | return false | |
− | + | end | |
− | + | </source> | |
See http://forum.micasaverde.com/index.php?topic=2015.0 and http://www.lua.org/manual/5.1/manual.html#5.8. | See http://forum.micasaverde.com/index.php?topic=2015.0 and http://www.lua.org/manual/5.1/manual.html#5.8. | ||
− | === Set Z-Wave parameters === | + | === Set Z-Wave parameters === |
− | See http://forum.micasaverde.com/index.php?topic=1937.msg7803#msg7803 | + | See http://forum.micasaverde.com/index.php?topic=1937.msg7803#msg7803 |
− | === | + | === a scene if the temperature is outside of a range === |
− | add snippets here... | + | add snippets here... |
+ | |||
+ | === Reload Luup at 3 AM every day === | ||
+ | |||
+ | 1. Create a scene and set it to run daily at 3 AM. | ||
+ | |||
+ | 2. Add this code in the '''Luup code''' box: | ||
+ | |||
+ | <source lang="lua">luup.call_action("urn:micasaverde-com:serviceId:HomeAutomationGateway1", "Reload", {}, 0)</source> | ||
+ | |||
+ | 3. Save. | ||
+ | |||
+ | === Thermostat conditioned by door/window === | ||
+ | |||
+ | Turn off the thermostat if the door/window is left open for 5 minutes or more and back on if the door/window is closed for 10 minutes or more. | ||
+ | |||
+ | '''1.''' Create a scene and add a '''trigger''' to run the scene when the '''door sensor is tripped''' (door is opened). | ||
+ | |||
+ | '''2.''' Add this code in the '''Luup code''' box: <source lang="lua"> | ||
+ | local SENSOR = 17 -- The door/window sensor device number | ||
+ | local THERMOSTAT = 3 -- The thermostat device number | ||
+ | local DELAY = 300 -- Seconds | ||
+ | |||
+ | local SES_SID = "urn:micasaverde-com:serviceId:SecuritySensor1" | ||
+ | local HVACO_SID = "urn:upnp-org:serviceId:HVAC_UserOperatingMode1" | ||
+ | |||
+ | luup.call_delay( "turnOffAc", DELAY) | ||
+ | |||
+ | -- Turn off the thermostat if the sensor has been tripped for at least 5 minutes. | ||
+ | function turnOffAc() | ||
+ | local tripped = luup.variable_get( SES_SID, "Tripped", SENSOR) or "0" | ||
+ | local lastTrip = luup.variable_get( SES_SID, "LastTrip", SENSOR) or os.time() | ||
+ | if (tripped == "1" and (os.time() - lastTrip >= DELAY)) then | ||
+ | local modeStatus = luup.variable_get( HVACO_SID, "ModeStatus", THERMOSTAT) or "Off" | ||
+ | luup.variable_set( HVACO_SID, "LastModeStatus", modeStatus, THERMOSTAT) | ||
+ | luup.call_action( HVACO_SID, "SetModeTarget", {NewModeTarget = "Off"}, THERMOSTAT) | ||
+ | end | ||
+ | end | ||
+ | </source> | ||
+ | |||
+ | <br> '''3.''' Create another scene and add a '''trigger''' to run the scene when the '''door sensor is not tripped''' (door is closed). | ||
+ | |||
+ | '''4.''' Add this code in the '''Luup code''' box: <source lang="lua"> | ||
+ | local SENSOR = 17 -- The door/window sensor device number | ||
+ | local THERMOSTAT = 3 -- The thermostat device number | ||
+ | local DELAY = 600 -- Seconds | ||
+ | |||
+ | local SES_SID = "urn:micasaverde-com:serviceId:SecuritySensor1" | ||
+ | local HVACO_SID = "urn:upnp-org:serviceId:HVAC_UserOperatingMode1" | ||
+ | |||
+ | luup.call_delay( "turnOnAc", DELAY) | ||
+ | |||
+ | -- Turn on the thermostat if the sensor hasn't been tripped in the past 10 minutes. | ||
+ | function turnOnAc() | ||
+ | local tripped = luup.variable_get( SES_SID, "Tripped", SENSOR) or "0" | ||
+ | local lastTrip = luup.variable_get( SES_SID, "LastTrip", SENSOR) or os.time() | ||
+ | if (tripped == "0" and (os.time() - lastTrip >= DELAY)) then | ||
+ | local lastModeStatus = luup.variable_get( HVACO_SID, "LastModeStatus", THERMOSTAT) or "Off" | ||
+ | luup.call_action( HVACO_SID, "SetModeTarget", {NewModeTarget = lastModeStatus}, THERMOSTAT) | ||
+ | end | ||
+ | end | ||
+ | </source> | ||
+ | |||
+ | '''5.''' Save. | ||
+ | |||
+ | [[Category:Development]] |
Latest revision as of 22:05, 23 April 2013
[edit] Adding Lua code to scenes and events
You can add Lua script to scenes and events for simple tasks, like making a scene or event conditional. Conditional meaning "do something only if some condition is met", such as attaching a condition to your "Come Home" scene so it is only run if the temperature is over 70 degrees. Basic conditional expressions are easy, and, if that's all you're looking to do, skip to the walk-through below.
If you're more technically inclined you can also do very advanced things too. For an overview of all the advanced things you can do with Vera's Luup engine, and how scenes and events fit in, see the Luup Intro page.
To add Lua code to a scene, create the Scenes and click 'Luup scene'. Fill in your Lua code in the input box. To do a simple condition, see the sample below. Or if you want to do advanced scripting you can use any [Lua] commands as described in the [Lua reference manual] as well as the variables and functions that the Luup engine adds to Lua documented here: Luup Lua extensions. The Lua code will be run every time the scene is activated either by the user or a scene or a timer. The Lua code is run before the commands that you included in the scene. If your Lua code ends with this: "return false" then the commands in the scene will not be run.
You can also add Lua code to an event by clicking 'Luup event'. Since events are attached to scenes anyway, there is usually little difference between adding the code to an event, or to the scene the event is part of. If the Lua code in an event returns false, then the event is aborted, meaning the scene that the event is attached to will not be triggered by the event. The main reason for attaching Lua code to an event is if you have multiple events to a scene. For example, you may have a scene called "Turn lights on in hallway" which is triggered by 2 events: 1) The front door opens, and 2) the motion sensor in the hallway is tripped. Perhaps whenever the front door opens you always want the scene to be activated, but you only want the motion sensor to activate the scene before 10:00. In this case, you would add Lua code to the motion sensor's event which does something like (pseudo-code): "if time>10:00 return false". That way the event from the motion sensor will be aborted if it's after 10:00, but the event from the front door will always activate the scene regardless.
When you edit the Lua code in a scene or event you must click 'Save' before the code is saved. Then you can activate the scene or trigger the event to see what happens when your code is run. If you're doing some advanced scripting that you'll need to debug this can be tedious, but there are easy ways to test your Lua script immediately without saving first as explained in Lua Debugging.
If you're doing advanced Lua scripting, you should note that all the Lua code in your scenes and events run in a single Lua instance, which is separate from any Lua plugins. This means if you set a global variable in one scene, or create a function inside the Lua code in a scene, then in another scene's Lua code you can use that global variable or call that function. You do not need to worry about conflicting with a Luup plugin, though, since they have their own Lua instance, meaning they have their own global variables and functions.
[edit] Walk-through #1 -- At 12 noon, turn off the interior lights if the temperature is over 80 degrees
Before you start, in Vera's setup interface go to 'Devices' and click the '+' button next to the thermostat. Make note of the Device #.
In this walkthrough we'll assume it's Device #3, but use the actual device number ofyour thermostat. Next, visit Luup Variables to get a list of all the variables for devices. A variable is a piece of information about the current state of a device, such as whether it's on or off, it's current temperature, etc.
Look down at Thermostat, and copy the name of the service/variable which corresponds to the current temperature, namely. In this case, it's
urn:upnp-org:serviceId:TemperatureSensor1 CurrentTemperature
Notice that it states the CurrentTemperature is in Celsius. So type in "80 degrees Fahrenheit to Celsius" in Google and you'll see that it's 26.6 degrees Celsius.
Now, the first step is to create the scene that turns off the lights. In Vera's setup interface, click 'Scenes', and click 'Add Scene' to add a new scene to one of the rooms. It's not important which room you choose. Scenes are categorized in rooms just to help you keep track of them if you have a lot of scenes. You can also put the scene in 'Global Scenes', or, you can create dummy rooms on the 'Rooms' tab if you want to have more "rooms" to organize your scenes with. After you click 'Add Scene', type in a description to remember your scene by, such as "Lights off 12:00 if 80". Under the 'Commands' area you'll see all the rooms. Click '+' next to the rooms that have lights you want to control, and choose "Off" in the pull-down. At this point, you have a normal scene, and, if you were to save your changes now, whenever you click the scene on the dashboard or on a remote control, the lights should turn off.
Second, next to the scene's description click 'add timer'. You can give the timer a description too so that if you have multiple timers you can see in the logs which one is activating the scene. Choose "Day of week based". If you want this scene to only run on certain days of the week, just check off which days you want this scene to run on. Otherwise, you can leave them all unchecked (or check them all) to do it every day. Leave the pull-down at "a certain time of day", and choose 12 : 00 : 00 from the pull-downs. At this point, if you were to save your changes, the lights would turn off automatically at 12 noon.
Third, the last step is to add the condition. To the right of the scene's description you'll see the button "Luup Scene". Click it and in the code box, copy and paste the following:
local lul_temp = luup.variable_get("urn:upnp-org:serviceId:TemperatureSensor1","CurrentTemperature", 3) if (tonumber(lul_temp) < 26.6) then return false end
Don't forget to change the "3" to whatever is the actual device number of your thermostat. Assign the result of luup.variable_get to a variable first, rather than putting it directly in the tonumber(), because luup.variable_get actually returns 2 values: the value of the variable, and the time when the variable was modified. The 'tonumber' is needed because all of a device's variables are stored as plain text--not numbers--so if you want to do arithmetic or numeric comparison of a variable, you need to put tonumber() around luup.variable_get.
luup.variable_get is documented in Luup Lua extensions.
Now click 'Update', and then click 'Save' to save everything. The "return false" means "don't run this scene". So if the current temperature is <26.6, the scene will be aborted and won't run. The timer will make it trigger every day at 12 noon.
To test it, you can click the scene button on the dashboard. The lights will turn off only if the temperature is over 80 degrees. If it's less than 80 degrees, the scene won't do anything. Since this scene is something that happens automatically and you probably won't execute manually, you can go to the scene again and check the "Hidden" box so the scene doesn't show up on Vera's 'Dashboard' and on your remote controls, like the iPhone. If you want to have a scene that turns off the lights which you can run whenever you want from 'Dashboardor your remote control, you should create another scene that has the same commands and simply don't add the timers and Luup conditions, and don't check the "Hidden" box.
You can substitute other service/variables and device ID's to make other types of conditions. The "if" statement above also supports nesting with ( and ), as well as the keywords 'and' and 'or'. So the following means the scene would abort if the temperature is <26.6 and >25, unless it's <23:
local lul_temp = luup.variable_get("urn:upnp-org:serviceId:TemperatureSensor1","CurrentTemperature", 3) if ((tonumber(lul_temp) < 26.6 and tonumber(lul_temp) > 25) or tonumber(lul_temp) < 23) then return false end
If the scene doesn't run, it's possible there is a syntax error. The easiest way to test this is to copy the Lua code from the scene, then go to Devices, Luup Plugins, and "Test Luup code". Paste the code in the box and click 'go'. If the info box above the 'go' button has a check and says "Message sent successful", your code is okay. If there's an error, it says: "Code failed".
To see if that's true, use putty, or telnet or ssh to log-in to Vera, as explained in detail in Logon_Vera_SSH and Lua Debugging, and type:
cd /var/log/cmh tail -f LuaUPnP.log | grep '^01'
Now click 'Save' in Vera's setup page, even if it's gray, as that will cause Vera to restart the Luup engine and log any syntax errors. See: Lua Debugging for in-depth details on how to debug.
[edit] Walk-through #2 -- Only run the scene during the daytime
In the Luup tab for the scene paste this:
return luup.is_night() == false
That works because if the return is 'true' the scene runs, and if it's 'false' it doesn't. So during the daytime the expression is true. Or, an alternative long form:
if (luup.is_night()) then return false else return true end
[edit] Samples
This page is a wiki which anyone can edit. If you have some Lua code you think other users might find useful, feel free to add it here.
[edit] Lighting and Switch Actions
Did you see the sample here already: http://wiki.micasaverde.com/index.php/Luup_Scenes_Events
[edit] Turn an appliance switch or a Danfoss thermostat on for device #5
luup.call_action("urn:upnp-org:serviceId:SwitchPower1", "SetTarget", {newTargetValue = "1"}, 5)
[edit] Turn an appliance switch or a Danfoss thermostat off
luup.call_action("urn:upnp-org:serviceId:SwitchPower1", "SetTarget", {newTargetValue = "0"}, 5)
[edit] Do something if switch device #5 is on
local lul_tmp = luup.variable_get("urn:upnp-org:serviceId:SwitchPower1", "Status", 5) if (lul_tmp == "1") then --something to do goes here end
[edit] Dim switch #6 to 30%
luup.call_action("urn:upnp-org:serviceId:Dimming1", "SetLoadLevelTarget", {newLoadlevelTarget = "30"}, 6)
[edit] Switch on, and switch off 2 seconds later
local device = 29 -- Switch on device 29: luup.call_action("urn:upnp-org:serviceId:SwitchPower1","SetTarget",{ newTargetValue="1" },device) luup.call_delay( 'switch_off', 2) -- Call the switch off function after a delay of 2 seconds function switch_off() luup.call_action("urn:upnp-org:serviceId:SwitchPower1","SetTarget",{ newTargetValue="0" },device) end
[edit] Blinking lights (and how to delay for a number of seconds)
http://forum.micasaverde.com/index.php?topic=5127.0
[edit] Motion Sensor Actions
[edit] Arm motion sensor #7
luup.variable_set("urn:micasaverde-com:serviceId:SecuritySensor1", "Armed", "1", 7)
[edit] Disarm motion sensor #7
luup.variable_set("urn:micasaverde-com:serviceId:SecuritySensor1", "Armed", "0", 7)
Note, arming and disarming isn't a concept within UPnP or Z-Wave. It's just a flag that the Luup engine uses, and is stored in a variable we created called "Armed".
[edit] Scene Actions
[edit] Run Scene #5
Thanks "denix" on the forum for the correct syntax. "Actually, the 4th parameter IS required, but it's not used. Otherwise the command fails with an error message"
luup.call_action("urn:micasaverde-com:serviceId:HomeAutomationGateway1", "RunScene", {SceneNum = "5"}, 0)
In this case we left the device number off (the 4th parameter to luup.call_action), because the "RunScene" action is handled by the Luup engine itself--not by some device within Z-Wave, etc.
However, normally you don't need to luup.call_action in Lua code. Rather, whatever actions, or commands, you want to run, you put into the scene itself, and the only Lua code is to simply check if some condition is true and abort the scene if the condition isn't met.
[edit] Thermostat Actions
[edit] Change the Temperature on Thermostat (Cool) device #19
luup.call_action("urn:upnp-org:serviceId:TemperatureSetpoint1_Cool", "SetCurrentSetpoint", {NewCurrentSetpoint = "68"}, 19)
[edit] Change the Temperature on a Thermostat (Heat) device #19
luup.call_action("urn:upnp-org:serviceId:TemperatureSetpoint1_Heat", "SetCurrentSetpoint", {NewCurrentSetpoint = "68"}, 19)
[edit] Change the Thermostat Operating mode device #19
luup.call_action("urn:upnp-org:serviceId:HVAC_UserOperatingMode1", "SetModeTarget", {NewModeTarget = "Off"}, 19)
[edit] Camera Actions
[edit] 'Privacy' mode for Foscam FI8908[w]
local IP_address = '<IP address of camera>' local username = '<username>' local password = '<password>' local timeout = 5 function move_up() luup.inet.wget('http://' .. IP_address .. '/decoder_control.cgi?command=0', timeout, username, password) end -- center the camera; takes some time, so we have to wait 2 minutes for the command to complete luup.inet.wget('http://' .. IP_address .. '/decoder_control.cgi?command=25', timeout, username, password) luup.call_delay('move_up', 120)
[edit] Misc Actions
[edit] Playing an announcement
http://forum.micasaverde.com/index.php?topic=5466.msg36405#msg36405
[edit] Calculate sunrise and sunset
See http://forum.micasaverde.com/index.php?topic=2073.msg8132#msg8132
... or use DAD: http://forum.micasaverde.com/index.php?topic=5466.0
[edit] Access the web
[edit] Invoke HTTP URL with GET request (Method 1)
-- 5 Second timeout local status, result = luup.inet.wget("http://www.yahoo.com", 5)
[edit] Invoke HTTP URL with GET request (Method 2)
Based on code by Jim/jgc94131require('ltn12') local http = require('socket.http') -- 5 Second timeout socket.http.TIMEOUT = 5 local response_body = {} local request_body = '' local r, c, h = socket.http.request{ url = 'http://website/page?parameter1=value¶meter2=value', method = "GET", port = 80, headers = { ["Content-Length"] = string.len(request_body), ["Content-Type"] = "application/x-www-form-urlencoded" }, source = ltn12.source.string(request_body), sink = ltn12.sink.table(response_body) }
[edit] Invoke HTTP URL with POST request (Method 3)
local http = require("socket.http") -- 5 Second timeout http.TIMEOUT = 5 -- The return parameters are in a different order from luup.inet.wget(...) result, status = http.request("http://192.168.0.113/runprocess.htm", "run=run")
[edit] Access the current time
The Lua function
os.date (format, time)
converts a time value `time` into a human readable date/time string, according to `format`. If you leave out the optional `time` parameter, it defaults to current time. The `format` parameter defaults to a fairly complete format. If you specify '*t' as the format, it will return a table instead of a formatted string.
t = os.date('*t') t = {year=2010, month=2, day=19, yday=50, wday=6, hour=22, min=45, sec=45, isdst=false}
The fields are year, month, day of month, day of year, day of week, hour in 24 hour clock, minutes, seconds and if it's Daylight Savings Time.
Current hour:
os.date('*t').hour
Current minute:
os.date('*t').min
Current second:
os.date('*t').sec
Do something between 16:00 and 21:15:
local t = os.date('*t') local current_second = t.hour * 3600 + t.min * 60 + t.sec -- number of seconds since midnight local min_time_in_seconds = 16 * 3600 + 0 * 60 -- 16:00 local max_time_in_seconds = 21 * 3600 + 15 * 60 -- 21:15 if (current_second > min_time_in_seconds) and (current_second < max_time_in_seconds) then -- do something else return false end
See http://forum.micasaverde.com/index.php?topic=2015.0 and http://www.lua.org/manual/5.1/manual.html#5.8.
[edit] Set Z-Wave parameters
See http://forum.micasaverde.com/index.php?topic=1937.msg7803#msg7803
[edit] a scene if the temperature is outside of a range
add snippets here...
[edit] Reload Luup at 3 AM every day
1. Create a scene and set it to run daily at 3 AM.
2. Add this code in the Luup code box:
luup.call_action("urn:micasaverde-com:serviceId:HomeAutomationGateway1", "Reload", {}, 0)
3. Save.
[edit] Thermostat conditioned by door/window
Turn off the thermostat if the door/window is left open for 5 minutes or more and back on if the door/window is closed for 10 minutes or more.
1. Create a scene and add a trigger to run the scene when the door sensor is tripped (door is opened).
2. Add this code in the Luup code box:local SENSOR = 17 -- The door/window sensor device number local THERMOSTAT = 3 -- The thermostat device number local DELAY = 300 -- Seconds local SES_SID = "urn:micasaverde-com:serviceId:SecuritySensor1" local HVACO_SID = "urn:upnp-org:serviceId:HVAC_UserOperatingMode1" luup.call_delay( "turnOffAc", DELAY) -- Turn off the thermostat if the sensor has been tripped for at least 5 minutes. function turnOffAc() local tripped = luup.variable_get( SES_SID, "Tripped", SENSOR) or "0" local lastTrip = luup.variable_get( SES_SID, "LastTrip", SENSOR) or os.time() if (tripped == "1" and (os.time() - lastTrip >= DELAY)) then local modeStatus = luup.variable_get( HVACO_SID, "ModeStatus", THERMOSTAT) or "Off" luup.variable_set( HVACO_SID, "LastModeStatus", modeStatus, THERMOSTAT) luup.call_action( HVACO_SID, "SetModeTarget", {NewModeTarget = "Off"}, THERMOSTAT) end end
3. Create another scene and add a trigger to run the scene when the door sensor is not tripped (door is closed).
local SENSOR = 17 -- The door/window sensor device number local THERMOSTAT = 3 -- The thermostat device number local DELAY = 600 -- Seconds local SES_SID = "urn:micasaverde-com:serviceId:SecuritySensor1" local HVACO_SID = "urn:upnp-org:serviceId:HVAC_UserOperatingMode1" luup.call_delay( "turnOnAc", DELAY) -- Turn on the thermostat if the sensor hasn't been tripped in the past 10 minutes. function turnOnAc() local tripped = luup.variable_get( SES_SID, "Tripped", SENSOR) or "0" local lastTrip = luup.variable_get( SES_SID, "LastTrip", SENSOR) or os.time() if (tripped == "0" and (os.time() - lastTrip >= DELAY)) then local lastModeStatus = luup.variable_get( HVACO_SID, "LastModeStatus", THERMOSTAT) or "Off" luup.call_action( HVACO_SID, "SetModeTarget", {NewModeTarget = lastModeStatus}, THERMOSTAT) end end
5. Save.