Lua tips
m (Syntax color the Lua code, fix a few typo's) |
|||
(One intermediate revision by one user not shown) | |||
Line 1: | Line 1: | ||
+ | [[Category:Development]] | ||
Lua is a very complete and powerful language, the full reference is at http://www.lua.org. This page attempts to help with common pitfalls, especially those that are likely to appear on scripting the MIOS system. | Lua is a very complete and powerful language, the full reference is at http://www.lua.org. This page attempts to help with common pitfalls, especially those that are likely to appear on scripting the MIOS system. | ||
Line 5: | Line 6: | ||
It's customary for some functions to return a '''nil''' value to mean 'nothing'; for example, luup.variable_get() returns '''nil''' if you ask for a non-existant variable, or a wrong service or device. Unfortunately, using that '''nil''' to build a string causes an error. For example: | It's customary for some functions to return a '''nil''' value to mean 'nothing'; for example, luup.variable_get() returns '''nil''' if you ask for a non-existant variable, or a wrong service or device. Unfortunately, using that '''nil''' to build a string causes an error. For example: | ||
− | < | + | <source lang="lua"> |
− | luup.log("Dim level for device #5 is: " .. value)</ | + | local value = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5) |
+ | luup.log("Dim level for device #5 is: " .. value) | ||
+ | </source> | ||
+ | |||
This works perfectly if the device #5 supports the "Dimming" service and has a "LoadLevelTarget" variable; but if you have the wrong device, the whole script would simply fail at the next line, where it tries to concatenate a string with a '''nil''' value. | This works perfectly if the device #5 supports the "Dimming" service and has a "LoadLevelTarget" variable; but if you have the wrong device, the whole script would simply fail at the next line, where it tries to concatenate a string with a '''nil''' value. | ||
A common solution is to use the '''or''' keyword to give your variable a default value:<br> | A common solution is to use the '''or''' keyword to give your variable a default value:<br> | ||
− | < | + | <source lang="lua"> |
− | luup.log("Dim level for device #5 is: " .. value) | + | local value = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5) or "-none-" |
+ | luup.log("Dim level for device #5 is: " .. value) | ||
+ | </source> | ||
− | |||
Now, if anything gets wrong reading the value, your variable will have a value of "-none-". In most cases, it's simpler to use the empty string "" as the default value. | Now, if anything gets wrong reading the value, your variable will have a value of "-none-". In most cases, it's simpler to use the empty string "" as the default value. | ||
Line 21: | Line 26: | ||
It's always good practice to avoid 'polluting' the global variable space. Any temporal variable should be defined with the '''local''' keyword. Note that making a variable '''local''' means it's accessible only within the current block, for example: | It's always good practice to avoid 'polluting' the global variable space. Any temporal variable should be defined with the '''local''' keyword. Note that making a variable '''local''' means it's accessible only within the current block, for example: | ||
− | < | + | <source lang="lua"> |
− | + | if (value > 5) then | |
− | + | local msg = "good!" | |
− | + | end | |
− | end | + | |
− | + | luup.log(msg) | |
+ | </source> | ||
+ | |||
This won't work correctly, the 'msg' is local to the 'then' block; it can't be accessed in any other context. The loop.log() function will be called using a global 'msg' variable, which is probably '''nil''', or even worse, could have the wrong value.<br> | This won't work correctly, the 'msg' is local to the 'then' block; it can't be accessed in any other context. The loop.log() function will be called using a global 'msg' variable, which is probably '''nil''', or even worse, could have the wrong value.<br> | ||
But that also means you can define an outer block, and make your locals 'not so local'. For example:<br> | But that also means you can define an outer block, and make your locals 'not so local'. For example:<br> | ||
− | < | + | <source lang="lua"> |
+ | do | ||
+ | local innerval = 0 | ||
− | + | function increment() | |
− | + | innerval = innerval + 1 | |
− | + | end | |
− | + | ||
− | + | ||
− | + | ||
+ | function getit() | ||
+ | return innerval | ||
+ | end | ||
end | end | ||
− | + | </source> | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | </ | + | |
In this example, 'innerval' is local to the outer block, which contains two functions. Both functions can access that variable; but no other code can. This is an example of encapsulation. Using this, you can be sure that no other code can mess with your variables. In this example, it's easy to see that innerval will only be incremented, there's no way to go backwards until the system is restarted.<br> | In this example, 'innerval' is local to the outer block, which contains two functions. Both functions can access that variable; but no other code can. This is an example of encapsulation. Using this, you can be sure that no other code can mess with your variables. In this example, it's easy to see that innerval will only be incremented, there's no way to go backwards until the system is restarted.<br> | ||
In many cases in which you might want a global variable, it could be better to use a local variable for a bigger block, even a block that surrounds your whole module. | In many cases in which you might want a global variable, it could be better to use a local variable for a bigger block, even a block that surrounds your whole module. | ||
− | |||
− | |||
== Mark your own turf == | == Mark your own turf == | ||
Line 64: | Line 62: | ||
Somewhat better is to join all global variables in a single global variable that holds a table: | Somewhat better is to join all global variables in a single global variable that holds a table: | ||
− | < | + | <source lang="lua"> |
− | energymonitor.starttime = os.time() | + | energymonitor = {} |
− | energymonitor.devicecount = 15</ | + | energymonitor.starttime = os.time() |
+ | energymonitor.devicecount = 15 | ||
+ | </source> | ||
+ | |||
Note that this is what Lua modules do: each one is defined as a single table. Even the standard library follows this convention, all string related functions are contained in the global 'string' table. | Note that this is what Lua modules do: each one is defined as a single table. Even the standard library follows this convention, all string related functions are contained in the global 'string' table. | ||
One (current) limitation is that any function that you want to be called by Luup has to be global, not inside a global table. For those, the best way is to use a unique prefix. | One (current) limitation is that any function that you want to be called by Luup has to be global, not inside a global table. For those, the best way is to use a unique prefix. |
Latest revision as of 22:47, 4 September 2011
Lua is a very complete and powerful language, the full reference is at http://www.lua.org. This page attempts to help with common pitfalls, especially those that are likely to appear on scripting the MIOS system.
[edit] Get a default
It's customary for some functions to return a nil value to mean 'nothing'; for example, luup.variable_get() returns nil if you ask for a non-existant variable, or a wrong service or device. Unfortunately, using that nil to build a string causes an error. For example:
local value = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5) luup.log("Dim level for device #5 is: " .. value)
This works perfectly if the device #5 supports the "Dimming" service and has a "LoadLevelTarget" variable; but if you have the wrong device, the whole script would simply fail at the next line, where it tries to concatenate a string with a nil value.
A common solution is to use the or keyword to give your variable a default value:
local value = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5) or "-none-" luup.log("Dim level for device #5 is: " .. value)
Now, if anything gets wrong reading the value, your variable will have a value of "-none-". In most cases, it's simpler to use the empty string "" as the default value.
[edit] Hold on to your locals
It's always good practice to avoid 'polluting' the global variable space. Any temporal variable should be defined with the local keyword. Note that making a variable local means it's accessible only within the current block, for example:
if (value > 5) then local msg = "good!" end luup.log(msg)
This won't work correctly, the 'msg' is local to the 'then' block; it can't be accessed in any other context. The loop.log() function will be called using a global 'msg' variable, which is probably nil, or even worse, could have the wrong value.
But that also means you can define an outer block, and make your locals 'not so local'. For example:
do local innerval = 0 function increment() innerval = innerval + 1 end function getit() return innerval end end
In this example, 'innerval' is local to the outer block, which contains two functions. Both functions can access that variable; but no other code can. This is an example of encapsulation. Using this, you can be sure that no other code can mess with your variables. In this example, it's easy to see that innerval will only be incremented, there's no way to go backwards until the system is restarted.
In many cases in which you might want a global variable, it could be better to use a local variable for a bigger block, even a block that surrounds your whole module.
[edit] Mark your own turf
When you do have to use a global variable, keep in mind that any other code could access it and even modify it. We're not talking about malitious intentions, just about unintended interactions between two different parts of the code. likely written by different people.
To reduce the probability of errors, a common technique is to use a prefix that is likely to be unique. For example, a plugin called "Energy Monitor" could start all global variables with 'enmon_'.
Somewhat better is to join all global variables in a single global variable that holds a table:
energymonitor = {} energymonitor.starttime = os.time() energymonitor.devicecount = 15
Note that this is what Lua modules do: each one is defined as a single table. Even the standard library follows this convention, all string related functions are contained in the global 'string' table.
One (current) limitation is that any function that you want to be called by Luup has to be global, not inside a global table. For those, the best way is to use a unique prefix.